summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOlivier Tabone <olivier.tabone@ripplemotion.fr>2023-07-20 17:50:06 +0200
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-07-24 07:37:54 +0200
commitb9473cac65190822e7c94f695f1f7b4d5b49502a (patch)
tree67c6bd94f6ea36dc842f6311a3f3fdac515514e1
parentf05cc5e3d2ae7663dbd248029bcb74500cf1029f (diff)
Fixed #34714 -- Added aget_object_or_404()/aget_list_or_404() shortcuts.
-rw-r--r--django/shortcuts.py34
-rw-r--r--docs/releases/5.0.txt4
-rw-r--r--docs/topics/http/shortcuts.txt26
-rw-r--r--tests/async/test_async_shortcuts.py58
4 files changed, 116 insertions, 6 deletions
diff --git a/django/shortcuts.py b/django/shortcuts.py
index 90ec1bedc5..822e6107ac 100644
--- a/django/shortcuts.py
+++ b/django/shortcuts.py
@@ -89,6 +89,23 @@ def get_object_or_404(klass, *args, **kwargs):
)
+async def aget_object_or_404(klass, *args, **kwargs):
+ """See get_object_or_404()."""
+ queryset = _get_queryset(klass)
+ if not hasattr(queryset, "aget"):
+ klass__name = (
+ klass.__name__ if isinstance(klass, type) else klass.__class__.__name__
+ )
+ raise ValueError(
+ "First argument to aget_object_or_404() must be a Model, Manager, or "
+ f"QuerySet, not '{klass__name}'."
+ )
+ try:
+ return await queryset.aget(*args, **kwargs)
+ except queryset.model.DoesNotExist:
+ raise Http404(f"No {queryset.model._meta.object_name} matches the given query.")
+
+
def get_list_or_404(klass, *args, **kwargs):
"""
Use filter() to return a list of objects, or raise an Http404 exception if
@@ -114,6 +131,23 @@ def get_list_or_404(klass, *args, **kwargs):
return obj_list
+async def aget_list_or_404(klass, *args, **kwargs):
+ """See get_list_or_404()."""
+ queryset = _get_queryset(klass)
+ if not hasattr(queryset, "filter"):
+ klass__name = (
+ klass.__name__ if isinstance(klass, type) else klass.__class__.__name__
+ )
+ raise ValueError(
+ "First argument to aget_list_or_404() must be a Model, Manager, or "
+ f"QuerySet, not '{klass__name}'."
+ )
+ obj_list = [obj async for obj in queryset.filter(*args, **kwargs)]
+ if not obj_list:
+ raise Http404(f"No {queryset.model._meta.object_name} matches the given query.")
+ return obj_list
+
+
def resolve_url(to, *args, **kwargs):
"""
Return a URL appropriate for the arguments passed.
diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt
index 815fb062b4..e7fcc6e07f 100644
--- a/docs/releases/5.0.txt
+++ b/docs/releases/5.0.txt
@@ -364,6 +364,10 @@ Models
* The new :attr:`.UniqueConstraint.nulls_distinct` attribute allows customizing
the treatment of ``NULL`` values on PostgreSQL 15+.
+* The new :func:`~django.shortcuts.aget_object_or_404` and
+ :func:`~django.shortcuts.aget_list_or_404` asynchronous shortcuts allow
+ asynchronous getting objects.
+
Pagination
~~~~~~~~~~
diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt
index f3cbd151aa..3e4778f0f2 100644
--- a/docs/topics/http/shortcuts.txt
+++ b/docs/topics/http/shortcuts.txt
@@ -162,10 +162,13 @@ will be returned::
=======================
.. function:: get_object_or_404(klass, *args, **kwargs)
+.. function:: aget_object_or_404(klass, *args, **kwargs)
- Calls :meth:`~django.db.models.query.QuerySet.get()` on a given model manager,
- but it raises :class:`~django.http.Http404` instead of the model's
- :class:`~django.db.models.Model.DoesNotExist` exception.
+ *Asynchronous version*: ``aget_object_or_404()``
+
+ Calls :meth:`~django.db.models.query.QuerySet.get()` on a given model
+ manager, but it raises :class:`~django.http.Http404` instead of the model's
+ :class:`~django.db.models.Model.DoesNotExist` exception.
Arguments
---------
@@ -236,14 +239,21 @@ Note: As with ``get()``, a
:class:`~django.core.exceptions.MultipleObjectsReturned` exception
will be raised if more than one object is found.
+.. versionchanged:: 5.0
+
+ ``aget_object_or_404()`` function was added.
+
``get_list_or_404()``
=====================
.. function:: get_list_or_404(klass, *args, **kwargs)
+.. function:: aget_list_or_404(klass, *args, **kwargs)
- Returns the result of :meth:`~django.db.models.query.QuerySet.filter()` on a
- given model manager cast to a list, raising :class:`~django.http.Http404` if
- the resulting list is empty.
+ *Asynchronous version*: ``aget_list_or_404()``
+
+ Returns the result of :meth:`~django.db.models.query.QuerySet.filter()` on
+ a given model manager cast to a list, raising :class:`~django.http.Http404`
+ if the resulting list is empty.
Arguments
---------
@@ -280,3 +290,7 @@ This example is equivalent to::
my_objects = list(MyModel.objects.filter(published=True))
if not my_objects:
raise Http404("No MyModel matches the given query.")
+
+.. versionchanged:: 5.0
+
+ ``aget_list_or_404()`` function was added.
diff --git a/tests/async/test_async_shortcuts.py b/tests/async/test_async_shortcuts.py
new file mode 100644
index 0000000000..09c76873fa
--- /dev/null
+++ b/tests/async/test_async_shortcuts.py
@@ -0,0 +1,58 @@
+from django.db.models import Q
+from django.http import Http404
+from django.shortcuts import aget_list_or_404, aget_object_or_404
+from django.test import TestCase
+
+from .models import RelatedModel, SimpleModel
+
+
+class GetListObjectOr404Test(TestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.s1 = SimpleModel.objects.create(field=0)
+ cls.s2 = SimpleModel.objects.create(field=1)
+ cls.r1 = RelatedModel.objects.create(simple=cls.s1)
+
+ async def test_aget_object_or_404(self):
+ self.assertEqual(await aget_object_or_404(SimpleModel, field=1), self.s2)
+ self.assertEqual(await aget_object_or_404(SimpleModel, Q(field=0)), self.s1)
+ self.assertEqual(
+ await aget_object_or_404(SimpleModel.objects.all(), field=1), self.s2
+ )
+ self.assertEqual(
+ await aget_object_or_404(self.s1.relatedmodel_set, pk=self.r1.pk), self.r1
+ )
+ # Http404 is returned if the list is empty.
+ msg = "No SimpleModel matches the given query."
+ with self.assertRaisesMessage(Http404, msg):
+ await aget_object_or_404(SimpleModel, field=2)
+
+ async def test_get_list_or_404(self):
+ self.assertEqual(await aget_list_or_404(SimpleModel, field=1), [self.s2])
+ self.assertEqual(await aget_list_or_404(SimpleModel, Q(field=0)), [self.s1])
+ self.assertEqual(
+ await aget_list_or_404(SimpleModel.objects.all(), field=1), [self.s2]
+ )
+ self.assertEqual(
+ await aget_list_or_404(self.s1.relatedmodel_set, pk=self.r1.pk), [self.r1]
+ )
+ # Http404 is returned if the list is empty.
+ msg = "No SimpleModel matches the given query."
+ with self.assertRaisesMessage(Http404, msg):
+ await aget_list_or_404(SimpleModel, field=2)
+
+ async def test_get_object_or_404_bad_class(self):
+ msg = (
+ "First argument to aget_object_or_404() must be a Model, Manager, or "
+ "QuerySet, not 'str'."
+ )
+ with self.assertRaisesMessage(ValueError, msg):
+ await aget_object_or_404("SimpleModel", field=0)
+
+ async def test_get_list_or_404_bad_class(self):
+ msg = (
+ "First argument to aget_list_or_404() must be a Model, Manager, or "
+ "QuerySet, not 'list'."
+ )
+ with self.assertRaisesMessage(ValueError, msg):
+ await aget_list_or_404([SimpleModel], field=1)