From 43b0131fb5724cb92716eedfe35a6b2b4d84e1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Wed, 28 Jan 2015 13:40:48 +0200 Subject: [PATCH] [1.8.x] Fixed #23617 -- Added get_pk_value_on_save() The method is mainly intended for use with UUIDField. For UUIDField we want to call the field's default even when primary key value is explicitly set to None to match the behavior of AutoField. Thanks to Marc Tamlyn and Tim Graham for review. Backport of 8adc59038cdc6ce4f9170e4de2d716d940e136b3 from master --- django/db/models/base.py | 3 +++ django/db/models/fields/__init__.py | 11 +++++++++++ django/db/models/query.py | 7 +++++++ docs/ref/models/fields.txt | 9 +++++++++ docs/releases/1.8.txt | 3 +++ tests/model_fields/test_uuid.py | 17 +++++++++++++++++ 6 files changed, 50 insertions(+) diff --git a/django/db/models/base.py b/django/db/models/base.py index 945cd0154b..3d65cc3d9d 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -772,6 +772,9 @@ class Model(six.with_metaclass(ModelBase)): if f.name in update_fields or f.attname in update_fields] pk_val = self._get_pk_val(meta) + if pk_val is None: + pk_val = meta.pk.get_pk_value_on_save(self) + setattr(self, meta.pk.attname, pk_val) pk_set = pk_val is not None if not pk_set and (force_update or update_fields): raise ValueError("Cannot force an update in save() with no primary key.") diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index d5dfac733f..627af0c985 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -506,6 +506,17 @@ class Field(RegisterLookupMixin): return _load_field, (self.model._meta.app_label, self.model._meta.object_name, self.name) + def get_pk_value_on_save(self, instance): + """ + Hook to generate new PK values on save. This method is called when + saving instances with no primary key value set. If this method returns + something else than None, then the returned value is used when saving + the new instance. + """ + if self.default: + return self.get_default() + return None + def to_python(self, value): """ Converts the input value into the expected Python data type, raising diff --git a/django/db/models/query.py b/django/db/models/query.py index 28936949f0..2b7aab6ac4 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -345,6 +345,11 @@ class QuerySet(object): obj.save(force_insert=True, using=self.db) return obj + def _populate_pk_values(self, objs): + for obj in objs: + if obj.pk is None: + obj.pk = obj._meta.pk.get_pk_value_on_save(obj) + def bulk_create(self, objs, batch_size=None): """ Inserts each of the instances into the database. This does *not* call @@ -369,6 +374,8 @@ class QuerySet(object): self._for_write = True connection = connections[self.db] fields = self.model._meta.local_concrete_fields + objs = list(objs) + self._populate_pk_values(objs) with transaction.atomic(using=self.db, savepoint=False): if (connection.features.can_combine_inserts_with_and_without_auto_increment_pk and self.model._meta.has_auto_field): diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 8604137afe..f4fe52a737 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -220,6 +220,15 @@ Note that ``lambda``\s cannot be used for field options like ``default`` because they cannot be :ref:`serialized by migrations `. See that documentation for other caveats. +The default value is used when new model instances are created and a value +isn't provided for the field. When the field is a primary key, the default is +also used when the field is set to ``None``. + +.. versionchanged:: 1.8 + + The default wasn't used for ``None`` primary key values in previous + versions. + ``editable`` ------------ diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 2806c21580..ac1dd36374 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -525,6 +525,9 @@ Models * You can now get the set of deferred fields for a model using :meth:`Model.get_deferred_fields() `. +* Model field ``default``’s are now used when primary key field's are set to + ``None``. + Signals ^^^^^^^ diff --git a/tests/model_fields/test_uuid.py b/tests/model_fields/test_uuid.py index b5bc37a48d..13fe245be3 100644 --- a/tests/model_fields/test_uuid.py +++ b/tests/model_fields/test_uuid.py @@ -87,3 +87,20 @@ class TestAsPrimaryKey(TestCase): PrimaryKeyUUIDModel.objects.create() loaded = PrimaryKeyUUIDModel.objects.get() self.assertIsInstance(loaded.pk, uuid.UUID) + + def test_uuid_pk_on_save(self): + saved = PrimaryKeyUUIDModel.objects.create(id=None) + loaded = PrimaryKeyUUIDModel.objects.get() + self.assertIsNotNone(loaded.id, None) + self.assertEqual(loaded.id, saved.id) + + def test_uuid_pk_on_bulk_create(self): + u1 = PrimaryKeyUUIDModel() + u2 = PrimaryKeyUUIDModel(id=None) + PrimaryKeyUUIDModel.objects.bulk_create([u1, u2]) + # Check that the two objects were correctly created. + u1_found = PrimaryKeyUUIDModel.objects.filter(id=u1.id).exists() + u2_found = PrimaryKeyUUIDModel.objects.exclude(id=u1.id).exists() + self.assertTrue(u1_found) + self.assertTrue(u2_found) + self.assertEqual(PrimaryKeyUUIDModel.objects.count(), 2)