Browse Source

Fixed #34714 -- Added aget_object_or_404()/aget_list_or_404() shortcuts.

Olivier Tabone 1 year ago
parent
commit
b9473cac65
4 changed files with 116 additions and 6 deletions
  1. 34 0
      django/shortcuts.py
  2. 4 0
      docs/releases/5.0.txt
  3. 20 6
      docs/topics/http/shortcuts.txt
  4. 58 0
      tests/async/test_async_shortcuts.py

+ 34 - 0
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.

+ 4 - 0
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
 ~~~~~~~~~~
 

+ 20 - 6
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.

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