Django QuerySet .defer() problem - bug or feature?
An example is better than a thousand words:
In [3]: User.objects.filter(id=19)[0] == User.objects.filter(id=19)[0]
Out[3]: True
In [4]: User.objects.filter(id=19)[0] == User.objects.filter(id=19).defer('email')[0]
Out[4]: False
Does it work like this on purpose ?
Subquestion: is there any simple way to get a regular model instance from the deferred one ?
EDIT:
It looks like contenttypes framework is patched appropriately: http://code.djangoproject.com/changeset/10523
so I would say that the Model._____eq_____() operator shouldn't look like this:
def __eq__(self, other):
return isinstance(other, self.__class__) and self._get_pk_val() == other._get_pk_val()
but more like this:
def __eq__(self, other):
return ContentType.objects.get_for_model(self) is ContentType.objects.get_for_model(oth开发者_开发技巧er) and self._get_pk_val() == other._get_pk_val()
This of course causes two DB hits for the first time, but fortunately get_for_model seems to implement cache.
Deferred queries return a different class, provided by the deferred_class_factory
:
# in db/models/query_utils.py
def deferred_class_factory(model, attrs):
"""
Returns a class object that is a copy of "model" with the specified "attrs"
being replaced with DeferredAttribute objects. The "pk_value" ties the
deferred attributes to a particular instance of the model.
"""
It is basically a proxy, as you can see from the method resolution order:
>>> x = User.objects.filter(id=1).defer("email")[0]
>>> x.__class__.__mro__
(<class 'django.contrib.auth.models.User_Deferred_email'>, \
<class 'django.contrib.auth.models.User'>, \
<class 'django.db.models.base.Model'>, <type 'object'>)
Its the normal behaviour, Because User.objects.filter(id=19)[0] will return a queryset with all of the related fields of the model, but User.objects.filter(id=19).defer('email')[0] will bring a queryset without email... So you have two querysets, one with a fewer field.
Update:
Test...
In [30]: a = User.objects.filter(id=1)[0]
In [31]: a
Out[31]: <User: mustafa>
In [27]: b = User.objects.filter(id=1).defer('username')[0]
In [28]: b
Out[28]: <User_Deferred_username: mustafa>
In [32]: a == b
Out[32]: False
In [33]: type(a)
Out[33]: <class 'django.contrib.auth.models.User'>
In [34]: type(b)
Out[34]: <class 'django.contrib.auth.models.User_Deferred_username'>
In [35]: a.username
Out[35]: u'mustafa'
In [36]: b.username
Out[36]: u'mustafa'
Defer Documentation explains this as:
A queryset that has deferred fields will still return model instances. Each deferred field will be retrieved from the database if you access that field (one at a time, not all the deferred fields at once).
EDIT 2:
In [43]: isinstance(b, a.__class__)
Out[43]: True
In [40]: User.__eq__??
Type: instancemethod
Base Class: <type 'instancemethod'>
String Form: <unbound method User.__eq__>
Namespace: Interactive
File: /home/mustafa/python/lib/django/db/models/base.py
Definition: User.__eq__(self, other)
Source:
def __eq__(self, other):
return isinstance(other, self.__class__) and self._get_pk_val() == other._get_pk_val()
== is a simple comparison and it compares two objects, it is not using related class ____eq____ method.
精彩评论