queryset order_by method added, testcase, docs
parent
b4aeae417e
commit
2fcb7fba1a
33
DOCS.rst
33
DOCS.rst
|
|
@ -173,6 +173,19 @@ ManyToManyField, ForeignKey, OneToOneField
|
||||||
<ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
<ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
||||||
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||||
|
|
||||||
|
Non-Polymorphic Queries
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
>>> ModelA.base_objects.all()
|
||||||
|
.
|
||||||
|
[ <ModelA: id 1, field1 (CharField)>,
|
||||||
|
<ModelA: id 2, field1 (CharField)>,
|
||||||
|
<ModelA: id 3, field1 (CharField)> ]
|
||||||
|
|
||||||
|
Each polymorphic model has 'base_objects' defined as a normal
|
||||||
|
Django manager. Of course, arbitrary custom managers may be
|
||||||
|
added to the models as well.
|
||||||
|
|
||||||
More Queryset Methods
|
More Queryset Methods
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
|
|
@ -180,6 +193,9 @@ More Queryset Methods
|
||||||
addition that the ``ModelX___field`` syntax can be used for the
|
addition that the ``ModelX___field`` syntax can be used for the
|
||||||
keyword arguments (but not for the non-keyword arguments).
|
keyword arguments (but not for the non-keyword arguments).
|
||||||
|
|
||||||
|
+ ``order_by()`` now similarly supports the ``ModelX___field`` syntax
|
||||||
|
for specifying ordering through a field in a submodel.
|
||||||
|
|
||||||
+ ``distinct()`` works as expected. It only regards the fields of
|
+ ``distinct()`` works as expected. It only regards the fields of
|
||||||
the base class, but this should never make a difference.
|
the base class, but this should never make a difference.
|
||||||
|
|
||||||
|
|
@ -201,19 +217,6 @@ More Queryset Methods
|
||||||
+ ``defer()`` and ``only()`` are not yet supported (support will be added
|
+ ``defer()`` and ``only()`` are not yet supported (support will be added
|
||||||
in the future).
|
in the future).
|
||||||
|
|
||||||
Non-Polymorphic Queries
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
>>> ModelA.base_objects.all()
|
|
||||||
.
|
|
||||||
[ <ModelA: id 1, field1 (CharField)>,
|
|
||||||
<ModelA: id 2, field1 (CharField)>,
|
|
||||||
<ModelA: id 3, field1 (CharField)> ]
|
|
||||||
|
|
||||||
Each polymorphic model has 'base_objects' defined as a normal
|
|
||||||
Django manager. Of course, arbitrary custom managers may be
|
|
||||||
added to the models as well.
|
|
||||||
|
|
||||||
manage.py dumpdata
|
manage.py dumpdata
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|
@ -425,8 +428,8 @@ Restrictions & Caveats
|
||||||
in PolymorphicModel, which causes some overhead. A minor patch to
|
in PolymorphicModel, which causes some overhead. A minor patch to
|
||||||
Django core would probably get rid of that.
|
Django core would probably get rid of that.
|
||||||
|
|
||||||
In General
|
Project Status
|
||||||
----------
|
--------------
|
||||||
|
|
||||||
It's important to consider that this code is very new and
|
It's important to consider that this code is very new and
|
||||||
to some extent still experimental. Please see the docs for
|
to some extent still experimental. Please see the docs for
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,11 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
additional_args = _translate_polymorphic_filter_definitions_in_kwargs(self.model, kwargs) # filter_field='data'
|
additional_args = _translate_polymorphic_filter_definitions_in_kwargs(self.model, kwargs) # filter_field='data'
|
||||||
return super(PolymorphicQuerySet, self)._filter_or_exclude(negate, *(list(args) + additional_args), **kwargs)
|
return super(PolymorphicQuerySet, self)._filter_or_exclude(negate, *(list(args) + additional_args), **kwargs)
|
||||||
|
|
||||||
|
def order_by(self, *args, **kwargs):
|
||||||
|
"""translate the field paths in the args, then call vanilla order_by."""
|
||||||
|
new_args = [ _translate_polymorphic_field_path(self.model, a) for a in args ]
|
||||||
|
return super(PolymorphicQuerySet, self).order_by(*new_args, **kwargs)
|
||||||
|
|
||||||
def _process_aggregate_args(self, args, kwargs):
|
def _process_aggregate_args(self, args, kwargs):
|
||||||
"""for aggregate and annotate kwargs: allow ModelX___field syntax for kwargs, forbid it for args.
|
"""for aggregate and annotate kwargs: allow ModelX___field syntax for kwargs, forbid it for args.
|
||||||
Modifies kwargs if needed (these are Aggregate objects, we translate the lookup member variable)"""
|
Modifies kwargs if needed (these are Aggregate objects, we translate the lookup member variable)"""
|
||||||
|
|
@ -125,7 +130,7 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
self._process_aggregate_args(args, kwargs)
|
self._process_aggregate_args(args, kwargs)
|
||||||
self.polymorphic_disabled = True
|
self.polymorphic_disabled = True
|
||||||
return super(PolymorphicQuerySet, self).aggregate(*args, **kwargs)
|
return super(PolymorphicQuerySet, self).aggregate(*args, **kwargs)
|
||||||
|
|
||||||
def extra(self, *args, **kwargs):
|
def extra(self, *args, **kwargs):
|
||||||
self.polymorphic_disabled = not kwargs.get('polymorphic',False)
|
self.polymorphic_disabled = not kwargs.get('polymorphic',False)
|
||||||
if 'polymorphic' in kwargs: kwargs.pop('polymorphic')
|
if 'polymorphic' in kwargs: kwargs.pop('polymorphic')
|
||||||
|
|
@ -345,12 +350,19 @@ def _translate_polymorphic_field_path(queryset_model, field_path):
|
||||||
"""
|
"""
|
||||||
Translate a field path from a keyword argument, as used for
|
Translate a field path from a keyword argument, as used for
|
||||||
PolymorphicQuerySet.filter()-like functions (and Q objects).
|
PolymorphicQuerySet.filter()-like functions (and Q objects).
|
||||||
|
Supports leading '-' (for order_by args).
|
||||||
|
|
||||||
E.g.: ModelC___field3 is translated into modela__modelb__modelc__field3
|
E.g.: ModelC___field3 is translated into modela__modelb__modelc__field3
|
||||||
Returns: translated path
|
Returns: translated path (unchanged, if no translation needed)
|
||||||
"""
|
"""
|
||||||
classname, sep, pure_field_path = field_path.partition('___')
|
classname, sep, pure_field_path = field_path.partition('___')
|
||||||
if not sep: return field_path
|
if not sep: return field_path
|
||||||
|
assert classname, 'PolymorphicModel: %s: bad field specification' % field_path
|
||||||
|
|
||||||
|
negated = False
|
||||||
|
if classname[0] == '-':
|
||||||
|
negated = True
|
||||||
|
classname = classname.lstrip('-')
|
||||||
|
|
||||||
if '__' in classname:
|
if '__' in classname:
|
||||||
# the user has app label prepended to class name via __ => use Django's get_model function
|
# the user has app label prepended to class name via __ => use Django's get_model function
|
||||||
|
|
@ -398,7 +410,7 @@ def _translate_polymorphic_field_path(queryset_model, field_path):
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
basepath = _create_base_path(queryset_model, model)
|
basepath = _create_base_path(queryset_model, model)
|
||||||
newpath = basepath + '__' if basepath else ''
|
newpath = ('-' if negated else '') + basepath + ('__' if basepath else '')
|
||||||
newpath += pure_field_path
|
newpath += pure_field_path
|
||||||
return newpath
|
return newpath
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ class MgrInheritC(ShowFieldsAndTypes, MgrInheritB):
|
||||||
class BlogBase(ShowFieldsAndTypes, PolymorphicModel):
|
class BlogBase(ShowFieldsAndTypes, PolymorphicModel):
|
||||||
name = models.CharField(max_length=10)
|
name = models.CharField(max_length=10)
|
||||||
class BlogA(BlogBase):
|
class BlogA(BlogBase):
|
||||||
pass
|
info = models.CharField(max_length=10)
|
||||||
class BlogB(BlogBase):
|
class BlogB(BlogBase):
|
||||||
pass
|
pass
|
||||||
class BlogA_Entry(ShowFieldsAndTypes, PolymorphicModel):
|
class BlogA_Entry(ShowFieldsAndTypes, PolymorphicModel):
|
||||||
|
|
@ -119,25 +119,55 @@ class testclass(TestCase):
|
||||||
print 'DiamondXY fields 2: field_b "%s", field_x "%s", field_y "%s"' % (o.field_b, o.field_x, o.field_y)
|
print 'DiamondXY fields 2: field_b "%s", field_x "%s", field_y "%s"' % (o.field_b, o.field_x, o.field_y)
|
||||||
if o.field_b != 'b': print '# Django model inheritance diamond problem detected'
|
if o.field_b != 'b': print '# Django model inheritance diamond problem detected'
|
||||||
|
|
||||||
def test_annotate_aggregate(self):
|
def test_annotate_aggregate_order(self):
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
|
|
||||||
BlogA.objects.all().delete()
|
BlogA.objects.all().delete()
|
||||||
blog = BlogA.objects.create(name='B1')
|
blog = BlogA.objects.create(name='B1', info='i1')
|
||||||
entry1 = blog.bloga_entry_set.create(text='bla')
|
entry1 = blog.bloga_entry_set.create(text='bla')
|
||||||
entry2 = BlogA_Entry.objects.create(blog=blog, text='bla2')
|
entry2 = BlogA_Entry.objects.create(blog=blog, text='bla2')
|
||||||
|
|
||||||
|
# create some BlogB to make the table more diverse
|
||||||
|
o = BlogB.objects.create(name='Bb1')
|
||||||
|
o = BlogB.objects.create(name='Bb2')
|
||||||
|
o = BlogB.objects.create(name='Bb3')
|
||||||
|
|
||||||
qs = BlogBase.objects.annotate(entrycount=Count('BlogA___bloga_entry'))
|
qs = BlogBase.objects.annotate(entrycount=Count('BlogA___bloga_entry'))
|
||||||
assert qs[0].entrycount == 2
|
assert qs[0].entrycount == 2
|
||||||
|
|
||||||
x = BlogBase.objects.aggregate(entrycount=Count('BlogA___bloga_entry'))
|
x = BlogBase.objects.aggregate(entrycount=Count('BlogA___bloga_entry'))
|
||||||
assert x['entrycount'] == 2
|
assert x['entrycount'] == 2
|
||||||
|
|
||||||
|
# create some more blogs for next test
|
||||||
|
b2 = BlogA.objects.create(name='B2', info='i2')
|
||||||
|
b2 = BlogA.objects.create(name='B3', info='i3')
|
||||||
|
b2 = BlogA.objects.create(name='B4', info='i4')
|
||||||
|
b2 = BlogA.objects.create(name='B5', info='i5')
|
||||||
|
|
||||||
|
# test ordering
|
||||||
|
expected = '''
|
||||||
|
[ <BlogB: id 4, name (CharField): "Bb3", >,
|
||||||
|
<BlogB: id 3, name (CharField): "Bb2", >,
|
||||||
|
<BlogB: id 2, name (CharField): "Bb1", >,
|
||||||
|
<BlogA: id 8, name (CharField): "B5", info (CharField): "i5">,
|
||||||
|
<BlogA: id 7, name (CharField): "B4", info (CharField): "i4">,
|
||||||
|
<BlogA: id 6, name (CharField): "B3", info (CharField): "i3">,
|
||||||
|
<BlogA: id 5, name (CharField): "B2", info (CharField): "i2">,
|
||||||
|
<BlogA: id 1, name (CharField): "B1", info (CharField): "i1"> ]'''
|
||||||
|
x = '\n' + repr(BlogBase.objects.order_by('-name'))
|
||||||
|
assert x == expected
|
||||||
|
|
||||||
def test_extra(self):
|
expected='''
|
||||||
Model2A.objects.create(field1='A1')
|
[ <BlogA: id 8, name (CharField): "B5", info (CharField): "i5">,
|
||||||
Model2B.objects.create(field1='B1', field2='B2')
|
<BlogA: id 7, name (CharField): "B4", info (CharField): "i4">,
|
||||||
Model2C.objects.create(field1='C1', field2='C2', field3='C3')
|
<BlogA: id 6, name (CharField): "B3", info (CharField): "i3">,
|
||||||
|
<BlogA: id 5, name (CharField): "B2", info (CharField): "i2">,
|
||||||
|
<BlogA: id 1, name (CharField): "B1", info (CharField): "i1">,
|
||||||
|
<BlogB: id 2, name (CharField): "Bb1", >,
|
||||||
|
<BlogB: id 3, name (CharField): "Bb2", >,
|
||||||
|
<BlogB: id 4, name (CharField): "Bb3", > ]'''
|
||||||
|
x = '\n' + repr(BlogBase.objects.order_by('-BlogA___info'))
|
||||||
|
assert x == expected
|
||||||
|
|
||||||
__test__ = {"doctest": """
|
__test__ = {"doctest": """
|
||||||
#######################################################
|
#######################################################
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue