فهرست منبع

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

Thanks to @DrMeers for the report and @ludwigkraatz for the initial patch.
Marc Tamlyn 11 سال پیش
والد
کامیت
b7bd7087e6
4فایلهای تغییر یافته به همراه47 افزوده شده و 11 حذف شده
  1. 14 6
      django/views/generic/base.py
  2. 11 4
      docs/ref/class-based-views/base.txt
  3. 3 0
      docs/releases/1.6.txt
  4. 19 1
      tests/generic_views/test_base.py

+ 14 - 6
django/views/generic/base.py

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

+ 11 - 4
docs/ref/class-based-views/base.txt

@@ -192,22 +192,24 @@ RedirectView
 
             permanent = False
             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.update_counter()
-                return reverse('product_detail', args=(pk,))
+                return super(ArticleCounterRedirectView, self).get_redirect_url(*args, **kwargs)
 
     **Example urls.py**::
 
         from django.conf.urls import patterns, url
         from django.views.generic.base import RedirectView
 
-        from article.views import ArticleCounterRedirectView
+        from article.views import ArticleCounterRedirectView, ArticleDetail
 
         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'),
         )
 
@@ -218,6 +220,11 @@ RedirectView
         The URL to redirect to, as a string. Or ``None`` to raise a 410 (Gone)
         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
 
         Whether the redirect should be permanent. The only difference here is

+ 3 - 0
docs/releases/1.6.txt

@@ -688,6 +688,9 @@ Miscellaneous
 
     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
 ==========================
 

+ 19 - 1
tests/generic_views/test_base.py

@@ -317,7 +317,9 @@ class TemplateViewTest(TestCase):
         self.assertEqual(response['Content-Type'], 'text/plain')
 
 
-class RedirectViewTest(unittest.TestCase):
+class RedirectViewTest(TestCase):
+    urls = 'generic_views.urls'
+
     rf = RequestFactory()
 
     def test_no_url(self):
@@ -360,6 +362,22 @@ class RedirectViewTest(unittest.TestCase):
         self.assertEqual(response.status_code, 301)
         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):
         "Default is a permanent redirect"
         response = RedirectView.as_view(url='/bar/')(self.rf.post('/foo/'))