#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.