1
0
mirror of https://github.com/django/django.git synced 2024-12-23 01:25:58 +00:00

Fixed #26638 -- Allowed callable arguments for QuerySet.get_or_create()/update_or_create() defaults.

This commit is contained in:
Will Koster 2016-05-25 15:33:35 -05:00 committed by Tim Graham
parent 44c7e5d374
commit 9899347641
4 changed files with 46 additions and 8 deletions

View File

@ -500,6 +500,7 @@ class QuerySet(object):
""" """
try: try:
with transaction.atomic(using=self.db): with transaction.atomic(using=self.db):
params = {k: v() if callable(v) else v for k, v in params.items()}
obj = self.create(**params) obj = self.create(**params)
return obj, True return obj, True
except IntegrityError: except IntegrityError:

View File

@ -1687,18 +1687,18 @@ tuple of the new object and ``True``. The new object will be created roughly
according to this algorithm:: according to this algorithm::
params = {k: v for k, v in kwargs.items() if '__' not in k} 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 = self.model(**params)
obj.save() obj.save()
In English, that means start with any non-``'defaults'`` keyword argument that 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). 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 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 use the result as the keyword arguments to the model class. If there are any
above, this is a simplification of the algorithm that is used, but it contains callables in ``defaults``, evaluate them. As hinted at above, this is a
all the pertinent details. The internal implementation has some more simplification of the algorithm that is used, but it contains all the pertinent
error-checking than this and handles some extra edge-conditions; if you're details. The internal implementation has some more error-checking than this and
interested, read the code. 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 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:: ``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 chapter because it isn't related to that book, but it can't create it either
because ``title`` field should be unique. because ``title`` field should be unique.
.. versionchanged:: 1.11
Added support for callable values in ``defaults``.
``update_or_create()`` ``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 convenience method for updating an object with the given ``kwargs``, creating
a new one if necessary. The ``defaults`` is a dictionary of (field, value) 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 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 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 race-condition which can result in multiple rows being inserted simultaneously
if uniqueness is not enforced at the database level. if uniqueness is not enforced at the database level.
.. versionchanged:: 1.11
Added support for callable values in ``defaults``.
``bulk_create()`` ``bulk_create()``
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~

View File

@ -175,7 +175,10 @@ Migrations
Models Models
~~~~~~ ~~~~~~
* ... * Added support for callable values in the ``defaults`` argument of
:meth:`QuerySet.update_or_create()
<django.db.models.query.QuerySet.update_or_create>` and
:meth:`~django.db.models.query.QuerySet.get_or_create`.
Requests and Responses Requests and Responses
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~

View File

@ -146,6 +146,25 @@ class GetOrCreateTests(TestCase):
self.assertFalse(created) self.assertFalse(created)
self.assertEqual(obj, obj2) 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): class GetOrCreateTestsWithManualPKs(TestCase):
@ -387,3 +406,10 @@ class UpdateOrCreateTests(TestCase):
) )
self.assertFalse(created) self.assertFalse(created)
self.assertEqual(obj.defaults, 'another testing') 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))