Fixed #15273 -- Extend RedirectView to allow reversal by name.

Thanks to @DrMeers for the report and @ludwigkraatz for the initial patch.
This commit is contained in:
Marc Tamlyn 2013-06-14 11:59:26 +01:00
parent 8365ed08b8
commit b7bd7087e6
4 changed files with 47 additions and 11 deletions

View File

@ -5,6 +5,7 @@ from functools import update_wrapper
from django import http from django import http
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse, NoReverseMatch
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.utils.decorators import classonlymethod from django.utils.decorators import classonlymethod
from django.utils import six from django.utils import six
@ -160,9 +161,10 @@ class RedirectView(View):
""" """
permanent = True permanent = True
url = None url = None
pattern_name = None
query_string = False query_string = False
def get_redirect_url(self, **kwargs): def get_redirect_url(self, *args, **kwargs):
""" """
Return the URL redirect to. Keyword arguments from the Return the URL redirect to. Keyword arguments from the
URL pattern match generating the redirect request URL pattern match generating the redirect request
@ -170,15 +172,21 @@ class RedirectView(View):
""" """
if self.url: if self.url:
url = self.url % kwargs url = self.url % kwargs
elif self.pattern_name:
try:
url = reverse(self.pattern_name, args=args, kwargs=kwargs)
except NoReverseMatch:
return None
else:
return None
args = self.request.META.get('QUERY_STRING', '') args = self.request.META.get('QUERY_STRING', '')
if args and self.query_string: if args and self.query_string:
url = "%s?%s" % (url, args) url = "%s?%s" % (url, args)
return url return url
else:
return None
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
url = self.get_redirect_url(**kwargs) url = self.get_redirect_url(*args, **kwargs)
if url: if url:
if self.permanent: if self.permanent:
return http.HttpResponsePermanentRedirect(url) return http.HttpResponsePermanentRedirect(url)

View File

@ -192,22 +192,24 @@ RedirectView
permanent = False permanent = False
query_string = True query_string = True
pattern_name = 'article-detail'
def get_redirect_url(self, pk): def get_redirect_url(self, *args, **kwargs):
article = get_object_or_404(Article, pk=pk) article = get_object_or_404(Article, pk=pk)
article.update_counter() article.update_counter()
return reverse('product_detail', args=(pk,)) return super(ArticleCounterRedirectView, self).get_redirect_url(*args, **kwargs)
**Example urls.py**:: **Example urls.py**::
from django.conf.urls import patterns, url from django.conf.urls import patterns, url
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
from article.views import ArticleCounterRedirectView from article.views import ArticleCounterRedirectView, ArticleDetail
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^(?P<pk>\d+)/$', ArticleCounterRedirectView.as_view(), name='article-counter'), url(r'^counter/(?P<pk>\d+)/$', ArticleCounterRedirectView.as_view(), name='article-counter'),
url(r'^details/(?P<pk>\d+)/$', ArticleDetail.as_view(), name='article-detail'),
url(r'^go-to-django/$', RedirectView.as_view(url='http://djangoproject.com'), name='go-to-django'), url(r'^go-to-django/$', RedirectView.as_view(url='http://djangoproject.com'), name='go-to-django'),
) )
@ -218,6 +220,11 @@ RedirectView
The URL to redirect to, as a string. Or ``None`` to raise a 410 (Gone) The URL to redirect to, as a string. Or ``None`` to raise a 410 (Gone)
HTTP error. HTTP error.
.. attribute:: pattern_name
The name of the URL pattern to redirect to. Reversing will be done
using the same args and kwargs as are passed in for this view.
.. attribute:: permanent .. attribute:: permanent
Whether the redirect should be permanent. The only difference here is Whether the redirect should be permanent. The only difference here is

View File

@ -688,6 +688,9 @@ Miscellaneous
url(r'^reset/done/$', 'django.contrib.auth.views.password_reset_complete', name='password_reset_complete') url(r'^reset/done/$', 'django.contrib.auth.views.password_reset_complete', name='password_reset_complete')
* :class:`~django.views.generic.base.RedirectView` now has a `pattern_name`
attribute which allows it to choose the target by reversing the URL.
Features deprecated in 1.6 Features deprecated in 1.6
========================== ==========================

View File

@ -317,7 +317,9 @@ class TemplateViewTest(TestCase):
self.assertEqual(response['Content-Type'], 'text/plain') self.assertEqual(response['Content-Type'], 'text/plain')
class RedirectViewTest(unittest.TestCase): class RedirectViewTest(TestCase):
urls = 'generic_views.urls'
rf = RequestFactory() rf = RequestFactory()
def test_no_url(self): def test_no_url(self):
@ -360,6 +362,22 @@ class RedirectViewTest(unittest.TestCase):
self.assertEqual(response.status_code, 301) self.assertEqual(response.status_code, 301)
self.assertEqual(response.url, '/bar/42/') self.assertEqual(response.url, '/bar/42/')
def test_named_url_pattern(self):
"Named pattern parameter should reverse to the matching pattern"
response = RedirectView.as_view(pattern_name='artist_detail')(self.rf.get('/foo/'), pk=1)
self.assertEqual(response.status_code, 301)
self.assertEqual(response['Location'], '/detail/artist/1/')
def test_named_url_pattern_using_args(self):
response = RedirectView.as_view(pattern_name='artist_detail')(self.rf.get('/foo/'), 1)
self.assertEqual(response.status_code, 301)
self.assertEqual(response['Location'], '/detail/artist/1/')
def test_wrong_named_url_pattern(self):
"A wrong pattern name returns 410 GONE"
response = RedirectView.as_view(pattern_name='wrong.pattern_name')(self.rf.get('/foo/'))
self.assertEqual(response.status_code, 410)
def test_redirect_POST(self): def test_redirect_POST(self):
"Default is a permanent redirect" "Default is a permanent redirect"
response = RedirectView.as_view(url='/bar/')(self.rf.post('/foo/')) response = RedirectView.as_view(url='/bar/')(self.rf.post('/foo/'))