#17485: Queries with both deferred fields and select_related defer field.name
instead of field.attname
----------------------------------------------+----------------------------
     Reporter:  koniiiik                      |      Owner:  nobody
         Type:  Bug                           |     Status:  new
    Component:  Database layer (models, ORM)  |    Version:  SVN
     Severity:  Normal                        |   Keywords:  defer
 Triage Stage:  Unreviewed                    |  select_related
Easy pickings:  0                             |  Has patch:  1
                                              |      UI/UX:  0
----------------------------------------------+----------------------------
 When deferring `ForeignKey` fields in conjunction with `select_related`,
 Django creates a `DeferredAttribute` for the field's `name` instead of its
 `attname`. This results in its `ReverseSingleRelatedObjectDescriptor`
 being overriden and thus stripping the model instance of most of this
 field's functionality.

 Consider the following models (taken from regressiontests.queries):
 {{{#!python
 class Celebrity(models.Model):
     name = models.CharField("Name", max_length=20)
     greatest_fan = models.ForeignKey("Fan", null=True, unique=True)

     def __unicode__(self):
         return self.name

 class Fan(models.Model):
     fan_of = models.ForeignKey(Celebrity)
 }}}

 Let's play around with it in the shell for a bit:
 {{{
 >>> Celebrity.objects.create(name="Joe")
 <Celebrity: Joe>
 >>> obj =
 
Celebrity.objects.all().defer('greatest_fan').select_related('greatest_fan').get()
 >>> print obj.__class__.__dict__
 {
   '__module__': 'subclassfktest.models',
   '_meta': <Options for Celebrity_Deferred_greatest_fan>,
   'objects': <django.db.models.manager.ManagerDescriptor object at
 0x1460810>,
   'MultipleObjectsReturned': <class
 'subclassfktest.models.MultipleObjectsReturned'>,
   '_base_manager': <django.db.models.manager.Manager object at 0x1460490>,
   'greatest_fan': <django.db.models.query_utils.DeferredAttribute object
 at 0x1457750>,
   'DoesNotExist': <class 'subclassfktest.models.DoesNotExist'>,
   '__doc__': 'Celebrity_Deferred_greatest_fan(id, name, greatest_fan_id)',
   '_default_manager': <django.db.models.manager.Manager object at
 0x1460ad0>,
   '_deferred': True
 }
 }}}

 We can see that the deferred atribute is at `greatest_fan` instead of
 `greatest_fan_id` and the `ReverseSingleRelatedObjectDescriptor` is not
 present at all.

 The following is just to show that the model instance is really broken
 because of this:

 {{{
 >>> print obj.greatest_fan
 None
 >>> f = Fan.objects.create(fan_of=obj)
 >>> obj.greatest_fan = f
 >>> obj.save()
 >>> obj2 = Celebrity.objects.get()
 >>> f.id
 1
 >>> print obj2.greatest_fan_id        # Should be 1
 None
 }}}

 An interesting thing, though, is that the instance gives access to the
 right related object instance even without a working
 `ReverseSingleRelatedObjectDescriptor`:

 {{{
 >>> obj2.greatest_fan = f
 >>> obj2.save()
 >>> obj =
 
Celebrity.objects.all().defer('greatest_fan').select_related('greatest_fan').get()
 >>> obj.greatest_fan.id
 1
 >>> obj.__dict__
 {'_greatest_fan_cache': <Fan: Fan object>, 'name': u'Joe',
 'greatest_fan_id': None, '_state': <django.db.models.base.ModelState
 object at 0x146b250>, 'greatest_fan': <Fan: Fan object>, 'id': 1}
 }}}

 Anyway, the `_id` attribute is still never set to the correct value.

 The fix for this behavior is a one-liner, see attachment. It makes sure to
 defer the field's `attname` even in this case (in all other cases where
 `deferred_class_factory` is called, `attnames` are used). There are side
 effects, though.

 Actually, this bug was hiding another one for which even tests exist but
 because of this one they pass.

 The first test failure is `test_basic` in
 `regressiontests.defer_regress.tests.DeferRegressionTest` which is quite
 obvious, it lists the incorrect model name caused by this bug and is fixed
 easily (since it is actually an incorrect test).

 The other one, though, is not that simple: `test_defer` in
 `modeltests.defer.tests.DeferTests`. More precisely the following two
 lines:
 {{{#!python
         # DOES THIS WORK?
         self.assert_delayed(qs.only("name").select_related("related")[0],
 1)
 self.assert_delayed(qs.defer("related").select_related("related")[0], 0)
 }}}
 where `related` is a ForeignKey.

 These have been around for a while and the only reason they passed until
 now is that `assert_delayed` checks the `attname` of each field whether it
 is deferred or not. In this case, however, the `attname` is obviously not
 deferred, since the `name` is in its stead.

 The question is, what should we do with these two tests? Do we want to fix
 them in one go with this bug or file a new ticket and temporarily comment
 out the two failing test lines?

-- 
Ticket URL: <https://code.djangoproject.com/ticket/17485>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to