From cae92ae615b8622c25266463e392ef913066ea21 Mon Sep 17 00:00:00 2001
From: Gary Wilson Jr <gary.wilson@gmail.com>
Date: Sun, 22 Jul 2007 03:41:11 +0000
Subject: [PATCH] Fixed #4373 -- Modified the get_object_or_404/get_list_or_404
 shortcuts to also accept `QuerySet`s.  Thanks SuperJared.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@5746 bcc190cf-cafb-0310-a4f2-bffc1f526a37
---
 AUTHORS                                      |  1 +
 django/shortcuts/__init__.py                 | 41 ++++++++++++--------
 docs/db-api.txt                              | 14 +++++--
 tests/modeltests/get_object_or_404/models.py | 27 +++++++++++--
 4 files changed, 60 insertions(+), 23 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index e1a18adf9d..250d10bc8e 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -242,6 +242,7 @@ answer newbie questions, and generally made Django that much better:
     Thomas Steinacher <http://www.eggdrop.ch/>
     nowell strite
     Sundance
+    SuperJared
     Radek Švarz <http://www.svarz.cz/translate/>
     Swaroop C H <http://www.swaroopch.info>
     Aaron Swartz <http://www.aaronsw.com/>
diff --git a/django/shortcuts/__init__.py b/django/shortcuts/__init__.py
index 9d7120bc7a..e4bba0919e 100644
--- a/django/shortcuts/__init__.py
+++ b/django/shortcuts/__init__.py
@@ -7,6 +7,7 @@ for convenience's sake.
 from django.template import loader
 from django.http import HttpResponse, Http404
 from django.db.models.manager import Manager
+from django.db.models.query import QuerySet
 
 def render_to_response(*args, **kwargs):
     """
@@ -16,40 +17,46 @@ def render_to_response(*args, **kwargs):
     return HttpResponse(loader.render_to_string(*args, **kwargs))
 load_and_render = render_to_response # For backwards compatibility.
 
+def _get_queryset(klass):
+    """
+    Return a QuerySet from a Model, Manager, or QuerySet. Created to make
+    get_object_or_404 and get_list_or_404 more DRY.
+    """
+    if isinstance(klass, QuerySet):
+        return klass
+    elif isinstance(klass, Manager):
+        manager = klass
+    else:
+        manager = klass._default_manager
+    return manager.all()
+
 def get_object_or_404(klass, *args, **kwargs):
     """
     Use get() to return an object, or raise a Http404 exception if the object
     does not exist.
 
-    klass may be a Model or Manager object.  All other passed
+    klass may be a Model, Manager, or QuerySet object.  All other passed
     arguments and keyword arguments are used in the get() query.
 
     Note: Like with get(), an AssertionError will be raised if more than one
     object is found.
     """
-    if isinstance(klass, Manager):
-        manager = klass
-        klass = manager.model
-    else:
-        manager = klass._default_manager
+    queryset = _get_queryset(klass)
     try:
-        return manager.get(*args, **kwargs)
-    except klass.DoesNotExist:
-        raise Http404('No %s matches the given query.' % klass._meta.object_name)
+        return queryset.get(*args, **kwargs)
+    except queryset.model.DoesNotExist:
+        raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
 
 def get_list_or_404(klass, *args, **kwargs):
     """
     Use filter() to return a list of objects, or raise a Http404 exception if
-    the list is empty.
+    the list is emtpy.
 
