diff --git a/.gitignore b/.gitignore index 86996837..bab3b717 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 d4cc17a4..29513b96 100644 --- a/polymorphic/query_translate.py +++ b/polymorphic/query_translate.py @@ -196,6 +196,8 @@ def _create_base_path(baseclass, myclass): 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 148b2dc8..41331a1a 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 c74ae1e8..a77f6841 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 3c70e282..e379ed94 100644 --- a/polymorphic/tests/test_orm.py +++ b/polymorphic/tests/test_orm.py @@ -74,6 +74,10 @@ RelationBC, RelationBase, RubberDuck, + SubclassSelectorAbstractBaseModel, + SubclassSelectorAbstractConcreteModel, + SubclassSelectorProxyBaseModel, + SubclassSelectorProxyConcreteModel, TestParentLinkAndRelatedName, UUIDArtProject, UUIDPlainA, @@ -859,21 +863,21 @@ def test_proxy_model_inheritance(self): 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) @@ -1024,3 +1028,21 @@ def test_bulk_create_unsupported_multi_table_inheritance(self): 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)