Skip to content

Commit a2348c8

Browse files
authored
Fixed #37139 -- Fixed inlines crash on parent models with db_default on primary key.
1 parent 8c2d3dc commit a2348c8

7 files changed

Lines changed: 61 additions & 5 deletions

File tree

django/db/models/base.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -709,9 +709,17 @@ def _set_pk_val(self, value):
709709

710710
def _is_pk_set(self, meta=None):
711711
pk_val = self._get_pk_val(meta)
712+
713+
def _is_set(value):
714+
return (
715+
value is None
716+
# Empty value when db_default is used.
717+
or isinstance(value, DatabaseDefault)
718+
)
719+
712720
return not (
713-
pk_val is None
714-
or (isinstance(pk_val, tuple) and any(f is None for f in pk_val))
721+
_is_set(pk_val)
722+
or (isinstance(pk_val, tuple) and any(_is_set(f) for f in pk_val))
715723
)
716724

717725
def get_deferred_fields(self):

tests/basic/tests.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,7 @@ def test_is_pk_unset(self):
554554
cases = [
555555
Article(),
556556
Article(id=None),
557+
PrimaryKeyWithDbDefault(),
557558
]
558559
for case in cases:
559560
with self.subTest(case=case):

tests/composite_pk/models/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
from .tenant import Comment, Post, Tenant, TimeStamped, Token, User
1+
from .tenant import Comment, Post, PostDbDefault, Tenant, TimeStamped, Token, User
22

33
__all__ = [
44
"Comment",
55
"Post",
6+
"PostDbDefault",
67
"Tenant",
78
"TimeStamped",
89
"Token",

tests/composite_pk/models/tenant.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ class Post(models.Model):
5555
id = models.UUIDField(default=uuid.uuid4)
5656

5757

58+
class PostDbDefault(models.Model):
59+
pk = models.CompositePrimaryKey("tenant_id", "id")
60+
tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE, default=1)
61+
id = models.IntegerField(db_default=1)
62+
63+
5864
class TimeStamped(models.Model):
5965
pk = models.CompositePrimaryKey("id", "created")
6066
id = models.SmallIntegerField(unique=True)

tests/composite_pk/tests.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from django.forms import modelform_factory
1818
from django.test import TestCase
1919

20-
from .models import Comment, Post, Tenant, TimeStamped, Token, User
20+
from .models import Comment, Post, PostDbDefault, Tenant, TimeStamped, Token, User
2121

2222

2323
class CommentForm(forms.ModelForm):
@@ -64,6 +64,12 @@ def test_pk_updated_if_field_updated(self):
6464
self.assertIsNone(user.id)
6565
self.assertIs(user._is_pk_set(), False)
6666

67+
def test_pk_not_set_db_default(self):
68+
post = PostDbDefault(tenant=self.tenant)
69+
self.assertEqual(post.tenant_id, self.tenant.pk)
70+
self.assertIsNotNone(post.id)
71+
self.assertIs(post._is_pk_set(), False)
72+
6773
def test_hash(self):
6874
self.assertEqual(hash(User(pk=(1, 2))), hash((1, 2)))
6975
self.assertEqual(hash(User(tenant_id=2, id=3)), hash((2, 3)))

tests/inline_formsets/models.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from django.db import models
2+
from django.db.models.functions import UUID4
23

34

45
class School(models.Model):
@@ -21,6 +22,27 @@ class Meta:
2122
]
2223

2324

25+
class ParentUUIDPk(models.Model):
26+
uuid = models.UUIDField(primary_key=True, db_default=UUID4(), editable=False)
27+
28+
class Meta:
29+
required_db_features = {
30+
"supports_uuid4_function",
31+
"supports_expression_defaults",
32+
}
33+
34+
35+
class ChildUUIDPk(models.Model):
36+
parent = models.ForeignKey(ParentUUIDPk, models.CASCADE)
37+
name = models.CharField(max_length=100)
38+
39+
class Meta:
40+
required_db_features = {
41+
"supports_uuid4_function",
42+
"supports_expression_defaults",
43+
}
44+
45+
2446
class Poet(models.Model):
2547
name = models.CharField(max_length=100)
2648

tests/inline_formsets/tests.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.forms.models import ModelForm, inlineformset_factory
22
from django.test import TestCase, skipUnlessDBFeature
33

4-
from .models import Child, Parent, Poem, Poet, School
4+
from .models import Child, ChildUUIDPk, Parent, ParentUUIDPk, Poem, Poet, School
55

66

77
class DeletionTests(TestCase):
@@ -113,6 +113,18 @@ def test_save_new(self):
113113
obj.save()
114114
self.assertEqual(school.child_set.count(), 1)
115115

116+
@skipUnlessDBFeature("supports_uuid4_function", "supports_expression_defaults")
117+
def test_add_form_uuid_pk(self):
118+
ChildFormSet = inlineformset_factory(ParentUUIDPk, ChildUUIDPk, fields=["name"])
119+
data = {
120+
"child_set-TOTAL_FORMS": "1",
121+
"child_set-INITIAL_FORMS": "1",
122+
"child_set-MAX_NUM_FORMS": "0",
123+
"child_set-0-name": "child",
124+
}
125+
formset = ChildFormSet(data)
126+
self.assertIs(formset.is_valid(), False)
127+
116128

117129
class InlineFormsetFactoryTest(TestCase):
118130
def test_inline_formset_factory(self):

0 commit comments

Comments
 (0)