Ticket #10695: defer.2.diff

File defer.2.diff, 9.6 KB (added by Alex Gaynor, 16 years ago)

add tests and code to make it work properly with foreing keys

  • django/contrib/gis/tests/relatedapp/tests.py

    diff --git a/django/contrib/gis/tests/relatedapp/tests.py b/django/contrib/gis/tests/relatedapp/tests.py
    index 3d162b0..acc616c 100644
    a b class RelatedGeoModelTest(unittest.TestCase):  
    184184            self.assertEqual(m.point, t[1])
    185185
    186186    # Test disabled until #10572 is resolved.
    187     #def test08_defer_only(self):
    188     #    "Testing defer() and only() on Geographic models."
    189     #    qs = Location.objects.all()
    190     #    def_qs = Location.objects.defer('point')
    191     #    for loc, def_loc in zip(qs, def_qs):
    192     #        self.assertEqual(loc.point, def_loc.point)
     187    def test08_defer_only(self):
     188        "Testing defer() and only() on Geographic models."
     189        qs = Location.objects.all()
     190        def_qs = Location.objects.defer('point')
     191        for loc, def_loc in zip(qs, def_qs):
     192            self.assertEqual(loc.point, def_loc.point)
    193193
    194194    # TODO: Related tests for KML, GML, and distance lookups.
    195195
  • 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..c7176e2 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
     173    def store_field(self, field):
     174        if field.name == field.attname:
     175            return field.get_cache_name()
     176        return "_defer%s" % field.get_cache_name()
     177
    167178    def __get__(self, instance, owner):
    168179        """
    169180        Retrieves and caches the value from the datastore on the first lookup.
    170181        Returns the cached value.
    171182        """
    172183        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):
     184        cls = self.model_ref()
     185        field = self.get_field()
     186        if not hasattr(instance, self.store_field(field)):
     187            setattr(instance, self.store_field(field), cls._base_manager.filter(pk=instance.pk).values_list(self.field_name, flat=True).get())
     188        return getattr(instance, self.store_field(field))
     189
     190    def __set__(self, instance, value):
    182191        """
    183192        Deferred loading attributes can be set normally (which means there will
    184193        never be a database lookup involved.
    185194        """
    186         self.value = value
    187         self.loaded = True
     195        field = self.get_field()
     196        setattr(instance, self.store_field(field), value)
    188197
    189198def select_related_descend(field, restricted, requested):
    190199    """
    def select_related_descend(field, restricted, requested):  
    206215# This function is needed because data descriptors must be defined on a class
    207216# object, not an instance, to have any effect.
    208217
    209 def deferred_class_factory(model, pk_value, attrs):
     218def deferred_class_factory(model, attrs):
    210219    """
    211220    Returns a class object that is a copy of "model" with the specified "attrs"
    212221    being replaced with DeferredAttribute objects. The "pk_value" ties the
    def deferred_class_factory(model, pk_value, attrs):  
    223232    # are identical.
    224233    name = "%s_Deferred_%s" % (model.__name__, '_'.join(sorted(list(attrs))))
    225234
    226     overrides = dict([(attr, DeferredAttribute(attr, pk_value, model))
     235    overrides = dict([(attr, DeferredAttribute(attr, model))
    227236            for attr in attrs])
    228237    overrides["Meta"] = Meta
    229238    overrides["__module__"] = model.__module__
    def deferred_class_factory(model, pk_value, attrs):  
    233242# The above function is also used to unpickle model instances with deferred
    234243# fields.
    235244deferred_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..598c513 100644
    a b from django.conf import settings  
    66from django.db import connection, models
    77
    88class Item(models.Model):
    9     name = models.CharField(max_length=10)
     9    name = models.CharField(max_length=15)
    1010    text = models.TextField(default="xyzzy")
    1111    value = models.IntegerField()
    1212    other_value = models.IntegerField(default=0)
    class Item(models.Model):  
    1414    def __unicode__(self):
    1515        return self.name
    1616
     17class RelatedItem(models.Model):
     18    item = models.ForeignKey(Item)
     19
    1720__test__ = {"regression_tests": """
    1821Deferred fields should really be deferred and not accidentally use the field's
    1922default value just because they aren't passed to __init__.
    u"xyzzy"  
    4043>>> len(connection.queries) == num + 2      # Effect of text lookup.
    4144True
    4245
     46>>> i = Item.objects.create(name="no I'm first", value=37)
     47>>> items = Item.objects.only('value').order_by('-value')
     48>>> items[0].name
     49u'first'
     50>>> items[1].name
     51u"no I'm first"
     52
     53>>> _ = RelatedItem.objects.create(item=i)
     54>>> r = RelatedItem.objects.defer('item').get()
     55>>> r.item_id == i.id
     56True
     57>>> r.item == i
     58True
     59
    4360>>> settings.DEBUG = False
    4461
    4562"""
    4663}
    47 
Back to Top