From d7e81275242a8439768367059701baa00a9be996 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Sat, 21 Mar 2009 13:09:13 +0000 Subject: [PATCH] Fixed #10194: added `django.shortcuts.redirect`, a do-what-I-mean redirect shortcut. See the docs at topics/http/shortcuts for details. git-svn-id: http://code.djangoproject.com/svn/django/trunk@10108 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/shortcuts/__init__.py | 41 ++++++- docs/topics/http/shortcuts.txt | 101 +++++++++++++++--- .../urlpatterns_reverse/tests.py | 33 ++++++ 3 files changed, 160 insertions(+), 15 deletions(-) diff --git a/django/shortcuts/__init__.py b/django/shortcuts/__init__.py index 1cece7c7c0..823c6bb5e7 100644 --- a/django/shortcuts/__init__.py +++ b/django/shortcuts/__init__.py @@ -6,8 +6,10 @@ for convenience's sake. from django.template import loader from django.http import HttpResponse, Http404 +from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect from django.db.models.manager import Manager from django.db.models.query import QuerySet +from django.core import urlresolvers def render_to_response(*args, **kwargs): """ @@ -17,6 +19,43 @@ def render_to_response(*args, **kwargs): httpresponse_kwargs = {'mimetype': kwargs.pop('mimetype', None)} return HttpResponse(loader.render_to_string(*args, **kwargs), **httpresponse_kwargs) +def redirect(to, *args, **kwargs): + """ + Returns an HttpResponseRedirect to the apropriate URL for the arguments + passed. + + The arguments could be: + + * A model: the model's `get_absolute_url()` function will be called. + + * A view name, possibly with arguments: `urlresolvers.reverse()` will + be used to reverse-resolve the name. + + * A URL, which will be used as-is for the redirect location. + + By default issues a temporary redirect; pass permanent=True to issue a + permanent redirect + """ + if kwargs.pop('permanent', False): + redirect_class = HttpResponsePermanentRedirect + else: + redirect_class = HttpResponseRedirect + + # If it's a model, use get_absolute_url() + if hasattr(to, 'get_absolute_url'): + return redirect_class(to.get_absolute_url()) + + # Next try a reverse URL resolution. + try: + return redirect_class(urlresolvers.reverse(to, args=args, kwargs=kwargs)) + except urlresolvers.NoReverseMatch: + # If this doesn't "feel" like a URL, re-raise. + if '/' not in to and '.' not in to: + raise + + # Finally, fall back and assume it's a URL + return redirect_class(to) + def _get_queryset(klass): """ Returns a QuerySet from a Model, Manager, or QuerySet. Created to make @@ -59,4 +98,4 @@ def get_list_or_404(klass, *args, **kwargs): obj_list = list(queryset.filter(*args, **kwargs)) if not obj_list: raise Http404('No %s matches the given query.' % queryset.model._meta.object_name) - return obj_list + return obj_list \ No newline at end of file diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt index d68faee258..11b17ed4a8 100644 --- a/docs/topics/http/shortcuts.txt +++ b/docs/topics/http/shortcuts.txt @@ -4,16 +4,23 @@ Django shortcut functions ========================= +.. module:: django.shortcuts + :synopsis: + Convience shortcuts that spam multiple levels of Django's MVC stack. + +.. index:: shortcuts + The package ``django.shortcuts`` collects helper functions and classes that "span" multiple levels of MVC. In other words, these functions/classes introduce controlled coupling for convenience's sake. -``render_to_response()`` -======================== +``render_to_response`` +====================== -``django.shortcuts.render_to_response`` renders a given template with a given -context dictionary and returns an ``HttpResponse`` object with that rendered -text. +.. function:: render_to_response(template[, dictionary][, context_instance][, mimetype]) + + Renders a given template with a given context dictionary and returns an + :class:`~django.http.HttpResponse` object with that rendered text. Required arguments ------------------ @@ -42,9 +49,8 @@ Optional arguments context_instance=RequestContext(request)) ``mimetype`` - - .. versionadded:: 1.0 - + .. versionadded:: 1.0 + The MIME type to use for the resulting document. Defaults to the value of the :setting:`DEFAULT_CONTENT_TYPE` setting. @@ -73,12 +79,77 @@ This example is equivalent to:: r = HttpResponse(t.render(c), mimetype="application/xhtml+xml") +``redirect`` +============ + +.. function:: redirect(to[, permanent=False], *args, **kwargs) + + Returns an HttpResponseRedirect to the apropriate URL for the arguments + passed. + + The arguments could be: + + * A model: the model's `get_absolute_url()` function will be called. + + * A view name, possibly with arguments: `urlresolvers.reverse()` will + be used to reverse-resolve the name. + + * A URL, which will be used as-is for the redirect location. + + By default issues a temporary redirect; pass permanent=True to issue a + permanent redirect + +Examples +-------- + +You can use the :func:`redirect` function in a number of ways. + + 1. By passing some object; that object's + :meth:`~django.db.models.Model.get_absolute_url` method will be called + to figure out the redirect URL:: + + def my_view(request): + ... + object = MyModel.objects.get(...) + return redirect(object) + + 2. By passing the name of a view and optionally some positional or + keyword arguments; the URL will be reverse resolved using the + :func:`~django.core.urlresolvers.reverse` method:: + + def my_view(request): + ... + return redirect('some-view-name', foo='bar') + + 3. By passing a hardcoded URL to redirect to:: + + def my_view(request): + ... + return redirect('/some/url/') + + This also works with full URLs:: + + def my_view(request): + ... + return redirect('http://example.com/') + +By default, :func:`redirect` returns a temporary redirect. All of the above +forms accept a ``permanent`` argument; if set to ``True`` a permanent redirect +will be returned:: + + def my_view(request): + ... + object = MyModel.objects.get(...) + return redirect(object, permanent=True) + ``get_object_or_404`` ===================== -``django.shortcuts.get_object_or_404`` calls -:meth:`~django.db.models.QuerySet.get()` on a given model manager, but it raises -``django.http.Http404`` instead of the model's ``DoesNotExist`` exception. +.. function:: get_object_or_404(object, *args, **kwargs) + + Calls :meth:`~django.db.models.QuerySet.get()` on a given model manager, + but it raises ``django.http.Http404`` instead of the model's + ``DoesNotExist`` exception. Required arguments ------------------ @@ -118,9 +189,11 @@ raised if more than one object is found. ``get_list_or_404`` =================== -``django.shortcuts.get_list_or_404`` returns the result of -:meth:`~django.db.models.QuerySet.filter()` on a given model manager, raising -``django.http.Http404`` if the resulting list is empty. +.. function:: get_list_or_404(klass, *args, **kwargs) + + Returns the result of :meth:`~django.db.models.QuerySet.filter()` on a + given model manager, raising ``django.http.Http404`` if the resulting list + is empty. Required arguments ------------------ diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py index aecfdfc6c5..b7ef0e5b9b 100644 --- a/tests/regressiontests/urlpatterns_reverse/tests.py +++ b/tests/regressiontests/urlpatterns_reverse/tests.py @@ -3,6 +3,8 @@ Unit tests for reverse URL lookups. """ from django.core.urlresolvers import reverse, NoReverseMatch +from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect +from django.shortcuts import redirect from django.test import TestCase test_data = ( @@ -97,3 +99,34 @@ class URLPatternReverse(TestCase): else: self.assertEquals(got, expected) +class ReverseShortcutTests(TestCase): + urls = 'regressiontests.urlpatterns_reverse.urls' + + def test_redirect_to_object(self): + # We don't really need a model; just something with a get_absolute_url + class FakeObj(object): + def get_absolute_url(self): + return "/hi-there/" + + res = redirect(FakeObj()) + self.assert_(isinstance(res, HttpResponseRedirect)) + self.assertEqual(res['Location'], '/hi-there/') + + res = redirect(FakeObj(), permanent=True) + self.assert_(isinstance(res, HttpResponsePermanentRedirect)) + self.assertEqual(res['Location'], '/hi-there/') + + def test_redirect_to_view_name(self): + res = redirect('hardcoded2') + self.assertEqual(res['Location'], '/hardcoded/doc.pdf') + res = redirect('places', 1) + self.assertEqual(res['Location'], '/places/1/') + res = redirect('headlines', year='2008', month='02', day='17') + self.assertEqual(res['Location'], '/headlines/2008.02.17/') + self.assertRaises(NoReverseMatch, redirect, 'not-a-view') + + def test_redirect_to_url(self): + res = redirect('/foo/') + self.assertEqual(res['Location'], '/foo/') + res = redirect('http://example.com/') + self.assertEqual(res['Location'], 'http://example.com/') \ No newline at end of file