Skip to content

Commit 447e8c2

Browse files
authored
Merge pull request #620 from sezabart/sezabart-patch-1
Resolve primary key name correctly.
2 parents feea643 + c6d7c92 commit 447e8c2

File tree

5 files changed

+167
-5
lines changed

5 files changed

+167
-5
lines changed

docs/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Changelog
44
v4.3.0 (202X-XX-XX)
55
-------------------
66

7+
* Fixed `Resolve primary key name correctly. <https://github.com/jazzband/django-polymorphic/pull/620>`_
78
* Implemented `Include get_child_inlines() hook in stacked inline admin forms. <https://github.com/jazzband/django-polymorphic/pull/681>`_
89
* Fixed `multi-database support in inheritance accessors. <https://github.com/jazzband/django-polymorphic/pull/550>`_
910
* Fixed `Caching in inheritance accessor functions <https://github.com/jazzband/django-polymorphic/pull/510>`_

src/polymorphic/base.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,8 @@ def __new__(self, model_name, bases, attrs, **kwargs):
7979

8080
# determine the name of the primary key field and store it into the class variable
8181
# polymorphic_primary_key_name (it is needed by query.py)
82-
for f in new_class._meta.fields:
83-
if f.primary_key and type(f) is not models.OneToOneField:
84-
new_class.polymorphic_primary_key_name = f.name
85-
break
82+
if new_class._meta.pk:
83+
new_class.polymorphic_primary_key_name = new_class._meta.pk.name
8684

8785
return new_class
8886

src/polymorphic/tests/migrations/0001_initial.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
# Generated by Django 4.2.26 on 2025-12-05 02:34
1+
# Generated by Django 4.2.27 on 2025-12-08 15:20
22

3+
from django.conf import settings
34
from django.db import migrations, models
45
import django.db.models.deletion
56
import django.db.models.manager
@@ -17,6 +18,17 @@ class Migration(migrations.Migration):
1718
]
1819

