Private by default

On July 22, 2007 in development, django, python

When most pages in a site require authentication, decorating all the views with @login_required can be annoying. You can reverse the default behavior by creating a custom middleware class:

1. middleware.py
import urllib
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.http import HttpResponseRedirect

def allow_anonymous(view_func):
  view_func.allow_anonymous = True
  return view_func

class RequireLogin:
  def process_view(self, request, view_func, view_args, view_kwargs):
    if request.path != settings.LOGIN_URL and \
      not request.user.is_authenticated() and \
      not getattr(view_func, 'allow_anonymous', False):
      url = '%s?%s=%s' % (settings.LOGIN_URL, REDIRECT_FIELD_NAME, \
                          urllib.quote(request.get_full_path()))
      return HttpResponseRedirect(url)

That’s an ugly block of code but it’s not too complex. allow_anonymous is a function decorator, like login_required. It just tags the function to tell the middleware that authentication isn’t required. The RequireLogin class verifies that the user is logged in. If not, and if the function is not decorated with allow_anonymous, it redirects to settings.LOGIN_URL.

So put that code in a file in your project – let’s say, yourproject/yourapp/middleware.py. Then open settings.py and add "yourproject.yourapp.middleware.RequireLogin" to MIDDLEWARE_CLASSES. This tells Django about your new middleware class, and RequireLogin.process_view will be called any time a view is about to be rendered.

Now you can use it in your view:

2. views.py
from yourproject.yourapp.middleware import allow_anonymous

def some_private_view(request):
  # won't be accessible unless user is logged in
  return HttpResponse('Hello, user!')

@allow_anonymous
def some_public_view(request):
  return HttpResponse('Hello, world!')

If all is working correctly, some_private_view should ask for a login, but some_public_view will allow viewing without it.

Update: I’ve updated the code to fix a bug when using django.contrib.auth.views.login for your login view. As you can’t mark this view function with @allow_anonymous, it would infinitely redirect back to it. Oops! Thanks for pointing it out, Phil.

12 comments Add yours…

Phil, 9 months ago

Hi Nathan. This is a good use case, and one which I’m currently working with. It was great to stumble across your post at just the right time.

I have a question though: how does the middleware know that it’s dealing with a login request? When I try to implement this, it goes into a loop, constantly rebuilding the URL and redirecting until the server blows up.

Am I missing something blindingly obvious?

MIchael Henson, 9 months ago

You have to decorate your login view with @allow_anonymous or else the Middleware will assume that you have to be redirected again.

Nathan Ostgard, 9 months ago

Doh! Good catch, Phil. I’ve updated the middleware code to handle that situation.

mark, 9 months ago

If you are using the django.views.static to serve static content during development you have to wrap django.views.static.serve with allow_anonymous otherwise it will return all redirects for items associated with other anonymous pages.

Richard H, 9 months ago

I am using django.views.static.serve in my urls.py. Where is the best place to do the allow_anonymous wrapping? Can anyone give an example?

D3f0, 8 months ago

You can add something like:
if request.path.startswith(URL_MEDIA):
return
since nobody would hook some view under his/her media root, that’s a simple and effective solution. In my case, I use put the url media inside settings.py, so I just add settings.URL_MEDIA

Hope it helps!

edebiyat, 2 months ago

I have a question though: how does the middleware know that it’s dealing with a login request? When I try to implement this, it goes into a loop, constantly rebuilding the URL and redirecting until the server blows up.

bill, about 1 month ago

yes this will help as “D3f0” suggests
“just add settings.URL_MEDIA” thanks

plau, about 1 month ago

Helpfull and easy to use.

Games, 19 days ago

Thanks it is useful.

kuaför malzemeleri, 5 days ago

well i had a session error i gues i ve implemeted it not correctly

Post a comment