| 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 | }}} |