Redirects

To implement redirects in Django, you need to know how they work in HTTP.

In HTTP, a redirect is an HTTP response with a status code in the 300-399 range, and a Location header that tells a browser which URL to go to. If your view returns a response like this, the browser will immediately make another request, to the specified URL.

The different 3XX codes have different meanings — make sure you use the right one.

That is 95% of what you need to know at the HTTP level. In Django, the most common functionality has been wrapped up for you in HttpResponseRedirect.

So this view, for example, does an unconditional, temporary redirect:

def my_view(request):
    return HttpResponseRedirect('/other/url/', status=307)

In addition, Django provides some shortcuts:

  • redirect, a utility that returns an HTTP response object and has built-in logic for redirecting to named views, and other things.

  • RedirectView — a class that provides an entire view that does redirection, and has a few neat features like being able to look up view by name, including arguments from a path, and also copy the query string. I recommend using this if the only thing that your view does is a redirect. Otherwise just use HttpResponse objects directly.

    For example, if you have an old URL at /old-path/<number>/ and want to permanently redirect it to /new-path/<number>/, you can use do it from urls.py like this:

    urls = [
        path('old-path/<int:pk>/', RedirectView.as_view(
            pattern_name='my_view',
            permanent=True,
            query_string=True,
        ),
        path('new-path/<int:pk>/', views.my_view, name='my_view'),
    ]
    

That’s it! On to Forms.

Discussion: CBV configuration in urls.py

If you can reduce a set of common functionality down to something that can be configured directly in urls.py, I think this is quite a nice pattern for making very simple views.

For redirects, RedirectView can work nicely. In my opinion once you get more complicated and need other logic, a function that uses HttpResponse objects will serve you better, or perhaps a function that delegates to RedirectView, rather than subclassing it.

Delegating to RedirectView is not perhaps the most obvious thing, due to how as_view() works. It looks like this:

# urls.py
   urls = [
       path('old-path/<int:pk>/', views.my_old_view)
       path('new-path/<int:pk>/', views.my_view, name='my_view'),
   ]
def my_old_view(request, pk):
    return RedirectView.as_view(
           pattern_name='my_view',
           permanent=True,
           query_string=True,
    )(request, pk)

Discussion: FBV configuration in urls.py

We should note that the pattern of defining views entirely within urls.py can be achieved just as well using functions as with View sub-classes.

Here are two methods for doing this:

Additional keyword parameters

See the docs for passing extra options to view functions.

So, for example, if we want to reproduce the functionality of RedirectView, complete with “configure it within urls.py”, we could have a view function like this:

def do_redirect(request, *args, pattern_name=None, permanent=False, query_string=True, **kwargs):
   url = reverse(pattern_name, args=args, kwargs=kwargs)
   # More of ``RedirectView`` logic here, using ``permanent`` and
   # ``query_string`` etc.
   return HttpResponseRedirect(url)
# urls.py

 urls = [
     path('old-path/<int:pk>/', do_redirect, {
         'pattern_name': 'my_view',
         'permanent': True,
         'query_string': True,
     }),
 ]

Mass-produced views — “view factories”

One of the issues with the above is you have a possibility of a clash between the contents of the configured kwargs and the other keyword arguments the view accepts. We can solve this using a function that returns a view function (in the same way that RedirectView.as_view() returns a view function). I call this a “view factory”. The outer function has keyword arguments for configuring the view, the inner function is the view itself:

def make_redirector(pattern_name=None, permanent=False, query_string=False):
   def redirector(request, *args, **kwargs):
       url = reverse(pattern_name, args=args, kwargs=kwargs)
       # More of ``RedirectView`` logic here, using ``permanent`` and
       # ``query_string`` etc.
       return HttpResponseRedirect(url)
   return redirector
# urls.py

 urls = [
     path('old-path/<int:pk>/', make_redirector(
         pattern_name='my_view',
         permanent=True,
         query_string=True,
     )),
 ]

This technique of “mass producing” view functions can be used to great effect in many other situations, especially if you have lots of extremely similar views that just need to be differently configured.

Unlike the example above where the view function accepts *args, **kwargs, the inner view function can have a signature that is as specific as you need it to be, so this will play well with type-checked parameters if you want it to.