diff --git a/docs/changelog.rst b/docs/changelog.rst index be0fa8e..5de4115 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,13 @@ Changelog ========== +Version 0.8.1 (2015-12-29) +-------------------------- + +* Fixed support for reverse relations for ``relname___field`` when the field starts with an ``_`` character. + Otherwise, the query will be interpreted as subclass lookup (``ClassName___field``). + + Version 0.8 (2015-12-28) ------------------------ diff --git a/polymorphic/query_translate.py b/polymorphic/query_translate.py index 98da463..3424eaf 100644 --- a/polymorphic/query_translate.py +++ b/polymorphic/query_translate.py @@ -4,11 +4,25 @@ """ from __future__ import absolute_import +import django from django.db import models from django.contrib.contenttypes.models import ContentType from django.db.models import Q, FieldDoesNotExist -from django.db.models.fields.related import RelatedField # Django 1.8 +from django.db.models.fields.related import RelatedField +if django.VERSION < (1, 6): + # There was no common base class in Django 1.5, mention all variants here. + from django.db.models.fields.related import RelatedObject, ManyToOneRel, ManyToManyRel + REL_FIELD_CLASSES = (RelatedField, RelatedObject, ManyToOneRel, ManyToManyRel) # Leaving GenericRel out. +elif django.VERSION < (1, 8): + # As of Django 1.6 there is a ForeignObjectRel. + from django.db.models.fields.related import ForeignObjectRel, RelatedObject + REL_FIELD_CLASSES = (RelatedField, ForeignObjectRel, RelatedObject) +else: + # As of Django 1.8 the base class serves everything. RelatedObject is gone. + from django.db.models.fields.related import ForeignObjectRel + REL_FIELD_CLASSES = (RelatedField, ForeignObjectRel) + from functools import reduce @@ -154,9 +168,13 @@ def translate_polymorphic_field_path(queryset_model, field_path): # Test whether it's actually a regular relation__ _fieldname (the field starting with an _) # so no tripple ClassName___field was intended. try: - # rel = (field_object, model, direct, m2m) - field = queryset_model._meta.get_field(classname) - if isinstance(field, RelatedField): + if django.VERSION >= (1, 8): + # This also retreives M2M relations now (including reverse foreign key relations) + field = queryset_model._meta.get_field(classname) + else: + field = queryset_model._meta.get_field_by_name(classname)[0] + + if isinstance(field, REL_FIELD_CLASSES): # Can also test whether the field exists in the related object to avoid ambiguity between # class names and field names, but that never happens when your class names are in CamelCase. return field_path # No exception raised, field does exist. diff --git a/polymorphic/tests.py b/polymorphic/tests.py index b67f584..e1278b7 100644 --- a/polymorphic/tests.py +++ b/polymorphic/tests.py @@ -159,7 +159,8 @@ class ModelUnderRelParent(PolymorphicModel): class ModelUnderRelChild(PolymorphicModel): - parent = models.ForeignKey(ModelUnderRelParent) + parent = models.ForeignKey(ModelUnderRelParent, related_name='children') + _private2 = models.CharField(max_length=10) class MyManagerQuerySet(PolymorphicQuerySet): @@ -734,12 +735,20 @@ class PolymorphicTests(TestCase): def test_polymorphic___filter_field(self): p = ModelUnderRelParent.objects.create(_private=True, field1='AA') - ModelUnderRelChild.objects.create(parent=p) + ModelUnderRelChild.objects.create(parent=p, _private2=True) # The "___" filter should also parse to "parent" -> "_private" as fallback. objects = ModelUnderRelChild.objects.filter(parent___private=True) self.assertEqual(len(objects), 1) + def test_polymorphic___filter_reverse_field(self): + p = ModelUnderRelParent.objects.create(_private=True, field1='BB') + ModelUnderRelChild.objects.create(parent=p, _private2=True) + + # Also test for reverse relations + objects = ModelUnderRelParent.objects.filter(children___private2=True) + self.assertEqual(len(objects), 1) + def test_delete(self): self.create_model2abcd()