Code

Ticket #10695: defer.diff

File defer.diff, 7.6 KB (added by Alex, 5 years ago)
Line 
1diff --git a/django/db/models/base.py b/django/db/models/base.py
2index 01e2ca7..05cd0d9 100644
3--- a/django/db/models/base.py
4+++ b/django/db/models/base.py
5@@ -362,9 +362,8 @@ class Model(object):
6                     # DeferredAttribute classes, so we only need to do this
7                     # once.
8                     obj = self.__class__.__dict__[field.attname]
9-                    pk_val = obj.pk_value
10                     model = obj.model_ref()
11-        return (model_unpickle, (model, pk_val, defers), data)
12+        return (model_unpickle, (model, defers), data)
13 
14     def _get_pk_val(self, meta=None):
15         if not meta:
16@@ -635,12 +634,12 @@ def get_absolute_url(opts, func, self, *args, **kwargs):
17 class Empty(object):
18     pass
19 
20-def model_unpickle(model, pk_val, attrs):
21+def model_unpickle(model, attrs):
22     """
23     Used to unpickle Model subclasses with deferred fields.
24     """
25     from django.db.models.query_utils import deferred_class_factory
26-    cls = deferred_class_factory(model, pk_val, attrs)
27+    cls = deferred_class_factory(model, attrs)
28     return cls.__new__(cls)
29 model_unpickle.__safe_for_unpickle__ = True
30 
31diff --git a/django/db/models/query.py b/django/db/models/query.py
32index ea7129b..9dcc031 100644
33--- a/django/db/models/query.py
34+++ b/django/db/models/query.py
35@@ -190,6 +190,20 @@ class QuerySet(object):
36         index_start = len(extra_select)
37         aggregate_start = index_start + len(self.model._meta.fields)
38 
39+        load_fields = only_load.get(self.model)
40+        skip = None
41+        if load_fields and not fill_cache:
42+            # Some fields have been deferred, so we have to initialise
43+            # via keyword arguments.
44+            skip = set()
45+            init_list = []
46+            for field in fields:
47+                if field.name not in load_fields:
48+                    skip.add(field.attname)
49+                else:
50+                    init_list.append(field.attname)
51+            model_cls = deferred_class_factory(self.model, skip)
52+
53         for row in self.query.results_iter():
54             if fill_cache:
55                 obj, _ = get_cached_row(self.model, row,
56@@ -197,25 +211,10 @@ class QuerySet(object):
57                             requested=requested, offset=len(aggregate_select),
58                             only_load=only_load)
59             else:
60-                load_fields = only_load.get(self.model)
61-                if load_fields:
62-                    # Some fields have been deferred, so we have to initialise
63-                    # via keyword arguments.
64+                if skip:
65                     row_data = row[index_start:aggregate_start]
66                     pk_val = row_data[pk_idx]
67-                    skip = set()
68-                    init_list = []
69-                    for field in fields:
70-                        if field.name not in load_fields:
71-                            skip.add(field.attname)
72-                        else:
73-                            init_list.append(field.attname)
74-                    if skip:
75-                        model_cls = deferred_class_factory(self.model, pk_val,
76-                                skip)
77-                        obj = model_cls(**dict(zip(init_list, row_data)))
78-                    else:
79-                        obj = self.model(*row[index_start:aggregate_start])
80+                    obj = model_cls(**dict(zip(init_list, row_data)))
81                 else:
82                     # Omit aggregates in object creation.
83                     obj = self.model(*row[index_start:aggregate_start])
84@@ -927,7 +926,7 @@ def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0,
85                 else:
86                     init_list.append(field.attname)
87             if skip:
88-                klass = deferred_class_factory(klass, pk_val, skip)
89+                klass = deferred_class_factory(klass, skip)
90                 obj = klass(**dict(zip(init_list, fields)))
91             else:
92                 obj = klass(*fields)
93diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py
94index 8baa654..000ba6a 100644
95--- a/django/db/models/query_utils.py
96+++ b/django/db/models/query_utils.py
97@@ -158,33 +158,37 @@ class DeferredAttribute(object):
98     A wrapper for a deferred-loading field. When the value is read from this
99     object the first time, the query is executed.
100     """
101-    def __init__(self, field_name, pk_value, model):
102+    def __init__(self, field_name, model):
103         self.field_name = field_name
104-        self.pk_value = pk_value
105         self.model_ref = weakref.ref(model)
106         self.loaded = False
107 
108+    def get_field(self):
109+        cls = self.model_ref()
110+        for field in cls._meta.fields:
111+            if field.attname == self.field_name:
112+                break
113+        return field
114+
115     def __get__(self, instance, owner):
116         """
117         Retrieves and caches the value from the datastore on the first lookup.
118         Returns the cached value.
119         """
120         assert instance is not None
121-        if not self.loaded:
122-            obj = self.model_ref()
123-            if obj is None:
124-                return
125-            self.value = list(obj._base_manager.filter(pk=self.pk_value).values_list(self.field_name, flat=True))[0]
126-            self.loaded = True
127-        return self.value
128-
129-    def __set__(self, name, value):
130+        cls = self.model_ref()
131+        field = self.get_field()
132+        if not hasattr(instance, field.get_cache_name()):
133+            setattr(instance, field.get_cache_name(), cls._base_manager.filter(pk=instance.pk).values_list(self.field_name, flat=True).get())
134+        return getattr(instance, field.get_cache_name())
135+
136+    def __set__(self, instance, value):
137         """
138         Deferred loading attributes can be set normally (which means there will
139         never be a database lookup involved.
140         """
141-        self.value = value
142-        self.loaded = True
143+        field = self.get_field()
144+        setattr(instance, fiel.get_cache_name(), value)
145 
146 def select_related_descend(field, restricted, requested):
147     """
148@@ -206,7 +210,7 @@ def select_related_descend(field, restricted, requested):
149 # This function is needed because data descriptors must be defined on a class
150 # object, not an instance, to have any effect.
151 
152-def deferred_class_factory(model, pk_value, attrs):
153+def deferred_class_factory(model, attrs):
154     """
155     Returns a class object that is a copy of "model" with the specified "attrs"
156     being replaced with DeferredAttribute objects. The "pk_value" ties the
157@@ -223,7 +227,7 @@ def deferred_class_factory(model, pk_value, attrs):
158     # are identical.
159     name = "%s_Deferred_%s" % (model.__name__, '_'.join(sorted(list(attrs))))
160 
161-    overrides = dict([(attr, DeferredAttribute(attr, pk_value, model))
162+    overrides = dict([(attr, DeferredAttribute(attr, model))
163             for attr in attrs])
164     overrides["Meta"] = Meta
165     overrides["__module__"] = model.__module__
166@@ -233,4 +237,3 @@ def deferred_class_factory(model, pk_value, attrs):
167 # The above function is also used to unpickle model instances with deferred
168 # fields.
169 deferred_class_factory.__safe_for_unpickling__ = True
170-
171diff --git a/tests/regressiontests/defer_regress/models.py b/tests/regressiontests/defer_regress/models.py
172index c46d7ce..aa62551 100644
173--- a/tests/regressiontests/defer_regress/models.py
174+++ b/tests/regressiontests/defer_regress/models.py
175@@ -40,8 +40,14 @@ u"xyzzy"
176 >>> len(connection.queries) == num + 2      # Effect of text lookup.
177 True
178 
179+>>> _ = Item.objects.create(name="no I'm first", value=37)
180+>>> items = Item.objects.only('value').order_by('-value')
181+>>> items[0].name
182+u'first'
183+>>> items[1].name
184+u"no I'm first"
185+
186 >>> settings.DEBUG = False
187 
188 """
189 }
190-