-    klass may be a Model or Manager object.  All other passed
+    klass may be a Model, Manager, or QuerySet object.  All other passed
     arguments and keyword arguments are used in the filter() query.
     """
-    if isinstance(klass, Manager):
-        manager = klass
-    else:
-        manager = klass._default_manager
-    obj_list = list(manager.filter(*args, **kwargs))
+    queryset = _get_queryset(klass)
+    obj_list = list(queryset.filter(*args, **kwargs))
     if not obj_list:
-        raise Http404('No %s matches the given query.' % manager.model._meta.object_name)
+        raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
     return obj_list
diff --git a/docs/db-api.txt b/docs/db-api.txt
index 8e664ce3c1..9d0d4358f5 100644
--- a/docs/db-api.txt
+++ b/docs/db-api.txt
@@ -1891,8 +1891,8 @@ get_object_or_404()
 One common idiom to use ``get()`` and raise ``Http404`` if the
 object doesn't exist. This idiom is captured by ``get_object_or_404()``.
 This function takes a Django model as its first argument and an
-arbitrary number of keyword arguments, which it passes to the manager's
-``get()`` function. It raises ``Http404`` if the object doesn't
+arbitrary number of keyword arguments, which it passes to the default
+manager's ``get()`` function. It raises ``Http404`` if the object doesn't
 exist. For example::
 
     # Get the Entry with a primary key of 3
@@ -1901,7 +1901,7 @@ exist. For example::
 When you provide a model to this shortcut function, the default manager
 is used to execute the underlying ``get()`` query. If you don't want to
 use the default manager, or if you want to search a list of related objects,
-you can provide ``get_object_or_404()`` with a manager object instead.
+you can provide ``get_object_or_404()`` with a ``Manager`` object instead.
 For example::
 
     # Get the author of blog instance e with a name of 'Fred'
@@ -1911,6 +1911,14 @@ For example::
     # entry with a primary key of 3
     e = get_object_or_404(Entry.recent_entries, pk=3)
 
+If you need to use a custom method that you added to a custom manager,
+then you can provide ``get_object_or_404()`` with a ``QuerySet`` object.
+For example::
+
+    # Use a QuerySet returned from a 'published' method of a custom manager
+    # in the search for an entry with primary key of 5
+    e = get_object_or_404(Entry.objects.published(), pk=5)
+
 get_list_or_404()
 -----------------
 
diff --git a/tests/modeltests/get_object_or_404/models.py b/tests/modeltests/get_object_or_404/models.py
index 5f449f4cfe..573eaf2047 100644
--- a/tests/modeltests/get_object_or_404/models.py
+++ b/tests/modeltests/get_object_or_404/models.py
@@ -3,11 +3,11 @@
 
 get_object_or_404 is a shortcut function to be used in view functions for
 performing a get() lookup and raising a Http404 exception if a DoesNotExist
-exception was rasied during the get() call.
+exception was raised during the get() call.
 
 get_list_or_404 is a shortcut function to be used in view functions for
 performing a filter() lookup and raising a Http404 exception if a DoesNotExist
-exception was rasied during the filter() call.
+exception was raised during the filter() call.
 """
 
 from django.db import models
@@ -69,11 +69,28 @@ Http404: No Article matches the given query.
 >>> get_object_or_404(Article.by_a_sir, title="Run away!")
 <Article: Run away!>
 
+# QuerySets can be used too.
+>>> get_object_or_404(Article.objects.all(), title__contains="Run")
+<Article: Run away!>
+
+# Just as when using a get() lookup, you will get an error if more than one
+# object is returned.
+>>> get_object_or_404(Author.objects.all())
+Traceback (most recent call last):
+...
+AssertionError: get() returned more than one Author -- it returned ...! Lookup parameters were {}
+
+# Using an EmptyQuerySet raises a Http404 error.
+>>> get_object_or_404(Article.objects.none(), title__contains="Run")
+Traceback (most recent call last):
+...
+Http404: No Article matches the given query.
+
 # get_list_or_404 can be used to get lists of objects
 >>> get_list_or_404(a.article_set, title__icontains='Run')
 [<Article: Run away!>]
 
-# Http404 is returned if the list is empty
+# Http404 is returned if the list is empty.
 >>> get_list_or_404(a.article_set, title__icontains='Shrubbery')
 Traceback (most recent call last):
 ...
@@ -83,4 +100,8 @@ Http404: No Article matches the given query.
 >>> get_list_or_404(Article.by_a_sir, title__icontains="Run")
 [<Article: Run away!>]
 
+# QuerySets can be used too.
+>>> get_list_or_404(Article.objects.all(), title__icontains="Run")
+[<Article: Run away!>]
+
 """}