| | 3 | {{{#!diff |
| | 4 | diff --git a/django/db/models/base.py b/django/db/models/base.py |
| | 5 | index a7a26b405c..87c19fd887 100644 |
| | 6 | --- a/django/db/models/base.py |
| | 7 | +++ b/django/db/models/base.py |
| | 8 | @@ -455,6 +455,7 @@ class ModelState: |
| | 9 | # explicit (non-auto) PKs. This impacts validation only; it has no effect |
| | 10 | # on the actual save. |
| | 11 | adding = True |
| | 12 | + is_set_composite_pk = False |
| | 13 | fields_cache = ModelStateFieldsCacheDescriptor() |
| | 14 | |
| | 15 | |
| | 16 | @@ -584,6 +585,8 @@ class Model(AltersData, metaclass=ModelBase): |
| | 17 | new = cls(*values) |
| | 18 | new._state.adding = False |
| | 19 | new._state.db = db |
| | 20 | + if new._meta.is_composite_pk: |
| | 21 | + new._state.is_set_composite_pk = True |
| | 22 | return new |
| | 23 | |
| | 24 | def __repr__(self): |
| | 25 | @@ -1104,10 +1107,15 @@ class Model(AltersData, metaclass=ModelBase): |
| | 26 | if f.name in update_fields or f.attname in update_fields |
| | 27 | ] |
| | 28 | |
| | 29 | - if not self._is_pk_set(meta): |
| | 30 | - pk_val = meta.pk.get_pk_value_on_save(self) |
| | 31 | - setattr(self, meta.pk.attname, pk_val) |
| | 32 | - pk_set = self._is_pk_set(meta) |
| | 33 | + if meta.is_composite_pk and not (force_update or update_fields): |
| | 34 | + pk_set = self._state.is_set_composite_pk |
| | 35 | + else: |
| | 36 | + pk_set = self._is_pk_set(meta) |
| | 37 | + if not pk_set: |
| | 38 | + pk_val = meta.pk.get_pk_value_on_save(self) |
| | 39 | + setattr(self, meta.pk.attname, pk_val) |
| | 40 | + pk_set = self._is_pk_set(meta) |
| | 41 | + |
| | 42 | if not pk_set and (force_update or update_fields): |
| | 43 | raise ValueError("Cannot force an update in save() with no primary key.") |
| | 44 | updated = False |
| | 45 | diff --git a/django/db/models/fields/composite.py b/django/db/models/fields/composite.py |
| | 46 | index 2b196f6d2a..3ab8295dbc 100644 |
| | 47 | --- a/django/db/models/fields/composite.py |
| | 48 | +++ b/django/db/models/fields/composite.py |
| | 49 | @@ -28,7 +28,8 @@ class CompositeAttribute: |
| | 50 | attnames = self.attnames |
| | 51 | length = len(attnames) |
| | 52 | |
| | 53 | - if values is None: |
| | 54 | + is_clear_cp = values is None |
| | 55 | + if is_clear_cp: |
| | 56 | values = (None,) * length |
| | 57 | |
| | 58 | if not isinstance(values, (list, tuple)): |
| | 59 | @@ -38,6 +39,7 @@ class CompositeAttribute: |
| | 60 | |
| | 61 | for attname, value in zip(attnames, values): |
| | 62 | setattr(instance, attname, value) |
| | 63 | + instance._state.is_set_composite_pk = is_clear_cp |
| | 64 | |
| | 65 | |
| | 66 | class CompositePrimaryKey(Field): |
| | 67 | diff --git a/tests/composite_pk/models/tenant.py b/tests/composite_pk/models/tenant.py |
| | 68 | index ac0b3d9715..1c1108c0d3 100644 |
| | 69 | --- a/tests/composite_pk/models/tenant.py |
| | 70 | +++ b/tests/composite_pk/models/tenant.py |
| | 71 | @@ -1,3 +1,5 @@ |
| | 72 | +import uuid |
| | 73 | + |
| | 74 | from django.db import models |
| | 75 | |
| | 76 | |
| | 77 | @@ -46,5 +48,5 @@ class Comment(models.Model): |
| | 78 | |
| | 79 | class Post(models.Model): |
| | 80 | pk = models.CompositePrimaryKey("tenant_id", "id") |
| | 81 | - tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE) |
| | 82 | - id = models.UUIDField() |
| | 83 | + tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE, default=1) |
| | 84 | + id = models.UUIDField(default=uuid.uuid4) |
| | 85 | diff --git a/tests/composite_pk/test_create.py b/tests/composite_pk/test_create.py |
| | 86 | index 7c9925b946..5120324f65 100644 |
| | 87 | --- a/tests/composite_pk/test_create.py |
| | 88 | +++ b/tests/composite_pk/test_create.py |
| | 89 | @@ -1,6 +1,6 @@ |
| | 90 | from django.test import TestCase |
| | 91 | |
| | 92 | -from .models import Tenant, User |
| | 93 | +from .models import Tenant, User, Post |
| | 94 | |
| | 95 | |
| | 96 | class CompositePKCreateTests(TestCase): |
| | 97 | @@ -14,6 +14,11 @@ class CompositePKCreateTests(TestCase): |
| | 98 | id=1, |
| | 99 | email="user0001@example.com", |
| | 100 | ) |
| | 101 | + cls.post = Post.objects.create() |
| | 102 | + |
| | 103 | + def test_save_default_pk_not_set(self): |
| | 104 | + with self.assertNumQueries(1): |
| | 105 | + Post().save() |
| | 106 | |
| | 107 | def test_create_user(self): |
| | 108 | test_cases = ( |
| | 109 | diff --git a/tests/composite_pk/test_filter.py b/tests/composite_pk/test_filter.py |
| | 110 | index 7e361c5925..9fa3e4edb1 100644 |
| | 111 | --- a/tests/composite_pk/test_filter.py |
| | 112 | +++ b/tests/composite_pk/test_filter.py |
| | 113 | @@ -37,6 +37,7 @@ class CompositePKFilterTests(TestCase): |
| | 114 | cls.comment_4 = Comment.objects.create(id=4, user=cls.user_3) |
| | 115 | cls.comment_5 = Comment.objects.create(id=5, user=cls.user_1) |
| | 116 | |
| | 117 | + |
| | 118 | def test_filter_and_count_user_by_pk(self): |
| | 119 | test_cases = ( |
| | 120 | ({"pk": self.user_1.pk}, 1), |
| | 121 | |
| | 122 | }}} |