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

This commit is contained in:
Olivier Tabone 2023-07-20 17:50:06 +02:00 committed by Mariusz Felisiak
parent f05cc5e3d2
commit b9473cac65
4 changed files with 116 additions and 6 deletions

View File

@ -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.

View File

@ -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
~~~~~~~~~~

View File

@ -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.

View File

@ -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)