From 0a89a57ffc491f2d1ff9a19e792739f67a945be3 Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Sat, 4 Apr 2009 05:34:23 +0000 Subject: [PATCH] Fixed deferred fields and select_related() interaction. Loading related models when some fields were deferred was resulting in incorrect offsets being used into the results row, causing the wrong data to be assigned to attributes. Refs #10710. This fixes the first of two bugs reported there. git-svn-id: http://code.djangoproject.com/svn/django/trunk@10383 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/query.py | 49 ++++++++++--------- tests/regressiontests/defer_regress/models.py | 20 ++++++++ 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 9dcc031a39..9323e9b36b 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -908,31 +908,35 @@ def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0, return None restricted = requested is not None - index_end = index_start + len(klass._meta.fields) - fields = row[index_start:index_end] - if not [x for x in fields if x is not None]: - # If we only have a list of Nones, there was not related object. - obj = None - else: - load_fields = only_load and only_load.get(klass) or None - if load_fields: - # Handle deferred fields. - skip = set() - init_list = [] - pk_val = fields[klass._meta.pk_index()] - for field in klass._meta.fields: - if field.name not in load_fields: - skip.add(field.name) - else: - init_list.append(field.attname) - if skip: - klass = deferred_class_factory(klass, skip) - obj = klass(**dict(zip(init_list, fields))) + load_fields = only_load and only_load.get(klass) or None + if load_fields: + # Handle deferred fields. + skip = set() + init_list = [] + pk_val = row[index_start + klass._meta.pk_index()] + for field in klass._meta.fields: + if field.name not in load_fields: + skip.add(field.name) else: - obj = klass(*fields) + init_list.append(field.attname) + field_count = len(init_list) + fields = row[index_start : index_start + field_count] + if fields == (None,) * field_count: + obj = None + elif skip: + klass = deferred_class_factory(klass, skip) + obj = klass(**dict(zip(init_list, fields))) else: obj = klass(*fields) - index_end += offset + else: + field_count = len(klass._meta.fields) + fields = row[index_start : index_start + field_count] + if fields == (None,) * field_count: + obj = None + else: + obj = klass(*fields) + + index_end = index_start + field_count + offset for f in klass._meta.fields: if not select_related_descend(f, restricted, requested): continue @@ -948,7 +952,6 @@ def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0, setattr(obj, f.get_cache_name(), rel_obj) return obj, index_end - def delete_objects(seen_objs): """ Iterate through a list of seen classes, and remove any instances that are diff --git a/tests/regressiontests/defer_regress/models.py b/tests/regressiontests/defer_regress/models.py index 0cd6facff7..d7ba713061 100644 --- a/tests/regressiontests/defer_regress/models.py +++ b/tests/regressiontests/defer_regress/models.py @@ -17,6 +17,18 @@ class Item(models.Model): class RelatedItem(models.Model): item = models.ForeignKey(Item) +class Child(models.Model): + name = models.CharField(max_length=10) + value = models.IntegerField() + +class Leaf(models.Model): + name = models.CharField(max_length=10) + child = models.ForeignKey(Child) + value = models.IntegerField(default=42) + + def __unicode__(self): + return self.name + __test__ = {"regression_tests": """ Deferred fields should really be deferred and not accidentally use the field's default value just because they aren't passed to __init__. @@ -66,7 +78,15 @@ True >>> r.item == i True +Some further checks for select_related() and inherited model behaviour +(regression for #10710). +>>> c1 = Child.objects.create(name="c1", value=42) +>>> obj = Leaf.objects.create(name="l1", child=c1) + +>>> obj = Leaf.objects.only("name", "child").select_related()[0] +>>> obj.child.name +u'c1' """ }