Ticket #10695: defer.diff

File defer.diff, 7.6 KB (added by Alex, 6 years ago)
  • django/db/models/base.py

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    index 01e2ca7..05cd0d9 100644
    a b class Model(object): 
    362362                    # DeferredAttribute classes, so we only need to do this
    363363                    # once.
    364364                    obj = self.__class__.__dict__[field.attname]
    365                     pk_val = obj.pk_value
    366365                    model = obj.model_ref()
    367         return (model_unpickle, (model, pk_val, defers), data)
     366        return (model_unpickle, (model, defers), data)
    368367
    369368    def _get_pk_val(self, meta=None):
    370369        if not meta:
    def get_absolute_url(opts, func, self, *args, **kwargs): 
    635634class Empty(object):
    636635    pass
    637636
    638 def model_unpickle(model, pk_val, attrs):
     637def model_unpickle(model, attrs):
    639638    """
    640639    Used to unpickle Model subclasses with deferred fields.
    641640    """
    642641    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)
    644643    return cls.__new__(cls)
    645644model_unpickle.__safe_for_unpickle__ = True
    646645
  • django/db/models/query.py

    diff --git a/django/db/models/query.py b/django/db/models/query.py
    index ea7129b..9dcc031 100644
    a b class QuerySet(object): 
    190190        index_start = len(extra_select)
    191191        aggregate_start = index_start + len(self.model._meta.fields)
    192192
     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
    193207        for row in self.query.results_iter():
    194208            if fill_cache:
    195209                obj, _ = get_cached_row(self.model, row,
    class QuerySet(object): 
    197211                            requested=requested, offset=len(aggregate_select),
    198212                            only_load=only_load)
    199213            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:
    204215                    row_data = row[index_start:aggregate_start]
    205216                    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)))
    219218                else:
    220219                    # Omit aggregates in object creation.
    221220                    obj = self.model(*row[index_start:aggregate_start])
    def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0, 
    927926                else:
    928927                    init_list.append(field.attname)
    929928            if skip:
    930                 klass = deferred_class_factory(klass, pk_val, skip)
     929                klass = deferred_class_factory(klass, skip)
    931930                obj = klass(**dict(zip(init_list, fields)))
    932931            else:
    933932                obj = klass(*fields)
  • django/db/models/query_utils.py

    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): 
    158158    A wrapper for a deferred-loading field. When the value is read from this
    159159    object the first time, the query is executed.
    160160    """
    161     def __init__(self, field_name, pk_value, model):
     161    def __init__(self, field_name, model):
    162162        self.field_name = field_name
    163         self.pk_value = pk_value
    164163        self.model_ref = weakref.ref(model)
    165164        self.loaded = False
    166165
     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
    167173    def __get__(self, instance, owner):
    168174        """
    169175        Retrieves and caches the value from the datastore on the first lookup.
    170176        Returns the cached value.
    171177        """
    172178        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):
    182186        """
    183187        Deferred loading attributes can be set normally (which means there will
    184188        never be a database lookup involved.
    185189        """
    186         self.value = value
    187         self.loaded = True
     190        field = self.get_field()
     191        setattr(instance, fiel.get_cache_name(), value)
    188192
    189193def select_related_descend(field, restricted, requested):
    190194    """
    def select_related_descend(field, restricted, requested): 
    206210# This function is needed because data descriptors must be defined on a class
    207211# object, not an instance, to have any effect.
    208212
    209 def deferred_class_factory(model, pk_value, attrs):
     213def deferred_class_factory(model, attrs):
    210214    """
    211215    Returns a class object that is a copy of "model" with the specified "attrs"
    212216    being replaced with DeferredAttribute objects. The "pk_value" ties the
    def deferred_class_factory(model, pk_value, attrs): 
    223227    # are identical.
    224228    name = "%s_Deferred_%s" % (model.__name__, '_'.join(sorted(list(attrs))))
    225229
    226     overrides = dict([(attr, DeferredAttribute(attr, pk_value, model))
     230    overrides = dict([(attr, DeferredAttribute(attr, model))
    227231            for attr in attrs])
    228232    overrides["Meta"] = Meta
    229233    overrides["__module__"] = model.__module__
    def deferred_class_factory(model, pk_value, attrs): 
    233237# The above function is also used to unpickle model instances with deferred
    234238# fields.
    235239deferred_class_factory.__safe_for_unpickling__ = True
    236 
  • tests/regressiontests/defer_regress/models.py

    diff --git a/tests/regressiontests/defer_regress/models.py b/tests/regressiontests/defer_regress/models.py
    index c46d7ce..aa62551 100644
    a b u"xyzzy" 
    4040>>> len(connection.queries) == num + 2      # Effect of text lookup.
    4141True
    4242
     43>>> _ = Item.objects.create(name="no I'm first", value=37)
     44>>> items = Item.objects.only('value').order_by('-value')
     45>>> items[0].name
     46u'first'
     47>>> items[1].name
     48u"no I'm first"
     49
    4350>>> settings.DEBUG = False
    4451
    4552"""
    4653}
    47 
Back to Top