Using RedirectView in urls.py

Posted on Sat 30 January 2016 in Web Development

Django's RedirectView is a generic Class-based View (or CBV for short) that is handy whenever you need to implement redirects. It's especially handy in urls.py for simple use cases where you don't need conditional redirects or any other business logic. In this post, I will explore a few use cases where you can use the view directly in the urls.py module.

A simple example

Suppose that we are revamping some areas of our product and we decide to create the new views from scratch. We want to be able to incrementally ship the new experience, while still keeping the old one around. At the end, we want to switch over to the new experience, without affecting the customers that still use the old URLs (ie. bookmarks, links sent in marketing emails, e.t.c.)

We can easily accomplish that by using RedirectView. Here's an example.

# urls.py
from django.conf.urls import url
from django.views.generic.base import RedirectView

from myapp.views import NewExperienceView

urlpatterns = [
    url(r'^path/$',
        RedirectView.as_view(pattern_name='new-path', permanent=True),
        name='path'),

    url(r'^path/new/$',
        NewExperienceView.as_view(),
        name='new-path')
]

It's as easy as that. We use RedirectView.as_view to create a new view function that we register with the url resolver. We set the pattern_name attribute to instruct it to redirect to that named url.

Note that we also used permanent=True to make the redirect permanent (301 Moved Permanently instead of 302 Found). We need this in our case, because this is a permanent switch and at some point we will remove the old URL rules. This is also a recomended practice by the search engines.

A slightly more complex example

What if our old view had required arguments encoded in the URL pattern? Will the approach presented above work?

It will, but only if the new view accepts the same arguments.

urlpatterns = [
    url(r'^path/to/(?P<object_slug>[\w-]+)/$',
        RedirectView.as_view(pattern_name='new-path', permanent=True),
        name='path'),

    url(r'^path/new/(?P<object_slug>[\w-]+)/$',
        NewExperienceView.as_view(),
        name='new-path')
]

An even more complex example

What if the new view doesn't have required arguments? In that case you can't use the above approach, or you'll get a 410 Gone response.

That's happening because RedirectView will try to use reverse on the provided pattern_name passing the same URL arguments extracted from your URL pattern. If the new view doesn't have arguments, the reverse will fail and you'll get the 410 Gone response (which is the standard Django response for a URL that is None).

How can we fix that? We can set the url directly and use reverse_lazy to get the actual URL of the new view.

# urls.py
from django.conf.urls import url
from django.core.urlresolvers import reverse_lazy
from django.views.generic.base import RedirectView

from myapp.views import NewExperienceView

urlpatterns = [
    url(r'^path/to/(?P<object_slug>[\w-]+)/$',
        RedirectView.as_view(url=reverse_lazy('new-path'), permanent=True),
        name='path-to-object-slug'),

    url(r'^path/new/$',
        NewExperienceView.as_view(),
        name='new-path')
]

We used reverse_lazy to get the actual URL for the new view and set that as the url attribute on the RedirectView. If url is set, RedirectView will not do a reverse, but instead will try to do a dictionary-style string formatting, passing any arguments that where captured in the URL. In our case, the URL doesn't have any string formatting arguments, so it will remain as it is and the redirect will be handled correctly.

Other use cases

For any other use cases you most probably won't be able to use the RedirectView.as_view directly. Here are a few examples:

  • Your new view has different arguments than the old one
  • You need to conditionally redirect based on the logged-in user or some other rules
  • You need to perform an operation on redirect (ie. increase an access counter)

In these cases you can always subclass RedirectView and provide a custom get_redirect_url() method. See Django docs on RedirectView for more details on how to do that.