From 2fcb7fba1a8fecbaae131fbfea43a13cd82e38b7 Mon Sep 17 00:00:00 2001 From: Bert Constantin Date: Tue, 2 Feb 2010 17:29:31 +0100 Subject: [PATCH] queryset order_by method added, testcase, docs --- DOCS.rst | 33 ++++++++++++++------------- polymorphic/polymorphic.py | 18 ++++++++++++--- polymorphic/tests.py | 46 +++++++++++++++++++++++++++++++------- 3 files changed, 71 insertions(+), 26 deletions(-) diff --git a/DOCS.rst b/DOCS.rst index 8b2bbf5..c0e6859 100644 --- a/DOCS.rst +++ b/DOCS.rst @@ -173,6 +173,19 @@ ManyToManyField, ForeignKey, OneToOneField , ] +Non-Polymorphic Queries +----------------------- + + >>> ModelA.base_objects.all() + . + [ , + , + ] + + 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 --------------------- @@ -180,6 +193,9 @@ More Queryset Methods addition that the ``ModelX___field`` syntax can be used for the 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 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 in the future). -Non-Polymorphic Queries ------------------------ - - >>> ModelA.base_objects.all() - . - [ , - , - ] - - 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 ------------------ @@ -425,8 +428,8 @@ Restrictions & Caveats in PolymorphicModel, which causes some overhead. A minor patch to Django core would probably get rid of that. -In General ----------- +Project Status +-------------- It's important to consider that this code is very new and to some extent still experimental. Please see the docs for diff --git a/polymorphic/polymorphic.py b/polymorphic/polymorphic.py index f15437b..e4c2007 100644 --- a/polymorphic/polymorphic.py +++ b/polymorphic/polymorphic.py @@ -105,6 +105,11 @@ class PolymorphicQuerySet(QuerySet): 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) + 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): """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)""" @@ -125,7 +130,7 @@ class PolymorphicQuerySet(QuerySet): self._process_aggregate_args(args, kwargs) self.polymorphic_disabled = True return super(PolymorphicQuerySet, self).aggregate(*args, **kwargs) - + def extra(self, *args, **kwargs): self.polymorphic_disabled = not kwargs.get('polymorphic',False) 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 PolymorphicQuerySet.filter()-like functions (and Q objects). + Supports leading '-' (for order_by args). 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('___') 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: # 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 '' basepath = _create_base_path(queryset_model, model) - newpath = basepath + '__' if basepath else '' + newpath = ('-' if negated else '') + basepath + ('__' if basepath else '') newpath += pure_field_path return newpath diff --git a/polymorphic/tests.py b/polymorphic/tests.py index f1e2b73..1d463ce 100644 --- a/polymorphic/tests.py +++ b/polymorphic/tests.py @@ -92,7 +92,7 @@ class MgrInheritC(ShowFieldsAndTypes, MgrInheritB): class BlogBase(ShowFieldsAndTypes, PolymorphicModel): name = models.CharField(max_length=10) class BlogA(BlogBase): - pass + info = models.CharField(max_length=10) class BlogB(BlogBase): pass 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) 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 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') 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')) assert qs[0].entrycount == 2 x = BlogBase.objects.aggregate(entrycount=Count('BlogA___bloga_entry')) 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 = ''' +[ , + , + , + , + , + , + , + ]''' + x = '\n' + repr(BlogBase.objects.order_by('-name')) + assert x == expected - def test_extra(self): - Model2A.objects.create(field1='A1') - Model2B.objects.create(field1='B1', field2='B2') - Model2C.objects.create(field1='C1', field2='C2', field3='C3') - + expected=''' +[ , + , + , + , + , + , + , + ]''' + x = '\n' + repr(BlogBase.objects.order_by('-BlogA___info')) + assert x == expected __test__ = {"doctest": """ #######################################################