queryset order_by method added, testcase, docs

fix_request_path_info
Bert Constantin 2010-02-02 17:29:31 +01:00
parent b4aeae417e
commit 2fcb7fba1a
3 changed files with 71 additions and 26 deletions

View File

@ -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

View File

@ -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

View File

@ -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": """
####################################################### #######################################################