1920
operations = [
21+
migrations.CreateModel(
22+
name='Account',
23+
fields=[
24+
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='account', serialize=False, to=settings.AUTH_USER_MODEL)),
25+
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
26+
],
27+
options={
28+
'abstract': False,
29+
'base_manager_name': 'objects',
30+
},
31+
),
2032
migrations.CreateModel(
2133
name='Base',
2234
fields=[
@@ -599,6 +611,30 @@ class Migration(migrations.Migration):
599611
},
600612
bases=('tests.relationbase',),
601613
),
614+
migrations.CreateModel(
615+
name='SpecialAccount1',
616+
fields=[
617+
('account_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.account')),
618+
('extra1', models.IntegerField(blank=True, default=None, null=True)),
619+
],
620+
options={
621+
'abstract': False,
622+
'base_manager_name': 'objects',
623+
},
624+
bases=('tests.account',),
625+
),
626+
migrations.CreateModel(
627+
name='SpecialAccount2',
628+
fields=[
629+
('account_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.account')),
630+
('extra1', models.CharField(blank=True, default='', max_length=30)),
631+
],
632+
options={
633+
'abstract': False,
634+
'base_manager_name': 'objects',
635+
},
636+
bases=('tests.account',),
637+
),
602638
migrations.CreateModel(
603639
name='SubclassSelectorAbstractConcreteModel',
604640
fields=[
@@ -1060,6 +1096,18 @@ class Migration(migrations.Migration):
10601096
},
10611097
bases=('tests.relationb',),
10621098
),
1099+
migrations.CreateModel(
1100+
name='SpecialAccount1_1',
1101+
fields=[
1102+
('specialaccount1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.specialaccount1')),
1103+
('extra2', models.IntegerField(blank=True, default=None, null=True)),
1104+
],
1105+
options={
1106+
'abstract': False,
1107+
'base_manager_name': 'objects',
1108+
},
1109+
bases=('tests.specialaccount1',),
1110+
),
10631111
migrations.CreateModel(
10641112
name='SubclassSelectorProxyConcreteModel',
10651113
fields=[

src/polymorphic/tests/models.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import django
44
from django.contrib.auth.models import Group
5+
from django.contrib.auth import get_user_model
56
from django.contrib.contenttypes.models import ContentType
67
from django.db import models
78
from django.db.models.query import QuerySet
@@ -565,3 +566,21 @@ class Meta:
565566
class PurpleHeadDuck(HomeDuck, BlueHeadDuck):
566567
class Meta:
567568
proxy = True
569+
570+
571+
class Account(PolymorphicModel):
572+
user = models.OneToOneField(
573+
get_user_model(), primary_key=True, on_delete=models.CASCADE, related_name="account"
574+
)
575+
576+
577+
class SpecialAccount1(Account):
578+
extra1 = models.IntegerField(null=True, default=None, blank=True)
579+
580+
581+
class SpecialAccount1_1(SpecialAccount1):
582+
extra2 = models.IntegerField(null=True, default=None, blank=True)
583+
584+
585+
class SpecialAccount2(Account):
586+
extra1 = models.CharField(default="", blank=True, max_length=30)

src/polymorphic/tests/test_orm.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import re
33
import uuid
44

5+
from django.contrib.auth import get_user_model
56
from django.contrib.contenttypes.models import ContentType
67
from django.db import models, connection
78
from django.db.models import Case, Count, FilteredRelation, Q, Sum, When, Exists, OuterRef
@@ -94,6 +95,10 @@
9495
UUIDResearchProject,
9596
Duck,
9697
PurpleHeadDuck,
98+
Account,
99+
SpecialAccount1,
100+
SpecialAccount1_1,
101+
SpecialAccount2,
97102
)
98103

99104

@@ -1521,3 +1526,94 @@ def test_subqueries(self):
15211526
InlineParent.objects.all().delete()
15221527
InlineModelA.objects.all().delete()
15231528
InlineModelB.objects.all().delete()
1529+
1530+
def test_one_to_one_primary_key(self):
1531+
# check pk name resolution
1532+
for mdl in [Account, SpecialAccount1, SpecialAccount1_1, SpecialAccount2]:
1533+
assert mdl.polymorphic_primary_key_name == mdl._meta.pk.name
1534+
1535+
user1 = get_user_model().objects.create(
1536+
username="user1", email="[email protected]", password="password"
1537+
)
1538+
user2 = get_user_model().objects.create(
1539+
username="user2", email="[email protected]", password="password"
1540+
)
1541+
user3 = get_user_model().objects.create(
1542+
username="user3", email="[email protected]", password="password"
1543+
)
1544+
user4 = get_user_model().objects.create(
1545+
username="user4", email="[email protected]", password="password"
1546+
)
1547+
1548+
user1_profile = SpecialAccount1_1.objects.create(user=user1, extra1=5, extra2=6)
1549+
1550+
user2_profile = SpecialAccount1.objects.create(user=user2, extra1=5)
1551+
1552+
user3_profile = SpecialAccount2.objects.create(user=user3, extra1="test")
1553+
1554+
user4_profile = SpecialAccount1_1.objects.create(user=user4, extra1=7, extra2=8)
1555+
1556+
user1.refresh_from_db()
1557+
assert user1.account.__class__ is SpecialAccount1_1
1558+
assert user1.account.extra1 == 5
1559+
assert user1.account.extra2 == 6
1560+
assert user1_profile.pk == user1.account.pk
1561+
1562+
user2.refresh_from_db()
1563+
assert user2.account.__class__ is SpecialAccount1
1564+
assert user2.account.extra1 == 5
1565+
assert user2_profile.pk == user2.account.pk
1566+
assert not hasattr(user2.account, "extra2")
1567+
1568+
user3.refresh_from_db()
1569+
assert user3.account.__class__ is SpecialAccount2
1570+
assert user3.account.extra1 == "test"
1571+
assert user3_profile.pk == user3.account.pk
1572+
assert not hasattr(user3.account, "extra2")
1573+
1574+
user4.refresh_from_db()
1575+
assert user4.account.__class__ is SpecialAccount1_1
1576+
assert user4.account.extra1 == 7
1577+
assert user4.account.extra2 == 8
1578+
assert user4_profile.pk == user4.account.pk
1579+
1580+
assert get_user_model().objects.filter(pk=user2.pk).delete() == (
1581+
3,
1582+
{"tests.SpecialAccount1": 1, "tests.Account": 1, "auth.User": 1},
1583+
)
1584+
1585+
assert SpecialAccount1.objects.count() == 2
1586+
assert Account.objects.count() == 3
1587+
1588+
remaining = get_user_model().objects.filter(
1589+
pk__in=[user1.pk, user2.pk, user3.pk, user4.pk]
1590+
)
1591+
assert remaining.count() == 3
1592+
for usr, expected in zip(
1593+
remaining.order_by("pk"), (user1_profile, user3_profile, user4_profile)
1594+
):
1595+
assert usr.account == expected
1596+
1597+
assert get_user_model().objects.filter(pk__in=[user3.pk]).delete() == (
1598+
3,
1599+
{"tests.SpecialAccount2": 1, "tests.Account": 1, "auth.User": 1},
1600+
)
1601+
1602+
assert Account.objects.count() == 2
1603+
1604+
assert SpecialAccount1_1.objects.all().delete() == (
1605+
6,
1606+
{"tests.SpecialAccount1_1": 2, "tests.SpecialAccount1": 2, "tests.Account": 2},
1607+
)
1608+
1609+
assert Account.objects.count() == 0
1610+
1611+
remaining = get_user_model().objects.filter(pk__gte=user1.pk)
1612+
assert remaining.count() == 2
1613+
for usr in remaining:
1614+
assert not hasattr(usr, "account")
1615+
1616+
assert get_user_model().objects.filter(pk__in=[user1.pk, user4.pk]).delete() == (
1617+
2,
1618+
{"auth.User": 2},
1619+
)

0 commit comments

Comments
 (0)