From 14b31bed4ca99561c72a9d7034c59455727c0742 Mon Sep 17 00:00:00 2001 From: Lukasz Zdun Date: Sat, 29 Sep 2018 07:54:15 +0200 Subject: [PATCH] #37: Fix model subclass ___ selector for abstract/proxy models --- .gitignore | 1 + polymorphic/query_translate.py | 2 + polymorphic/tests/migrations/0001_initial.py | 62 ++++++++++++++++++++ polymorphic/tests/models.py | 28 +++++++++ polymorphic/tests/test_orm.py | 37 +++++++++--- 5 files changed, 122 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 8699683..bab3b71 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ build/ dist/ docs/_build/ htmlcov/ +venv/ diff --git a/polymorphic/query_translate.py b/polymorphic/query_translate.py index d4cc17a..29513b9 100644 --- a/polymorphic/query_translate.py +++ b/polymorphic/query_translate.py @@ -196,6 +196,8 @@ def translate_polymorphic_field_path(queryset_model, field_path): return myclass.__name__.lower() path = _create_base_path(baseclass, b) if path: + if b._meta.abstract or b._meta.proxy: + return myclass.__name__.lower() return path + '__' + myclass.__name__.lower() return '' diff --git a/polymorphic/tests/migrations/0001_initial.py b/polymorphic/tests/migrations/0001_initial.py index 148b2dc..41331a1 100644 --- a/polymorphic/tests/migrations/0001_initial.py +++ b/polymorphic/tests/migrations/0001_initial.py @@ -1130,4 +1130,66 @@ class Migration(migrations.Migration): }, bases=('tests.duck',), ), + migrations.CreateModel( + name='SubclassSelectorAbstractBaseModel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('base_field', models.CharField(default='test_bf', max_length=10)), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + ), + migrations.CreateModel( + name='SubclassSelectorProxyBaseModel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('base_field', models.CharField(default='test_bf', max_length=10)), + ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_tests.subclassselectorproxybasemodel_set+', to='contenttypes.ContentType')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + ), + migrations.CreateModel( + name='SubclassSelectorAbstractConcreteModel', + fields=[ + ('subclassselectorabstractbasemodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.SubclassSelectorAbstractBaseModel')), + ('abstract_field', models.CharField(default='test_af', max_length=10)), + ('concrete_field', models.CharField(default='test_cf', max_length=10)), + ], + options={ + 'abstract': False, + }, + bases=('tests.subclassselectorabstractbasemodel',), + ), + migrations.AddField( + model_name='subclassselectorabstractbasemodel', + name='polymorphic_ctype', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_tests.subclassselectorabstractbasemodel_set+', to='contenttypes.ContentType'), + ), + migrations.CreateModel( + name='SubclassSelectorProxyModel', + fields=[ + ], + options={ + 'proxy': True, + 'indexes': [], + }, + bases=('tests.subclassselectorproxybasemodel',), + ), + migrations.CreateModel( + name='SubclassSelectorProxyConcreteModel', + fields=[ + ('subclassselectorproxybasemodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.SubclassSelectorProxyBaseModel')), + ('concrete_field', models.CharField(default='test_cf', max_length=10)), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('tests.subclassselectorproxymodel',), + ), ] diff --git a/polymorphic/tests/models.py b/polymorphic/tests/models.py index 2ea89d5..d2f6b82 100644 --- a/polymorphic/tests/models.py +++ b/polymorphic/tests/models.py @@ -450,3 +450,31 @@ class MultiTableBase(PolymorphicModel): class MultiTableDerived(MultiTableBase): field2 = models.CharField(max_length=10) + + +class SubclassSelectorAbstractBaseModel(PolymorphicModel): + base_field = models.CharField(max_length=10, default='test_bf') + + +class SubclassSelectorAbstractModel(SubclassSelectorAbstractBaseModel): + abstract_field = models.CharField(max_length=10, default='test_af') + + class Meta: + abstract = True + + +class SubclassSelectorAbstractConcreteModel(SubclassSelectorAbstractModel): + concrete_field = models.CharField(max_length=10, default='test_cf') + + +class SubclassSelectorProxyBaseModel(PolymorphicModel): + base_field = models.CharField(max_length=10, default='test_bf') + + +class SubclassSelectorProxyModel(SubclassSelectorProxyBaseModel): + class Meta: + proxy = True + + +class SubclassSelectorProxyConcreteModel(SubclassSelectorProxyModel): + concrete_field = models.CharField(max_length=10, default='test_cf') \ No newline at end of file diff --git a/polymorphic/tests/test_orm.py b/polymorphic/tests/test_orm.py index 82fcfbb..d413fa7 100644 --- a/polymorphic/tests/test_orm.py +++ b/polymorphic/tests/test_orm.py @@ -4,7 +4,7 @@ import uuid from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models import Case, Count, Q, When -from django.test import TestCase, TransactionTestCase +from django.test import TransactionTestCase from django.utils import six from polymorphic import query_translate @@ -68,13 +68,16 @@ from polymorphic.tests.models import ( ProxyModelA, ProxyModelB, ProxyModelBase, - QuerySet, RedheadDuck, RelationA, RelationB, RelationBC, RelationBase, RubberDuck, + SubclassSelectorAbstractBaseModel, + SubclassSelectorAbstractConcreteModel, + SubclassSelectorProxyBaseModel, + SubclassSelectorProxyConcreteModel, TestParentLinkAndRelatedName, UUIDArtProject, UUIDPlainA, @@ -852,21 +855,21 @@ class PolymorphicTests(TransactionTestCase): self.assertEqual(ProxyModelB.objects.model, ProxyModelB) # Create objects - ProxyModelA.objects.create(name="object1") - ProxyModelB.objects.create(name="object2", field2="bb") + object1_pk = ProxyModelA.objects.create(name="object1").pk + object2_pk = ProxyModelB.objects.create(name="object2", field2="bb").pk # Getting single objects object1 = ProxyModelBase.objects.get(name='object1') object2 = ProxyModelBase.objects.get(name='object2') - self.assertEqual(repr(object1), '') - self.assertEqual(repr(object2), '') + self.assertEqual(repr(object1), '' % object1_pk) + self.assertEqual(repr(object2), '' % object2_pk) self.assertIsInstance(object1, ProxyModelA) self.assertIsInstance(object2, ProxyModelB) # Same for lists objects = list(ProxyModelBase.objects.all().order_by('name')) - self.assertEqual(repr(objects[0]), '') - self.assertEqual(repr(objects[1]), '') + self.assertEqual(repr(objects[0]), '' % object1_pk) + self.assertEqual(repr(objects[1]), '' % object2_pk) self.assertIsInstance(objects[0], ProxyModelA) self.assertIsInstance(objects[1], ProxyModelB) @@ -1006,3 +1009,21 @@ class PolymorphicTests(TransactionTestCase): MultiTableDerived.objects.bulk_create([ MultiTableDerived(field1='field1', field2='field2') ]) + + def test_can_query_using_subclass_selector_on_abstract_model(self): + obj = SubclassSelectorAbstractConcreteModel.objects.create(concrete_field='abc') + + queried_obj = SubclassSelectorAbstractBaseModel.objects.filter( + SubclassSelectorAbstractConcreteModel___concrete_field='abc' + ).get() + + self.assertEqual(obj.pk, queried_obj.pk) + + def test_can_query_using_subclass_selector_on_proxy_model(self): + obj = SubclassSelectorProxyConcreteModel.objects.create(concrete_field='abc') + + queried_obj = SubclassSelectorProxyBaseModel.objects.filter( + SubclassSelectorProxyConcreteModel___concrete_field='abc' + ).get() + + self.assertEqual(obj.pk, queried_obj.pk)