瀏覽代碼

Fixed #34112 -- Added async-compatible interface to Model methods.

Thanks Adam Johnson for the review.
DevilsAutumn 2 年之前
父節點
當前提交
d5bcdf858d
共有 6 個文件被更改,包括 86 次插入0 次删除
  1. 1 0
      AUTHORS
  2. 25 0
      django/db/models/base.py
  3. 21 0
      docs/ref/models/instances.txt
  4. 4 0
      docs/releases/4.2.txt
  5. 10 0
      docs/topics/async.txt
  6. 25 0
      tests/async/test_async_model_methods.py

+ 1 - 0
AUTHORS

@@ -141,6 +141,7 @@ answer newbie questions, and generally made Django that much better:
     Bernd Schlapsi
     Bernhard Essl <me@bernhardessl.com>
     berto
+    Bhuvnesh Sharma <bhuvnesh875@gmail.com>
     Bill Fenner <fenner@gmail.com>
     Bjørn Stabell <bjorn@exoweb.net>
     Bo Marchman <bo.marchman@gmail.com>

+ 25 - 0
django/db/models/base.py

@@ -4,6 +4,8 @@ import warnings
 from functools import partialmethod
 from itertools import chain
 
+from asgiref.sync import sync_to_async
+
 import django
 from django.apps import apps
 from django.conf import settings
@@ -737,6 +739,9 @@ class Model(metaclass=ModelBase):
 
         self._state.db = db_instance._state.db
 
+    async def arefresh_from_db(self, using=None, fields=None):
+        return await sync_to_async(self.refresh_from_db)(using=using, fields=fields)
+
     def serializable_value(self, field_name):
         """
         Return the value of the field name for this instance. If the field is
@@ -810,6 +815,18 @@ class Model(metaclass=ModelBase):
 
     save.alters_data = True
 
+    async def asave(
+        self, force_insert=False, force_update=False, using=None, update_fields=None
+    ):
+        return await sync_to_async(self.save)(
+            force_insert=force_insert,
+            force_update=force_update,
+            using=using,
+            update_fields=update_fields,
+        )
+
+    asave.alters_data = True
+
     def save_base(
         self,
         raw=False,
@@ -1111,6 +1128,14 @@ class Model(metaclass=ModelBase):
 
     delete.alters_data = True
 
+    async def adelete(self, using=None, keep_parents=False):
+        return await sync_to_async(self.delete)(
+            using=using,
+            keep_parents=keep_parents,
+        )
+
+    adelete.alters_data = True
+
     def _get_FIELD_display(self, field):
         value = getattr(self, field.attname)
         choices_dict = dict(make_hashable(field.flatchoices))

+ 21 - 0
docs/ref/models/instances.txt

@@ -132,6 +132,9 @@ value from the database::
     >>> obj.field  # Loads the field from the database
 
 .. method:: Model.refresh_from_db(using=None, fields=None)
+.. method:: Model.arefresh_from_db(using=None, fields=None)
+
+*Asynchronous version*: ``arefresh_from_db()``
 
 If you need to reload a model's values from the database, you can use the
 ``refresh_from_db()`` method. When this method is called without arguments the
@@ -188,6 +191,10 @@ all of the instance's fields when a deferred field is reloaded::
 A helper method that returns a set containing the attribute names of all those
 fields that are currently deferred for this model.
 
+.. versionchanged:: 4.2
+
+    ``arefresh_from_db()`` method was added.
+
 .. _validating-objects:
 
 Validating objects
@@ -406,6 +413,9 @@ Saving objects
 To save an object back to the database, call ``save()``:
 
 .. method:: Model.save(force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None)
+.. method:: Model.asave(force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None)
+
+*Asynchronous version*: ``asave()``
 
 For details on using the ``force_insert`` and ``force_update`` arguments, see
 :ref:`ref-models-force-insert`. Details about the ``update_fields`` argument
@@ -416,6 +426,10 @@ method. See :ref:`overriding-model-methods` for more details.
 
 The model save process also has some subtleties; see the sections below.
 
+.. versionchanged:: 4.2
+
+    ``asave()`` method was added.
+
 Auto-incrementing primary keys
 ------------------------------
 
@@ -644,6 +658,9 @@ Deleting objects
 ================
 
 .. method:: Model.delete(using=DEFAULT_DB_ALIAS, keep_parents=False)
+.. method:: Model.adelete(using=DEFAULT_DB_ALIAS, keep_parents=False)
+
+*Asynchronous version*: ``adelete()``
 
 Issues an SQL ``DELETE`` for the object. This only deletes the object in the
 database; the Python instance will still exist and will still have data in
@@ -660,6 +677,10 @@ Sometimes with :ref:`multi-table inheritance <multi-table-inheritance>` you may
 want to delete only a child model's data. Specifying ``keep_parents=True`` will
 keep the parent model's data.
 
+.. versionchanged:: 4.2
+
+    ``adelete()`` method was added.
+
 Pickling objects
 ================
 

+ 4 - 0
docs/releases/4.2.txt

@@ -239,6 +239,10 @@ Models
 * :class:`F() <django.db.models.F>` expressions that output ``BooleanField``
   can now be negated using ``~F()`` (inversion operator).
 
+* ``Model`` now provides asynchronous versions of some methods that use the
+  database, using an ``a`` prefix: :meth:`~.Model.adelete`,
+  :meth:`~.Model.arefresh_from_db`, and :meth:`~.Model.asave`.
+
 Requests and Responses
 ~~~~~~~~~~~~~~~~~~~~~~
 

+ 10 - 0
docs/topics/async.txt

@@ -91,10 +91,20 @@ Detailed notes can be found in :ref:`async-queries`, but in short:
 * ``async for`` is supported on all QuerySets (including the output of
   ``values()`` and ``values_list()``.)
 
+Django also supports some asynchronous model methods that use the database::
+
+    async def make_book(...):
+        book = Book(...)
+        await book.asave(using="secondary")
+
 Transactions do not yet work in async mode. If you have a piece of code that
 needs transactions behavior, we recommend you write that piece as a single
 synchronous function and call it using :func:`sync_to_async`.
 
+.. versionchanged:: 4.2
+
+    Asynchronous model interface was added.
+
 Performance
 -----------
 

+ 25 - 0
tests/async/test_async_model_methods.py

@@ -0,0 +1,25 @@
+from django.test import TestCase
+
+from .models import SimpleModel
+
+
+class AsyncModelOperationTest(TestCase):
+    @classmethod
+    def setUpTestData(cls):
+        cls.s1 = SimpleModel.objects.create(field=0)
+
+    async def test_asave(self):
+        self.s1.field = 10
+        await self.s1.asave()
+        refetched = await SimpleModel.objects.aget()
+        self.assertEqual(refetched.field, 10)
+
+    async def test_adelete(self):
+        await self.s1.adelete()
+        count = await SimpleModel.objects.acount()
+        self.assertEqual(count, 0)
+
+    async def test_arefresh_from_db(self):
+        await SimpleModel.objects.filter(pk=self.s1.pk).aupdate(field=20)
+        await self.s1.arefresh_from_db()
+        self.assertEqual(self.s1.field, 20)