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)