diff --git a/django/db/models/base.py b/django/db/models/base.py
index bf9854d..b0a5094 100644
a
|
b
|
class Model(six.with_metaclass(ModelBase, object)):
|
407 | 407 | super(Model, self).__init__() |
408 | 408 | signals.post_init.send(sender=self.__class__, instance=self) |
409 | 409 | |
| 410 | @classmethod |
| 411 | def from_db(cls, using, values, field_names, init_with_args, can_fast_init): |
| 412 | if can_fast_init: |
| 413 | new = Empty() |
| 414 | new.__class__ = cls |
| 415 | new.__dict__ = dict(zip(field_names, values)) |
| 416 | new._state = ModelState() |
| 417 | elif init_with_args: |
| 418 | new = cls(*values) |
| 419 | else: |
| 420 | new = cls(**dict(zip(field_names, values))) |
| 421 | new._state.adding, new._state.db = False, using |
| 422 | return new |
| 423 | |
410 | 424 | def __repr__(self): |
411 | 425 | try: |
412 | 426 | u = six.text_type(self) |
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 45b32b0..5e82c4c 100644
a
|
b
|
from django.db.models.loading import get_models, app_cache_ready
|
12 | 12 | from django.utils import six |
13 | 13 | from django.utils.datastructures import SortedDict |
14 | 14 | from django.utils.encoding import force_text, smart_text, python_2_unicode_compatible |
| 15 | from django.utils.functional import cached_property |
15 | 16 | from django.utils.translation import activate, deactivate_all, get_language, string_concat |
16 | 17 | |
17 | 18 | # Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces". |
… |
… |
class Options(object):
|
352 | 353 | # the main example). Trim them. |
353 | 354 | return [val for val in names if not val.endswith('+')] |
354 | 355 | |
| 356 | @cached_property |
| 357 | def attnames(self): |
| 358 | return [f.attname for f in self.fields] |
| 359 | |
355 | 360 | def init_name_map(self): |
356 | 361 | """ |
357 | 362 | Initialises the field name -> field object mapping. |
… |
… |
class Options(object):
|
520 | 525 | # of the chain to the ancestor is that parent |
521 | 526 | # links |
522 | 527 | return self.parents[parent] or parent_link |
| 528 | |
| 529 | def can_fast_init(self, attnames): |
| 530 | """ |
| 531 | Checks if it is possible to skip Model.__init__ and instead use |
| 532 | a fast-path for the init. |
| 533 | |
| 534 | The skip can be done if the model doesn't override `__int__`, doesn't |
| 535 | have pre or post_init signals and doesn't have setters for the |
| 536 | attnames (either through __setattr__ or descriptors). |
| 537 | """ |
| 538 | from django.db.models import Model, signals |
| 539 | has_descriptors = False |
| 540 | for attname in attnames: |
| 541 | if attname in self.model.__dict__ and hasattr(self.model.__dict__[attname], '__set__'): |
| 542 | has_descriptors = True |
| 543 | break |
| 544 | return ( |
| 545 | self.model.__init__ == Model.__init__ and |
| 546 | self.model.__setattr__ == object.__setattr__ and not |
| 547 | signals.pre_init.has_listeners(self.model) and not |
| 548 | signals.post_init.has_listeners(self.model) and not |
| 549 | has_descriptors) |
diff --git a/django/db/models/query.py b/django/db/models/query.py
index f56d5d2..d6b781e 100644
a
|
b
|
from django.db.models.query_utils import (Q, select_related_descend,
|
15 | 15 | deferred_class_factory, InvalidQuery) |
16 | 16 | from django.db.models.deletion import Collector |
17 | 17 | from django.db.models import sql |
18 | | from django.utils.functional import partition |
19 | 18 | from django.utils import six |
| 19 | from django.utils.functional import partition |
20 | 20 | |
21 | 21 | # Used to control how many objects are worked with at once in some cases (e.g. |
22 | 22 | # when deleting objects). |
… |
… |
class QuerySet(object):
|
288 | 288 | else: |
289 | 289 | init_list.append(field.attname) |
290 | 290 | model_cls = deferred_class_factory(self.model, skip) |
| 291 | else: |
| 292 | model_cls = self.model |
| 293 | init_list = [f.attname for f in model_cls._meta.fields] |
291 | 294 | |
292 | | # Cache db, model and known_related_object outside the loop |
| 295 | # Pre-calculate & cache everything possible outside the loop. |
293 | 296 | db = self.db |
294 | 297 | model = self.model |
295 | 298 | kro_attname, kro_instance = self._known_related_object or (None, None) |
296 | 299 | compiler = self.query.get_compiler(using=db) |
| 300 | can_fast_init = model._meta.can_fast_init(init_list) |
| 301 | init_with_kwargs = not bool(skip) |
297 | 302 | if fill_cache: |
298 | 303 | klass_info = get_klass_info(model, max_depth=max_depth, |
299 | 304 | requested=requested, only_load=only_load) |
… |
… |
class QuerySet(object):
|
302 | 307 | obj, _ = get_cached_row(row, index_start, db, klass_info, |
303 | 308 | offset=len(aggregate_select)) |
304 | 309 | else: |
305 | | # Omit aggregates in object creation. |
306 | | row_data = row[index_start:aggregate_start] |
307 | | if skip: |
308 | | obj = model_cls(**dict(zip(init_list, row_data))) |
309 | | else: |
310 | | obj = model(*row_data) |
311 | | |
312 | | # Store the source database of the object |
313 | | obj._state.db = db |
314 | | # This object came from the database; it's not being added. |
315 | | obj._state.adding = False |
| 310 | obj = model_cls.from_db(db, row[index_start:aggregate_start], init_list, |
| 311 | init_with_kwargs, can_fast_init) |
316 | 312 | |
317 | 313 | if extra_select: |
318 | 314 | for i, k in enumerate(extra_select): |
… |
… |
def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
|
1411 | 1407 | else: |
1412 | 1408 | pk_idx = klass._meta.pk_index() |
1413 | 1409 | |
1414 | | return klass, field_names, field_count, related_fields, reverse_related_fields, pk_idx |
| 1410 | can_fast_init = klass._meta.can_fast_init(field_names) |
| 1411 | return klass, field_names, field_count, related_fields, reverse_related_fields, pk_idx, can_fast_init |
1415 | 1412 | |
1416 | 1413 | |
1417 | 1414 | def get_cached_row(row, index_start, using, klass_info, offset=0, |
… |
… |
def get_cached_row(row, index_start, using, klass_info, offset=0,
|
1437 | 1434 | """ |
1438 | 1435 | if klass_info is None: |
1439 | 1436 | return None |
1440 | | klass, field_names, field_count, related_fields, reverse_related_fields, pk_idx = klass_info |
| 1437 | klass, field_names, field_count, related_fields, reverse_related_fields, pk_idx, can_fast_init = klass_info |
1441 | 1438 | |
1442 | | |
1443 | | fields = row[index_start : index_start + field_count] |
| 1439 | fields = row[index_start: index_start + field_count] |
1444 | 1440 | # If the pk column is None (or the Oracle equivalent ''), then the related |
1445 | 1441 | # object must be non-existent - set the relation to None. |
1446 | | if fields[pk_idx] == None or fields[pk_idx] == '': |
| 1442 | if fields[pk_idx] is None or fields[pk_idx] == '': |
1447 | 1443 | obj = None |
1448 | 1444 | elif field_names: |
1449 | 1445 | fields = list(fields) |
1450 | 1446 | for rel_field, value in parent_data: |
1451 | 1447 | field_names.append(rel_field.attname) |
1452 | 1448 | fields.append(value) |
1453 | | obj = klass(**dict(zip(field_names, fields))) |
| 1449 | obj = klass.from_db(using, fields, field_names, False, can_fast_init) |
1454 | 1450 | else: |
1455 | | obj = klass(*fields) |
1456 | | # If an object was retrieved, set the database state. |
1457 | | if obj: |
1458 | | obj._state.db = using |
1459 | | obj._state.adding = False |
| 1451 | obj = klass.from_db(using, fields, klass._meta.attnames, True, can_fast_init) |
1460 | 1452 | |
1461 | 1453 | # Instantiate related fields |
1462 | 1454 | index_end = index_start + field_count + offset |
… |
… |
class RawQuerySet(object):
|
1529 | 1521 | self.translations = translations or {} |
1530 | 1522 | |
1531 | 1523 | def __iter__(self): |
1532 | | # Mapping of attrnames to row column positions. Used for constructing |
1533 | | # the model using kwargs, needed when not all model's fields are present |
1534 | | # in the query. |
1535 | | model_init_field_names = {} |
1536 | 1524 | # A list of tuples of (column name, column position). Used for |
1537 | 1525 | # annotation fields. |
1538 | 1526 | annotation_fields = [] |
… |
… |
class RawQuerySet(object):
|
1548 | 1536 | |
1549 | 1537 | # Find out which columns are model's fields, and which ones should be |
1550 | 1538 | # annotated to the model. |
| 1539 | attname_to_colpos = {} |
1551 | 1540 | for pos, column in enumerate(self.columns): |
1552 | 1541 | if column in self.model_fields: |
1553 | | model_init_field_names[self.model_fields[column].attname] = pos |
| 1542 | attname_to_colpos[self.model_fields[column].attname] = pos |
1554 | 1543 | else: |
1555 | 1544 | annotation_fields.append((column, pos)) |
1556 | 1545 | |
1557 | 1546 | # Find out which model's fields are not present in the query. |
1558 | 1547 | skip = set() |
| 1548 | # List of positions of model's fetched fields in the output. |
| 1549 | field_col_pos = [] |
| 1550 | model_init_attnames = [] |
1559 | 1551 | for field in self.model._meta.fields: |
1560 | | if field.attname not in model_init_field_names: |
| 1552 | if field.attname not in attname_to_colpos: |
1561 | 1553 | skip.add(field.attname) |
| 1554 | else: |
| 1555 | # Find out the pos of this field. |
| 1556 | field_col_pos.append(attname_to_colpos[field.attname]) |
| 1557 | model_init_attnames.append(field.attname) |
| 1558 | |
1562 | 1559 | if skip: |
1563 | 1560 | if self.model._meta.pk.attname in skip: |
1564 | 1561 | raise InvalidQuery('Raw query must include the primary key') |
1565 | 1562 | model_cls = deferred_class_factory(self.model, skip) |
1566 | 1563 | else: |
| 1564 | # All model's fields are present in the query. |
1567 | 1565 | model_cls = self.model |
1568 | | # All model's fields are present in the query. So, it is possible |
1569 | | # to use *args based model instantation. For each field of the model, |
1570 | | # record the query column position matching that field. |
1571 | | model_init_field_pos = [] |
1572 | | for field in self.model._meta.fields: |
1573 | | model_init_field_pos.append(model_init_field_names[field.attname]) |
1574 | 1566 | if need_resolv_columns: |
1575 | 1567 | fields = [self.model_fields.get(c, None) for c in self.columns] |
| 1568 | can_fast_init = model_cls._meta.can_fast_init(model_init_attnames) |
| 1569 | init_with_args = not bool(skip) |
1576 | 1570 | # Begin looping through the query values. |
1577 | 1571 | for values in query: |
1578 | 1572 | if need_resolv_columns: |
1579 | 1573 | values = compiler.resolve_columns(values, fields) |
1580 | 1574 | # Associate fields to values |
1581 | | if skip: |
1582 | | model_init_kwargs = {} |
1583 | | for attname, pos in six.iteritems(model_init_field_names): |
1584 | | model_init_kwargs[attname] = values[pos] |
1585 | | instance = model_cls(**model_init_kwargs) |
1586 | | else: |
1587 | | model_init_args = [values[pos] for pos in model_init_field_pos] |
1588 | | instance = model_cls(*model_init_args) |
| 1575 | model_init_args = [values[pos] for pos in field_col_pos] |
| 1576 | instance = model_cls.from_db( |
| 1577 | db, model_init_args, model_init_attnames, |
| 1578 | init_with_args, can_fast_init) |
1589 | 1579 | if annotation_fields: |
1590 | 1580 | for column, pos in annotation_fields: |
1591 | 1581 | setattr(instance, column, values[pos]) |
1592 | 1582 | |
1593 | | instance._state.db = db |
1594 | | instance._state.adding = False |
1595 | | |
1596 | 1583 | yield instance |
1597 | 1584 | |
1598 | 1585 | def __repr__(self): |
diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py
index c1a690a..a2b2814 100644
a
|
b
|
def select_related_descend(field, restricted, requested, load_fields, reverse=Fa
|
166 | 166 | # This function is needed because data descriptors must be defined on a class |
167 | 167 | # object, not an instance, to have any effect. |
168 | 168 | |
| 169 | _cls_cache = {} |
169 | 170 | def deferred_class_factory(model, attrs): |
170 | 171 | """ |
171 | 172 | Returns a class object that is a copy of "model" with the specified "attrs" |
172 | 173 | being replaced with DeferredAttribute objects. The "pk_value" ties the |
173 | 174 | deferred attributes to a particular instance of the model. |
174 | 175 | """ |
| 176 | cache_key = model, frozenset(attrs) |
| 177 | if cache_key in _cls_cache: |
| 178 | return _cls_cache[cache_key] |
175 | 179 | class Meta: |
176 | 180 | proxy = True |
177 | 181 | app_label = model._meta.app_label |
… |
… |
def deferred_class_factory(model, attrs):
|
188 | 192 | overrides["Meta"] = Meta |
189 | 193 | overrides["__module__"] = model.__module__ |
190 | 194 | overrides["_deferred"] = True |
191 | | return type(str(name), (model,), overrides) |
| 195 | ret = type(str(name), (model,), overrides) |
| 196 | _cls_cache[cache_key] = ret |
| 197 | return ret |
192 | 198 | |
193 | 199 | # The above function is also used to unpickle model instances with deferred |
194 | 200 | # fields. |
diff --git a/tests/regressiontests/select_related_regress/models.py b/tests/regressiontests/select_related_regress/models.py
index a291a54..0d4ebbb 100644
a
|
b
|
class Item(models.Model):
|
94 | 94 | |
95 | 95 | def __str__(self): |
96 | 96 | return self.name |
| 97 | |
| 98 | # Models for fast-path init tests |
| 99 | class Rel(models.Model): |
| 100 | name = models.CharField(max_length=10) |
| 101 | type = models.CharField(max_length=10) |
| 102 | |
| 103 | def __init__(self, *args, **kwargs): |
| 104 | # Override init so that fast-path is skipped |
| 105 | super(Rel, self).__init__(*args, **kwargs) |
| 106 | |
| 107 | class Base(models.Model): |
| 108 | name = models.CharField(max_length=10) |
| 109 | type = models.CharField(max_length=10) |
| 110 | rel = models.ForeignKey(Rel) |
| 111 | |
| 112 | def __init__(self, *args, **kwargs): |
| 113 | # Override init so that fast-path is skipped |
| 114 | super(Base, self).__init__(*args, **kwargs) |
diff --git a/tests/regressiontests/select_related_regress/tests.py b/tests/regressiontests/select_related_regress/tests.py
index 7f93a1c..096f062 100644
a
|
b
|
from django.utils import six
|
5 | 5 | |
6 | 6 | from .models import (Building, Child, Device, Port, Item, Country, Connection, |
7 | 7 | ClientStatus, State, Client, SpecialClient, TUser, Person, Student, |
8 | | Organizer, Class, Enrollment) |
| 8 | Organizer, Class, Enrollment, Rel, Base) |
9 | 9 | |
10 | 10 | |
11 | 11 | class SelectRelatedRegressTests(TestCase): |
… |
… |
class SelectRelatedRegressTests(TestCase):
|
139 | 139 | self.assertEqual(troy.name, 'Troy Buswell') |
140 | 140 | self.assertEqual(troy.value, 42) |
141 | 141 | self.assertEqual(troy.state.name, 'Western Australia') |
| 142 | |
| 143 | def test_no_fastinit_select_related(self): |
| 144 | r1 = Rel.objects.create(name='r1', type='r1t1') |
| 145 | b1 = Base.objects.create(name='b1', type='b1t1', rel=r1) |
| 146 | qs = Base.objects.select_related('rel').only('type', 'rel__type') |
| 147 | self.assertEqual(len(qs), 1) |
| 148 | with self.assertNumQueries(0): |
| 149 | self.assertEqual(qs[0].type, b1.type) |
| 150 | self.assertEqual(qs[0].rel.type, r1.type) |
| 151 | with self.assertNumQueries(2): |
| 152 | self.assertEqual(qs[0].name, b1.name) |
| 153 | self.assertEqual(qs[0].rel.name, r1.name) |