Skip to content

Commit 6990167

Browse files
SimonFoncecapgammans
authored andcommitted
Add option to model meta class to prevent proxy models being polymorphic
Fixes #376 #390
1 parent bd5faf0 commit 6990167

File tree

5 files changed

+209
-5
lines changed

5 files changed

+209
-5
lines changed

polymorphic/base.py

+11
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ class PolymorphicModelBase(ModelBase):
5757
def __new__(self, model_name, bases, attrs, **kwargs):
5858
# print; print '###', model_name, '- bases:', bases
5959

60+
polymorphic__proxy = None
61+
if "Meta" in attrs:
62+
if hasattr(attrs["Meta"], "polymorphic__proxy"):
63+
polymorphic__proxy = attrs["Meta"].polymorphic__proxy
64+
del attrs["Meta"].polymorphic__proxy
65+
6066
# Workaround compatibility issue with six.with_metaclass() and custom Django model metaclasses:
6167
if not attrs and model_name == "NewBase":
6268
return super().__new__(self, model_name, bases, attrs, **kwargs)
@@ -83,6 +89,11 @@ def __new__(self, model_name, bases, attrs, **kwargs):
8389
# for __init__ function of this class (monkeypatching inheritance accessors)
8490
new_class.polymorphic_super_sub_accessors_replaced = False
8591

92+
if polymorphic__proxy is not None:
93+
new_class._meta.polymorphic__proxy = polymorphic__proxy
94+
else:
95+
new_class._meta.polymorphic__proxy = not new_class._meta.proxy
96+
8697
# determine the name of the primary key field and store it into the class variable
8798
# polymorphic_primary_key_name (it is needed by query.py)
8899
for f in new_class._meta.fields:

polymorphic/managers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def from_queryset(cls, queryset_class, class_name=None):
2828

2929
def get_queryset(self):
3030
qs = self.queryset_class(self.model, using=self._db, hints=self._hints)
31-
if self.model._meta.proxy:
31+
if not self.model._meta.polymorphic__proxy:
3232
qs = qs.instance_of(self.model)
3333
return qs
3434

polymorphic/tests/migrations/0001_initial.py

