Django User Logins...
I'm working on rebuilding my site, and to reduce trollery i am putting in mandatory logins. Django makes custom authentication really easy. All is really required is that you call authenticate, login, logout. Simple.
What I want is a common login / user form to exist at the top of every page. Rather than reading the docs properly (for shame!) i decided to dive in and learn some stuff. My approach was to create a template to include in the master (session.html) which provides the UI, and a middleware class to actually handle the session stuff. I'm going to leave the template out, as its fairly trivial (and non-valid XHTML currently)
Middleware
Writing middleware is straight forward. Basically, you create a class which implements any methods you need. For my needs I just needed to implement process_request(self, request). Here it is for your convienience:
middleware.py
from django.http import HttpResponse, HttpResponseRedirect
from django.contrib.auth import logout, authenticate, login
class Login(object):
def process_request (self, request):
"""Checks to see if 'login' or 'logout' POST arguments exist, and if so try to log in or out."""
if request.POST.get('login', False):
username = request.POST['__username__']
password = request.POST['__password__']
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
else:
request.session["login_message"] = "Login Failed"
return HttpResponseRedirect(request.path);
if request.POST.get('logout', False):
logout(request)
return HttpResponseRedirect(request.path);
As you can see, I am switching on the existence of POST variables. If the variables are found, then we will return a HttpResponseRedirect to the current page, to stop arsing up other things that work based on post backs ââ¬" In my case, thats some ugly wiki page code that really needs a refactor. You may also notice that a large part of that code is ripped straight from the docs, some underscores added around field names to avoid other potential collisions. Magically. There's a hack with a session variable, I'll get back to that later.
Lastly, to get this to work, i added 'brehaut.middleware.Login', to the MIDDLEWARE_CLASSES tuple in the settings file for my project.
Context Processors
This seems to be working well. Its simple, and its not intrusive. However, if the authentication fails the only way the user will know is that the form will still be there, but be empty. Preferably I would like to be able to leave a message for the user. The easiest way I can see is to provide an login_message variable for the template processor.
Here's where context processors seem to come in. By default the global_settings.py includes three context processors in the TEMPLATE_CONTEXT_PROCESSORS tuple. You will possibly have encountered the results of 'django.core.context_processors.auth' processors and definitely the 'django.core.context_processors.debug' processors.
I want to inject the variable i was talking about above into the template; heres the code i used to achieve it:
context_processors.py
def login_messages ( request ):
"""brehaut.middleware.Login may need messages added to the RequestContext
for the templates to spit out, we do that here
"""
new_dict = {
'login_message': request.session.get("login_message", ""),
}
try:
del request.session["login_message"]
except KeyError:
pass
return new_dict
Again, simple code. In this case, it checks the session for a 'login_message' variable, and if it exists it puts it into the new_dict which is added to the template context. Then it deletes it.
This makes use of the hack in the middleware, and its to get around the redirect without being to complex. As with anything here, if anyone has a better suggestion I would be quite happy to hear about it.
To use this i needed to add
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'brehaut.context_processors.login_messages',
)
to my settings.py
Conclusions
This works, its reasonably tidy other than one hack, and it illustrates how easy lower level stuff like middleware is in Django. I can't guarantee its the best approach, and if anyone has any suggestions I would love to hear from you.
Pros: I don't have to decorate every page for login details. I don't need to add any special log in URLs or template pages.
Cons: It requires two new python files, and messing with your settings a fair bit.
Related Resources
Comments
Oliver
Cool... now: troll troll trollity troll :D
Dan
Interesting, thanks for posting your code. With this method do you have to tell Django to use RequestContest by passing an instance of it as context_instance every time you do a render? Unless I'm misunderstanding something about django, there is no way to get it always do a process_context like you can for middleware. I have just started dealing with this, and I need to send a User object on every render since every page's header is different for authenticated users, but the best solution I've come up with so far requires me sending context_instance=RequestContext(request) in every render_to_response, which works fine for me - but isn't perfect. Dan
Brehaut
Hi Dan, I'm at work at the moment so I cant check code sorry.
To answer you question as best as I can, yes currently I am having to pass a RequestContext to render_to_response to get the context processor called - sorry for not being clearer about that. I'm also interested in cleaning that up if anyone has an ideas.
Andrew
Matt
There is a context processor which automatically makes the RequestContext available to each view. Put the following in your settings: TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.request', ... (I'm using generic views, so it was the only way to get the request available.)
Brehaut
Matt, I really don't understand how that works. The docs say
"If TEMPLATE_CONTEXT_PROCESSORS contains this processor, every RequestContext will contain a variable request, which is the current HttpRequest object. Note that this processor is not enabled by default; you'll have to activate it."The problem is, to create a
RequestContext in the first place, you have to pass it request anyway, so i cant see how this is going to save work.
Matt
Odd... if I recall correctly, my template rendered by generic views didn't have `request` available to them until I added that context processor. Maybe it's a generic view special case? Or just try it, and see if it works?
Brehaut
Yeah, I played around with it for about 20 minutes. No luck. Oh well.
