Skip to content

Commit 12ba20d

Browse files
committed
Fixed django#10532 -- Relaxed hard-type checking in get_object/list_or_404 shortcuts
Thanks Anssi Kääriäinen for the patch suggestion, and Tim Graham for the review.
1 parent 4b2cf1c commit 12ba20d

File tree

2 files changed

+25
-25
lines changed

2 files changed

+25
-25
lines changed

django/shortcuts.py

+22-22
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
of MVC. In other words, these functions/classes introduce controlled coupling
44
for convenience's sake.
55
"""
6-
from django.db.models.base import ModelBase
7-
from django.db.models.manager import Manager
8-
from django.db.models.query import QuerySet
96
from django.http import (
107
Http404, HttpResponse, HttpResponsePermanentRedirect, HttpResponseRedirect,
118
)
@@ -61,25 +58,15 @@ def redirect(to, *args, **kwargs):
6158

6259
def _get_queryset(klass):
6360
"""
64-
Returns a QuerySet from a Model, Manager, or QuerySet. Created to make
65-
get_object_or_404 and get_list_or_404 more DRY.
66-
67-
Raises a ValueError if klass is not a Model, Manager, or QuerySet.
61+
Return a QuerySet or a Manager.
62+
Duck typing in action: any class with a `get()` method (for
63+
get_object_or_404) or a `filter()` method (for get_list_or_404) might do
64+
the job.
6865
"""
69-
if isinstance(klass, QuerySet):
70-
return klass
71-
elif isinstance(klass, Manager):
72-
manager = klass
73-
elif isinstance(klass, ModelBase):
74-
manager = klass._default_manager
75-
else:
76-
if isinstance(klass, type):
77-
klass__name = klass.__name__
78-
else:
79-
klass__name = klass.__class__.__name__
80-
raise ValueError("Object is of type '%s', but must be a Django Model, "
81-
"Manager, or QuerySet" % klass__name)
82-
return manager.all()
66+
# If it is a model class or anything else with ._default_manager
67+
if hasattr(klass, '_default_manager'):
68+
return klass._default_manager.all()
69+
return klass
8370

8471

8572
def get_object_or_404(klass, *args, **kwargs):
@@ -96,6 +83,12 @@ def get_object_or_404(klass, *args, **kwargs):
9683
queryset = _get_queryset(klass)
9784
try:
9885
return queryset.get(*args, **kwargs)
86+
except AttributeError:
87+
klass__name = klass.__name__ if isinstance(klass, type) else klass.__class__.__name__
88+
raise ValueError(
89+
"First argument to get_object_or_404() must be a Model, Manager, "
90+
"or QuerySet, not '%s'." % klass__name
91+
)
9992
except queryset.model.DoesNotExist:
10093
raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
10194

@@ -109,7 +102,14 @@ def get_list_or_404(klass, *args, **kwargs):
109102
arguments and keyword arguments are used in the filter() query.
110103
"""
111104
queryset = _get_queryset(klass)
112-
obj_list = list(queryset.filter(*args, **kwargs))
105+
try:
106+
obj_list = list(queryset.filter(*args, **kwargs))
107+
except AttributeError:
108+
klass__name = klass.__name__ if isinstance(klass, type) else klass.__class__.__name__
109+
raise ValueError(
110+
"First argument to get_list_or_404() must be a Model, Manager, or "
111+
"QuerySet, not '%s'." % klass__name
112+
)
113113
if not obj_list:
114114
raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
115115
return obj_list

tests/get_object_or_404/tests.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -81,18 +81,18 @@ def test_get_object_or_404(self):
8181
def test_bad_class(self):
8282
# Given an argument klass that is not a Model, Manager, or Queryset
8383
# raises a helpful ValueError message
84-
msg = "Object is of type 'str', but must be a Django Model, Manager, or QuerySet"
84+
msg = "First argument to get_object_or_404() must be a Model, Manager, or QuerySet, not 'str'."
8585
with self.assertRaisesMessage(ValueError, msg):
8686
get_object_or_404(str("Article"), title__icontains="Run")
8787

8888
class CustomClass(object):
8989
pass
9090

91-
msg = "Object is of type 'CustomClass', but must be a Django Model, Manager, or QuerySet"
91+
msg = "First argument to get_object_or_404() must be a Model, Manager, or QuerySet, not 'CustomClass'."
9292
with self.assertRaisesMessage(ValueError, msg):
9393
get_object_or_404(CustomClass, title__icontains="Run")
9494

9595
# Works for lists too
96-
msg = "Object is of type 'list', but must be a Django Model, Manager, or QuerySet"
96+
msg = "First argument to get_list_or_404() must be a Model, Manager, or QuerySet, not 'list'."
9797
with self.assertRaisesMessage(ValueError, msg):
9898
get_list_or_404([Article], title__icontains="Run")

0 commit comments

Comments
 (0)