Skip to content

Commit

Permalink
Fixed #26638 -- Allowed callable arguments for QuerySet.get_or_create…
Browse files Browse the repository at this point in the history
…()/update_or_create() defaults.
  • Loading branch information
ddsnowboard authored and timgraham committed Jun 3, 2016
1 parent 44c7e5d commit 9899347
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 8 deletions.
1 change: 1 addition & 0 deletions django/db/models/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ def _create_object_from_params(self, lookup, params):
"""
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:
Expand Down
22 changes: 15 additions & 7 deletions docs/ref/models/querysets.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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::
Expand Down Expand Up @@ -1764,14 +1764,18 @@ 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()``
~~~~~~~~~~~~~~~~~~~~~~

.. method:: update_or_create(defaults=None, **kwargs)

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
Expand Down Expand Up @@ -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()``
~~~~~~~~~~~~~~~~~

Expand Down
5 changes: 4 additions & 1 deletion docs/releases/1.11.txt
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,10 @@ Migrations
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
~~~~~~~~~~~~~~~~~~~~~~
Expand Down
26 changes: 26 additions & 0 deletions tests/get_or_create/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,25 @@ def test_defaults_exact(self):
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):

Expand Down Expand Up @@ -387,3 +406,10 @@ def test_defaults_exact(self):
)
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))

0 comments on commit 9899347

Please sign in to comment.