mirror of
https://github.com/django-polymorphic/django-polymorphic.git
synced 2026-01-12 19:15:53 +03:00
Add option to model meta class to prevent proxy models being polymorphic
Fixes #376 #390
This commit is contained in:
parent
0d820d0641
commit
a8276fc8a5
|
|
@ -53,6 +53,12 @@ class PolymorphicModelBase(ModelBase):
|
|||
"""
|
||||
|
||||
def __new__(self, model_name, bases, attrs, **kwargs):
|
||||
polymorphic__proxy = None
|
||||
if "Meta" in attrs:
|
||||
if hasattr(attrs["Meta"], "polymorphic__proxy"):
|
||||
polymorphic__proxy = attrs["Meta"].polymorphic__proxy
|
||||
del attrs["Meta"].polymorphic__proxy
|
||||
|
||||
# Workaround compatibility issue with six.with_metaclass() and custom Django model metaclasses:
|
||||
if not attrs and model_name == "NewBase":
|
||||
return super().__new__(self, model_name, bases, attrs, **kwargs)
|
||||
|
|
@ -70,6 +76,11 @@ class PolymorphicModelBase(ModelBase):
|
|||
# for __init__ function of this class (monkeypatching inheritance accessors)
|
||||
new_class.polymorphic_super_sub_accessors_replaced = False
|
||||
|
||||
if polymorphic__proxy is not None:
|
||||
new_class._meta.polymorphic__proxy = polymorphic__proxy
|
||||
else:
|
||||
new_class._meta.polymorphic__proxy = not new_class._meta.proxy
|
||||
|
||||
# determine the name of the primary key field and store it into the class variable
|
||||
# polymorphic_primary_key_name (it is needed by query.py)
|
||||
for f in new_class._meta.fields:
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class PolymorphicManager(models.Manager):
|
|||
|
||||
def get_queryset(self):
|
||||
qs = self.queryset_class(self.model, using=self._db, hints=self._hints)
|
||||
if self.model._meta.proxy:
|
||||
if not self.model._meta.polymorphic__proxy:
|
||||
qs = qs.instance_of(self.model)
|
||||
return qs
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 4.2.26 on 2025-12-03 23:29
|
||||
# Generated by Django 4.2.26 on 2025-12-04 01:12
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
|
@ -917,6 +917,17 @@ class Migration(migrations.Migration):
|
|||
'swappable': 'POLYMORPHIC_TEST_SWAPPABLE',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AliasProxyChild',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
bases=('tests.proxybase',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProxyChild',
|
||||
fields=[
|
||||
|
|
@ -972,6 +983,17 @@ class Migration(migrations.Migration):
|
|||
},
|
||||
bases=('tests.subclassselectorproxybasemodel',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TradProxyChild',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
bases=('tests.proxybase',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Bottom',
|
||||
fields=[
|
||||
|
|
@ -1119,6 +1141,39 @@ class Migration(migrations.Migration):
|
|||
},
|
||||
bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AliasOfNonProxyChild',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
bases=('tests.nonproxychild',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='NonAliasNonProxyChild',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
bases=('tests.nonproxychild',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProxyChildAliasProxy',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
bases=('tests.tradproxychild',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PurpleHeadDuck',
|
||||
fields=[
|
||||
|
|
@ -1130,6 +1185,17 @@ class Migration(migrations.Migration):
|
|||
},
|
||||
bases=('tests.blueheadduck', models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TradProxyOnProxyChild',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
bases=('tests.proxychild',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Model2D',
|
||||
fields=[
|
||||
|
|
|
|||
|
|
@ -348,7 +348,7 @@ class UUIDPlainC(UUIDPlainB):
|
|||
field3 = models.CharField(max_length=30)
|
||||
|
||||
|
||||
# base -> proxy
|
||||
# base(poly) -> proxy
|
||||
|
||||
|
||||
class ProxyBase(PolymorphicModel):
|
||||
|
|
@ -364,7 +364,61 @@ class NonProxyChild(ProxyBase):
|
|||
name = models.CharField(max_length=30)
|
||||
|
||||
|
||||
# base -> proxy -> real models
|
||||
# A traditional django proxy models. ie proxy'ed class is alias class
|
||||
# but in django_polymorphic this is not so.
|
||||
#
|
||||
# We have model types :-
|
||||
# base(poly) / child(poly) : A concrete polymorphic model 1+ fields
|
||||
# base(non poly) : A concrete django model 1+ fields
|
||||
# proxy(poly) : A proxy model where it is considered different
|
||||
# : from it superclasses
|
||||
# proxy(Traditional Django) : A proxy model where it is an alias for the
|
||||
# : underlying model
|
||||
|
||||
|
||||
# base(poly) -> proxy(poly) -> proxy(Traditional Django)
|
||||
class TradProxyOnProxyChild(ProxyChild):
|
||||
class Meta:
|
||||
proxy = True
|
||||
polymorphic__proxy = True
|
||||
|
||||
|
||||
# base(poly) -> proxy(Traditional Django)
|
||||
class TradProxyChild(ProxyBase):
|
||||
class Meta:
|
||||
proxy = True
|
||||
polymorphic__proxy = True
|
||||
|
||||
|
||||
# base(poly) -> proxy(Traditional Django) -> proxy(poly)
|
||||
# Not really helpful model as reduces to base(poly) -> proxy(poly)
|
||||
|
||||
|
||||
# base(poly) -> child(poly) -> proxy(Traditional Django)
|
||||
class AliasOfNonProxyChild(NonProxyChild):
|
||||
class Meta:
|
||||
proxy = True
|
||||
polymorphic__proxy = True
|
||||
|
||||
|
||||
# base(poly) -> proxy(Traditional Django) -> proxy(poly)
|
||||
class ProxyChildAliasProxy(TradProxyChild):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
|
||||
# base(poly) -> proxy(poly)
|
||||
class AliasProxyChild(ProxyBase):
|
||||
class Meta:
|
||||
proxy = True
|
||||
polymorphic__proxy = True
|
||||
|
||||
|
||||
# child(poly) -> proxy(poly)
|
||||
class NonAliasNonProxyChild(NonProxyChild):
|
||||
class Meta:
|
||||
proxy = True
|
||||
polymorphic__proxy = False
|
||||
|
||||
|
||||
class ProxiedBase(ShowFieldTypeAndContent, PolymorphicModel):
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from polymorphic import query_translate
|
|||
from polymorphic.managers import PolymorphicManager
|
||||
from polymorphic.models import PolymorphicTypeInvalid, PolymorphicTypeUndefined
|
||||
from polymorphic.tests.models import (
|
||||
AliasProxyChild,
|
||||
ArtProject,
|
||||
Base,
|
||||
BlogA,
|
||||
|
|
@ -91,6 +92,11 @@ from polymorphic.tests.models import (
|
|||
UUIDResearchProject,
|
||||
Duck,
|
||||
PurpleHeadDuck,
|
||||
NonAliasNonProxyChild,
|
||||
TradProxyOnProxyChild,
|
||||
TradProxyChild,
|
||||
AliasOfNonProxyChild,
|
||||
ProxyChildAliasProxy,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -864,6 +870,58 @@ class PolymorphicTests(TransactionTestCase):
|
|||
assert ProxyBase.objects.count() == 5
|
||||
assert ProxyChild.objects.count() == 3
|
||||
|
||||
def test_queryset_on_polymorphic_proxy_model_returns_superclasses(self):
|
||||
ProxyBase.objects.create(some_data="Base1")
|
||||
AliasProxyChild.objects.create(some_data="ProxyChild1")
|
||||
AliasProxyChild.objects.create(some_data="ProxyChild2")
|
||||
ProxyChild.objects.create(some_data="PolyChild1")
|
||||
NonAliasNonProxyChild.objects.create(some_data="SubChild1")
|
||||
NonAliasNonProxyChild.objects.create(some_data="SubChild2")
|
||||
NonProxyChild.objects.create(some_data="NonProxyChild1", name="t1")
|
||||
|
||||
with self.subTest("superclasses"):
|
||||
self.assertEqual(7, ProxyBase.objects.count())
|
||||
self.assertEqual(7, AliasProxyChild.objects.count())
|
||||
with self.subTest("only complete classes"):
|
||||
# Non proxy models should not return the proxy siblings
|
||||
self.assertEqual(1, ProxyChild.objects.count())
|
||||
self.assertEqual(2, NonAliasNonProxyChild.objects.count())
|
||||
self.assertEqual(3, NonProxyChild.objects.count())
|
||||
|
||||
def test_polymorphic_proxy_object_has_different_ctype_from_base(self):
|
||||
obj1 = ProxyBase.objects.create(some_data="Base1")
|
||||
obj2 = AliasProxyChild.objects.create(some_data="ProxyChild1")
|
||||
obj1_ctype = ContentType.objects.get_for_model(obj1, for_concrete_model=False)
|
||||
obj2_ctype = ContentType.objects.get_for_model(obj2, for_concrete_model=False)
|
||||
self.assertNotEqual(obj1_ctype, obj2_ctype)
|
||||
|
||||
def test_can_create_django_style_proxy_classes_alias(self):
|
||||
ProxyBase.objects.create(some_data="Base1")
|
||||
TradProxyChild.objects.create(some_data="Base2")
|
||||
self.assertEqual(2, ProxyBase.objects.count())
|
||||
self.assertEqual(2, TradProxyChild.objects.count())
|
||||
TradProxyOnProxyChild.objects.create()
|
||||
|
||||
def test_convert_back_to_django_style_from_polymorphic(self):
|
||||
ProxyBase.objects.create(some_data="Base1")
|
||||
ProxyChild.objects.create(some_data="Base1")
|
||||
TradProxyOnProxyChild.objects.create(some_data="Base3")
|
||||
self.assertEqual(3, ProxyBase.objects.count())
|
||||
self.assertEqual(2, ProxyChild.objects.count())
|
||||
self.assertEqual(3, TradProxyOnProxyChild.objects.count())
|
||||
|
||||
def test_convert_back_to_django_style_from_polymorphic_stops_at_concrete(self):
|
||||
ProxyBase.objects.create(some_data="Base1")
|
||||
NonProxyChild.objects.create(some_data="Base1")
|
||||
AliasOfNonProxyChild.objects.create(some_data="Base1")
|
||||
|
||||
self.assertEqual(3, ProxyBase.objects.count())
|
||||
self.assertEqual(2, NonProxyChild.objects.count())
|
||||
self.assertEqual(2, AliasOfNonProxyChild.objects.count())
|
||||
|
||||
def test_revert_back_to_polymorphic_proxy(self):
|
||||
self.assertFalse(ProxyChildAliasProxy._meta.polymorphic__proxy)
|
||||
|
||||
def test_proxy_get_real_instance_class(self):
|
||||
"""
|
||||
The call to ``get_real_instance()`` also checks whether the returned model is of the correct type.
|
||||
|
|
@ -873,12 +931,12 @@ class PolymorphicTests(TransactionTestCase):
|
|||
name = "Item1"
|
||||
nonproxychild = NonProxyChild.objects.create(name=name)
|
||||
|
||||
pb = ProxyBase.objects.get(id=1)
|
||||
pb = ProxyBase.objects.get(id=nonproxychild.pk)
|
||||
assert pb.get_real_instance_class() == NonProxyChild
|
||||
assert pb.get_real_instance() == nonproxychild
|
||||
assert pb.name == name
|
||||
|
||||
pbm = NonProxyChild.objects.get(id=1)
|
||||
pbm = NonProxyChild.objects.get(id=nonproxychild.pk)
|
||||
assert pbm.get_real_instance_class() == NonProxyChild
|
||||
assert pbm.get_real_instance() == nonproxychild
|
||||
assert pbm.name == name
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user