+78
Original file line numberDiff line numberDiff line change
@@ -1510,6 +1510,84 @@ class Migration(migrations.Migration):
15101510
options={"proxy": True},
15111511
bases=("tests.proxybase",),
15121512
),
1513+
migrations.CreateModel(
1514+
name="AliasProxyChild",
1515+
fields=[],
1516+
options={"proxy": True},
1517+
bases=("tests.proxybase",),
1518+
),
1519+
migrations.CreateModel(
1520+
name="NonProxyChildAliasProxy",
1521+
fields=[],
1522+
options={"proxy": True},
1523+
bases=("tests.nonproxychild",),
1524+
),
1525+
migrations.CreateModel(
1526+
name='AliasOfNonProxyChild',
1527+
fields=[
1528+
],
1529+
options={
1530+
'proxy': True,
1531+
'indexes': [],
1532+
'constraints': [],
1533+
},
1534+
bases=('tests.nonproxychild',),
1535+
),
1536+
migrations.CreateModel(
1537+
name='NonAliasNonProxyChild',
1538+
fields=[
1539+
],
1540+
options={
1541+
'proxy': True,
1542+
'indexes': [],
1543+
'constraints': [],
1544+
},
1545+
bases=('tests.nonproxychild',),
1546+
),
1547+
migrations.CreateModel(
1548+
name='TradProxyChild',
1549+
fields=[
1550+
],
1551+
options={
1552+
'proxy': True,
1553+
'indexes': [],
1554+
'constraints': [],
1555+
},
1556+
bases=('tests.proxybase',),
1557+
),
1558+
migrations.CreateModel(
1559+
name='TradProxyOnProxyChild',
1560+
fields=[
1561+
],
1562+
options={
1563+
'proxy': True,
1564+
'indexes': [],
1565+
'constraints': [],
1566+
},
1567+
bases=('tests.proxychild',),
1568+
),
1569+
migrations.CreateModel(
1570+
name='PolyTradProxyChild',
1571+
fields=[
1572+
],
1573+
options={
1574+
'proxy': True,
1575+
'indexes': [],
1576+
'constraints': [],
1577+
},
1578+
bases=('tests.tradproxychild',),
1579+
),
1580+
migrations.CreateModel(
1581+
name='ProxyChildAliasProxy',
1582+
fields=[
1583+
],
1584+
options={
1585+
'proxy': True,
1586+
'indexes': [],
1587+
'constraints': [],
1588+
},
1589+
bases=('tests.tradproxychild',),
1590+
),
15131591
migrations.CreateModel(
15141592
name="ProxyModelBase",
15151593
fields=[],

polymorphic/tests/models.py

+54-2
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ class UUIDPlainC(UUIDPlainB):
332332
field3 = models.CharField(max_length=10)
333333

334334

335-
# base -> proxy
335+
# base(poly) -> proxy
336336

337337

338338
class ProxyBase(PolymorphicModel):
@@ -348,7 +348,59 @@ class NonProxyChild(ProxyBase):
348348
name = models.CharField(max_length=10)
349349

350350

351-
# base -> proxy -> real models
351+
# A traditional django proxy models. ie proxy'ed class is alias class
352+
# but in django_polymorphic this is not so.
353+
#
354+
# We have model types :-
355+
# base(poly) / child(poly) : A concrete polymorphic model 1+ fields
356+
# base(non ploy) : A concrete django model 1+ fields
357+
# proxy(poly) : A proxy model where it considered different
358+
# : from it superclasses
359+
# proxy(Traditional Django) : A proxy model where it is an alias for the
360+
# : underline model
361+
362+
363+
# base(poly) -> proxy(poly) -> proxy(Traditional Django)
364+
class TradProxyOnProxyChild(ProxyChild):
365+
class Meta:
366+
proxy = True
367+
polymorphic__proxy = True
368+
369+
370+
# base(poly) -> proxy(Traditional Django)
371+
class TradProxyChild(ProxyBase):
372+
class Meta:
373+
proxy = True
374+
polymorphic__proxy = True
375+
376+
# base(poly) -> proxy(Traditional Django) -> proxy(poly)
377+
# Not really helpful model as reduces to base(poly) -> proxy(poly)
378+
379+
# base(poly) -> child(poly) -> proxy(Traditional Django)
380+
class AliasOfNonProxyChild(NonProxyChild):
381+
class Meta:
382+
proxy = True
383+
polymorphic__proxy = True
384+
385+
386+
# base(poly) -> proxy(Traditional Django) -> proxy(poly)
387+
class ProxyChildAliasProxy(TradProxyChild):
388+
class Meta:
389+
proxy = True
390+
391+
392+
# base(poly) -> proxy(poly)
393+
class AliasProxyChild(ProxyBase):
394+
class Meta:
395+
proxy = True
396+
polymorphic__proxy = True
397+
398+
399+
# base(poly) -> proxy(poly)
400+
class NonAliasNonProxyChild(NonProxyChild):
401+
class Meta:
402+
proxy = True
403+
polymorphic__proxy = False
352404

353405

354406
class ProxiedBase(ShowFieldTypeAndContent, PolymorphicModel):

polymorphic/tests/test_orm.py

+65-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from polymorphic.managers import PolymorphicManager
1212
from polymorphic.models import PolymorphicTypeInvalid, PolymorphicTypeUndefined
1313
from polymorphic.tests.models import (
14+
AliasProxyChild,
1415
ArtProject,
1516
Base,
1617
BlogA,
@@ -87,6 +88,13 @@
8788
UUIDPlainC,
8889
UUIDProject,
8990
UUIDResearchProject,
91+
92+
NonAliasNonProxyChild,
93+
TradProxyOnProxyChild,
94+
TradProxyChild,
95+
AliasOfNonProxyChild,
96+
ProxyChildAliasProxy,
97+
9098
)
9199

92100

@@ -875,6 +883,61 @@ def test_queryset_on_proxy_model_does_not_return_superclasses(self):
875883
self.assertEqual(5, ProxyBase.objects.count())
876884
self.assertEqual(3, ProxyChild.objects.count())
877885

886+
def test_queryset_on_polymorphic_proxy_model_returns_superclasses(self):
887+
ProxyBase.objects.create(some_data="Base1")
888+
AliasProxyChild.objects.create(some_data="ProxyChild1")
889+
AliasProxyChild.objects.create(some_data="ProxyChild2")
890+
ProxyChild.objects.create(some_data="PolyChild1")
891+
NonAliasNonProxyChild.objects.create(some_data="SubChild1")
892+
NonAliasNonProxyChild.objects.create(some_data="SubChild2")
893+
NonProxyChild.objects.create(some_data="NonProxChild1", name="t1")
894+
895+
with self.subTest(" superclasses"):
896+
self.assertEqual(7, ProxyBase.objects.count())
897+
self.assertEqual(7, AliasProxyChild.objects.count())
898+
with self.subTest("only compete classes"):
899+
# Non proxy models should not return the proxy siblings
900+
self.assertEqual(1, ProxyChild.objects.count())
901+
self.assertEqual(2, NonAliasNonProxyChild.objects.count())
902+
self.assertEqual(3, NonProxyChild.objects.count())
903+
904+
def test_polymorphic_proxy_object_has_different_ctype_from_base(self):
905+
obj1 = ProxyBase.objects.create(some_data="Base1")
906+
obj2 = AliasProxyChild.objects.create(some_data="ProxyChild1")
907+
obj1_ctype = ContentType.objects.get_for_model(
908+
obj1, for_concrete_model=False)
909+
obj2_ctype = ContentType.objects.get_for_model(
910+
obj2, for_concrete_model=False)
911+
self.assertNotEqual(obj1_ctype, obj2_ctype)
912+
913+
def test_can_create_django_style_proxy_classes_alias(self):
914+
ProxyBase.objects.create(some_data="Base1")
915+
TradProxyChild.objects.create(some_data="Base2")
916+
self.assertEqual(2, ProxyBase.objects.count())
917+
self.assertEqual(2, TradProxyChild.objects.count())
918+
TradProxyOnProxyChild.objects.create()
919+
920+
def test_convert_back_to_django_style_from_polymorphic(self):
921+
ProxyBase.objects.create(some_data="Base1")
922+
ProxyChild.objects.create(some_data="Base1")
923+
TradProxyOnProxyChild.objects.create(some_data="Base3")
924+
925+
self.assertEqual(3, ProxyBase.objects.count())
926+
self.assertEqual(2, ProxyChild.objects.count())
927+
self.assertEqual(3, TradProxyOnProxyChild.objects.count())
928+
929+
def test_convert_back_to_django_style_from_polymorphic_stops_at_concrete(self):
930+
ProxyBase.objects.create(some_data="Base1")
931+
NonProxyChild.objects.create(some_data="Base1")
932+
AliasOfNonProxyChild.objects.create(some_data="Base1")
933+
934+
self.assertEqual(3, ProxyBase.objects.count())
935+
self.assertEqual(2, NonProxyChild.objects.count())
936+
self.assertEqual(2, AliasOfNonProxyChild.objects.count())
937+
938+
def test_revert_back_to_polymorphic_proxy(self):
939+
self.assertFalse(ProxyChildAliasProxy._meta.polymorphic__proxy)
940+
878941
def test_proxy_get_real_instance_class(self):
879942
"""
880943
The call to ``get_real_instance()`` also checks whether the returned model is of the correct type.
@@ -884,12 +947,12 @@ def test_proxy_get_real_instance_class(self):
884947
name = "Item1"
885948
nonproxychild = NonProxyChild.objects.create(name=name)
886949

887-
pb = ProxyBase.objects.get(id=1)
950+
pb = ProxyBase.objects.get(id=nonproxychild.pk)
888951
self.assertEqual(pb.get_real_instance_class(), NonProxyChild)
889952
self.assertEqual(pb.get_real_instance(), nonproxychild)
890953
self.assertEqual(pb.name, name)
891954

892-
pbm = NonProxyChild.objects.get(id=1)
955+
pbm = NonProxyChild.objects.get(id=nonproxychild.pk)
893956
self.assertEqual(pbm.get_real_instance_class(), NonProxyChild)
894957
self.assertEqual(pbm.get_real_instance(), nonproxychild)
895958
self.assertEqual(pbm.name, name)

0 commit comments

Comments
 (0)