summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDevilsAutumn <bhuvnesh875@gmail.com>2022-10-30 22:21:54 +0530
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2022-11-02 09:14:17 +0100
commitd5bcdf858d962d02de925603c17986980f03729a (patch)
tree367182dbc54c46faa2196b7f047eceeda62ec650
parent6103059592c6c1bd171977d26b76ee475175043c (diff)
Fixed #34112 -- Added async-compatible interface to Model methods.
Thanks Adam Johnson for the review.
-rw-r--r--AUTHORS1
-rw-r--r--django/db/models/base.py25
-rw-r--r--docs/ref/models/instances.txt21
-rw-r--r--docs/releases/4.2.txt4
-rw-r--r--docs/topics/async.txt10
-rw-r--r--tests/async/test_async_model_methods.py25
6 files changed, 86 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
index 6359e50154..72e26ebc3e 100644
--- a/AUTHORS
+++ b/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>
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 584db319da..2eb7ba7e9b 100644
--- a/django/db/models/base.py
+++ b/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))
diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt
index f0306a7460..f9f11cac0d 100644
--- a/docs/ref/models/instances.txt
+++ b/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
================
diff --git a/docs/releases/4.2.txt b/docs/releases/4.2.txt
index 4f7bda7080..ba01bf12e5 100644
--- a/docs/releases/4.2.txt
+++ b/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
~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/topics/async.txt b/docs/topics/async.txt
index 9e2785c351..2b9b1a85d9 100644
--- a/docs/topics/async.txt
+++ b/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
-----------
diff --git a/tests/async/test_async_model_methods.py b/tests/async/test_async_model_methods.py
new file mode 100644
index 0000000000..94e0370e35
--- /dev/null
+++ b/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)