From 9899347641b2d3b4457cc99203a2b06504b32a16 Mon Sep 17 00:00:00 2001 From: Will Koster Date: Wed, 25 May 2016 15:33:35 -0500 Subject: [PATCH] Fixed #26638 -- Allowed callable arguments for QuerySet.get_or_create()/update_or_create() defaults. --- django/db/models/query.py | 1 + docs/ref/models/querysets.txt | 22 +++++++++++++++------- docs/releases/1.11.txt | 5 ++++- tests/get_or_create/tests.py | 26 ++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 2f4ef52e6c..40f7ae6ea9 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -500,6 +500,7 @@ class QuerySet(object): """ try: with transaction.atomic(using=self.db): + params = {k: v() if callable(v) else v for k, v in params.items()} obj = self.create(**params) return obj, True except IntegrityError: diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 34ec26466c..0a1224dc1a 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1687,18 +1687,18 @@ tuple of the new object and ``True``. The new object will be created roughly according to this algorithm:: params = {k: v for k, v in kwargs.items() if '__' not in k} - params.update(defaults) + params.update(({k: v() if callable(v) else v for k, v in defaults.items()}) obj = self.model(**params) obj.save() In English, that means start with any non-``'defaults'`` keyword argument that doesn't contain a double underscore (which would indicate a non-exact lookup). Then add the contents of ``defaults``, overriding any keys if necessary, and -use the result as the keyword arguments to the model class. As hinted at -above, this is a simplification of the algorithm that is used, but it contains -all the pertinent details. The internal implementation has some more -error-checking than this and handles some extra edge-conditions; if you're -interested, read the code. +use the result as the keyword arguments to the model class. If there are any +callables in ``defaults``, evaluate them. As hinted at above, this is a +simplification of the algorithm that is used, but it contains all the pertinent +details. The internal implementation has some more error-checking than this and +handles some extra edge-conditions; if you're interested, read the code. If you have a field named ``defaults`` and want to use it as an exact lookup in ``get_or_create()``, just use ``'defaults__exact'``, like so:: @@ -1764,6 +1764,10 @@ whenever a request to a page has a side effect on your data. For more, see chapter because it isn't related to that book, but it can't create it either because ``title`` field should be unique. +.. versionchanged:: 1.11 + + Added support for callable values in ``defaults``. + ``update_or_create()`` ~~~~~~~~~~~~~~~~~~~~~~ @@ -1771,7 +1775,7 @@ whenever a request to a page has a side effect on your data. For more, see A convenience method for updating an object with the given ``kwargs``, creating a new one if necessary. The ``defaults`` is a dictionary of (field, value) -pairs used to update the object. +pairs used to update the object. The values in ``defaults`` can be callables. Returns a tuple of ``(object, created)``, where ``object`` is the created or updated object and ``created`` is a boolean specifying whether a new object was @@ -1806,6 +1810,10 @@ As described above in :meth:`get_or_create`, this method is prone to a race-condition which can result in multiple rows being inserted simultaneously if uniqueness is not enforced at the database level. +.. versionchanged:: 1.11 + + Added support for callable values in ``defaults``. + ``bulk_create()`` ~~~~~~~~~~~~~~~~~ diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 9c162b41c4..12c2eba7ff 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -175,7 +175,10 @@ Migrations Models ~~~~~~ -* ... +* Added support for callable values in the ``defaults`` argument of + :meth:`QuerySet.update_or_create() + ` and + :meth:`~django.db.models.query.QuerySet.get_or_create`. Requests and Responses ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/get_or_create/tests.py b/tests/get_or_create/tests.py index 0f790e67b2..84f8261a92 100644 --- a/tests/get_or_create/tests.py +++ b/tests/get_or_create/tests.py @@ -146,6 +146,25 @@ class GetOrCreateTests(TestCase): self.assertFalse(created) self.assertEqual(obj, obj2) + def test_callable_defaults(self): + """ + Callables in `defaults` are evaluated if the instance is created. + """ + obj, created = Person.objects.get_or_create( + first_name="George", + defaults={"last_name": "Harrison", "birthday": lambda: date(1943, 2, 25)}, + ) + self.assertTrue(created) + self.assertEqual(date(1943, 2, 25), obj.birthday) + + def test_callable_defaults_not_called(self): + def raise_exception(): + raise AssertionError + obj, created = Person.objects.get_or_create( + first_name="John", last_name="Lennon", + defaults={"birthday": lambda: raise_exception()}, + ) + class GetOrCreateTestsWithManualPKs(TestCase): @@ -387,3 +406,10 @@ class UpdateOrCreateTests(TestCase): ) self.assertFalse(created) self.assertEqual(obj.defaults, 'another testing') + + def test_update_callable_default(self): + obj, created = Person.objects.update_or_create( + first_name='George', last_name='Harrison', + defaults={'birthday': lambda: date(1943, 2, 25)}, + ) + self.assertEqual(obj.birthday, date(1943, 2, 25))