diff --git a/django/db/models/base.py b/django/db/models/base.py
index 01e2ca7..05cd0d9 100644
a
|
b
|
class Model(object):
|
362 | 362 | # DeferredAttribute classes, so we only need to do this |
363 | 363 | # once. |
364 | 364 | obj = self.__class__.__dict__[field.attname] |
365 | | pk_val = obj.pk_value |
366 | 365 | model = obj.model_ref() |
367 | | return (model_unpickle, (model, pk_val, defers), data) |
| 366 | return (model_unpickle, (model, defers), data) |
368 | 367 | |
369 | 368 | def _get_pk_val(self, meta=None): |
370 | 369 | if not meta: |
… |
… |
def get_absolute_url(opts, func, self, *args, **kwargs):
|
635 | 634 | class Empty(object): |
636 | 635 | pass |
637 | 636 | |
638 | | def model_unpickle(model, pk_val, attrs): |
| 637 | def model_unpickle(model, attrs): |
639 | 638 | """ |
640 | 639 | Used to unpickle Model subclasses with deferred fields. |
641 | 640 | """ |
642 | 641 | from django.db.models.query_utils import deferred_class_factory |
643 | | cls = deferred_class_factory(model, pk_val, attrs) |
| 642 | cls = deferred_class_factory(model, attrs) |
644 | 643 | return cls.__new__(cls) |
645 | 644 | model_unpickle.__safe_for_unpickle__ = True |
646 | 645 | |
diff --git a/django/db/models/query.py b/django/db/models/query.py
index ea7129b..9dcc031 100644
a
|
b
|
class QuerySet(object):
|
190 | 190 | index_start = len(extra_select) |
191 | 191 | aggregate_start = index_start + len(self.model._meta.fields) |
192 | 192 | |
| 193 | load_fields = only_load.get(self.model) |
| 194 | skip = None |
| 195 | if load_fields and not fill_cache: |
| 196 | # Some fields have been deferred, so we have to initialise |
| 197 | # via keyword arguments. |
| 198 | skip = set() |
| 199 | init_list = [] |
| 200 | for field in fields: |
| 201 | if field.name not in load_fields: |
| 202 | skip.add(field.attname) |
| 203 | else: |
| 204 | init_list.append(field.attname) |
| 205 | model_cls = deferred_class_factory(self.model, skip) |
| 206 | |
193 | 207 | for row in self.query.results_iter(): |
194 | 208 | if fill_cache: |
195 | 209 | obj, _ = get_cached_row(self.model, row, |
… |
… |
class QuerySet(object):
|
197 | 211 | requested=requested, offset=len(aggregate_select), |
198 | 212 | only_load=only_load) |
199 | 213 | else: |
200 | | load_fields = only_load.get(self.model) |
201 | | if load_fields: |
202 | | # Some fields have been deferred, so we have to initialise |
203 | | # via keyword arguments. |
| 214 | if skip: |
204 | 215 | row_data = row[index_start:aggregate_start] |
205 | 216 | pk_val = row_data[pk_idx] |
206 | | skip = set() |
207 | | init_list = [] |
208 | | for field in fields: |
209 | | if field.name not in load_fields: |
210 | | skip.add(field.attname) |
211 | | else: |
212 | | init_list.append(field.attname) |
213 | | if skip: |
214 | | model_cls = deferred_class_factory(self.model, pk_val, |
215 | | skip) |
216 | | obj = model_cls(**dict(zip(init_list, row_data))) |
217 | | else: |
218 | | obj = self.model(*row[index_start:aggregate_start]) |
| 217 | obj = model_cls(**dict(zip(init_list, row_data))) |
219 | 218 | else: |
220 | 219 | # Omit aggregates in object creation. |
221 | 220 | obj = self.model(*row[index_start:aggregate_start]) |
… |
… |
def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0,
|
927 | 926 | else: |
928 | 927 | init_list.append(field.attname) |
929 | 928 | if skip: |
930 | | klass = deferred_class_factory(klass, pk_val, skip) |
| 929 | klass = deferred_class_factory(klass, skip) |
931 | 930 | obj = klass(**dict(zip(init_list, fields))) |
932 | 931 | else: |
933 | 932 | obj = klass(*fields) |
diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py
index 8baa654..000ba6a 100644
a
|
b
|
class DeferredAttribute(object):
|
158 | 158 | A wrapper for a deferred-loading field. When the value is read from this |
159 | 159 | object the first time, the query is executed. |
160 | 160 | """ |
161 | | def __init__(self, field_name, pk_value, model): |
| 161 | def __init__(self, field_name, model): |
162 | 162 | self.field_name = field_name |
163 | | self.pk_value = pk_value |
164 | 163 | self.model_ref = weakref.ref(model) |
165 | 164 | self.loaded = False |
166 | 165 | |
| 166 | def get_field(self): |
| 167 | cls = self.model_ref() |
| 168 | for field in cls._meta.fields: |
| 169 | if field.attname == self.field_name: |
| 170 | break |
| 171 | return field |
| 172 | |
167 | 173 | def __get__(self, instance, owner): |
168 | 174 | """ |
169 | 175 | Retrieves and caches the value from the datastore on the first lookup. |
170 | 176 | Returns the cached value. |
171 | 177 | """ |
172 | 178 | assert instance is not None |
173 | | if not self.loaded: |
174 | | obj = self.model_ref() |
175 | | if obj is None: |
176 | | return |
177 | | self.value = list(obj._base_manager.filter(pk=self.pk_value).values_list(self.field_name, flat=True))[0] |
178 | | self.loaded = True |
179 | | return self.value |
180 | | |
181 | | def __set__(self, name, value): |
| 179 | cls = self.model_ref() |
| 180 | field = self.get_field() |
| 181 | if not hasattr(instance, field.get_cache_name()): |
| 182 | setattr(instance, field.get_cache_name(), cls._base_manager.filter(pk=instance.pk).values_list(self.field_name, flat=True).get()) |
| 183 | return getattr(instance, field.get_cache_name()) |
| 184 | |
| 185 | def __set__(self, instance, value): |
182 | 186 | """ |
183 | 187 | Deferred loading attributes can be set normally (which means there will |
184 | 188 | never be a database lookup involved. |
185 | 189 | """ |
186 | | self.value = value |
187 | | self.loaded = True |
| 190 | field = self.get_field() |
| 191 | setattr(instance, fiel.get_cache_name(), value) |
188 | 192 | |
189 | 193 | def select_related_descend(field, restricted, requested): |
190 | 194 | """ |
… |
… |
def select_related_descend(field, restricted, requested):
|
206 | 210 | # This function is needed because data descriptors must be defined on a class |
207 | 211 | # object, not an instance, to have any effect. |
208 | 212 | |
209 | | def deferred_class_factory(model, pk_value, attrs): |
| 213 | def deferred_class_factory(model, attrs): |
210 | 214 | """ |
211 | 215 | Returns a class object that is a copy of "model" with the specified "attrs" |
212 | 216 | being replaced with DeferredAttribute objects. The "pk_value" ties the |
… |
… |
def deferred_class_factory(model, pk_value, attrs):
|
223 | 227 | # are identical. |
224 | 228 | name = "%s_Deferred_%s" % (model.__name__, '_'.join(sorted(list(attrs)))) |
225 | 229 | |
226 | | overrides = dict([(attr, DeferredAttribute(attr, pk_value, model)) |
| 230 | overrides = dict([(attr, DeferredAttribute(attr, model)) |
227 | 231 | for attr in attrs]) |
228 | 232 | overrides["Meta"] = Meta |
229 | 233 | overrides["__module__"] = model.__module__ |
… |
… |
def deferred_class_factory(model, pk_value, attrs):
|
233 | 237 | # The above function is also used to unpickle model instances with deferred |
234 | 238 | # fields. |
235 | 239 | deferred_class_factory.__safe_for_unpickling__ = True |
236 | | |
diff --git a/tests/regressiontests/defer_regress/models.py b/tests/regressiontests/defer_regress/models.py
index c46d7ce..aa62551 100644
a
|
b
|
u"xyzzy"
|
40 | 40 | >>> len(connection.queries) == num + 2 # Effect of text lookup. |
41 | 41 | True |
42 | 42 | |
| 43 | >>> _ = Item.objects.create(name="no I'm first", value=37) |
| 44 | >>> items = Item.objects.only('value').order_by('-value') |
| 45 | >>> items[0].name |
| 46 | u'first' |
| 47 | >>> items[1].name |
| 48 | u"no I'm first" |
| 49 | |
43 | 50 | >>> settings.DEBUG = False |
44 | 51 | |
45 | 52 | """ |
46 | 53 | } |
47 | | |