فهرست منبع

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

Will Koster 8 سال پیش
والد
کامیت
9899347641
4فایلهای تغییر یافته به همراه46 افزوده شده و 8 حذف شده
  1. 1 0
      django/db/models/query.py
  2. 15 7
      docs/ref/models/querysets.txt
  3. 4 1
      docs/releases/1.11.txt
  4. 26 0
      tests/get_or_create/tests.py

+ 1 - 0
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:

+ 15 - 7
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()``
 ~~~~~~~~~~~~~~~~~
 

+ 4 - 1
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()
+  <django.db.models.query.QuerySet.update_or_create>` and
+  :meth:`~django.db.models.query.QuerySet.get_or_create`.
 
 Requests and Responses
 ~~~~~~~~~~~~~~~~~~~~~~

+ 26 - 0
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))