As per a few other SO answers, I'm using middleware to show a login form on every page of my project, such that a user can login in-place. I am aware some frown upon this, but it really does make for a much better user experience. Ideally it'd be asynchronous, but I haven't gotten there.
Anyway, middleware.py:
from MyProj.forms import MyProjTopLoginForm
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
#Notes: https://stackoverflow.com/questions/2734055/putting-a-django-login-form-on-every-page
class LoginFormMiddleware(object):
def process_request(self, request):
# if the top login form has been posted
if request.method == 'POST' and 'top_login_form-username' in request.POST:
# validate the form
form = MyProjTopLoginForm(prefix="top_login_form", data=request.POST)
if form.is_valid():
# log the user in
from django.contrib.auth import login
login(request, form.get_user())
# if this is the logout page, then redirect to /
# so we don't get logged out just after logging in
if reverse('logout') in request.get_full_path():
return HttpResponseRedirect('/')
#return HttpResponseRedirect('/')
else:
form = MyProjTopLoginForm(request, prefix="top_login_form")
# attach the form to the request so it can be accessed within the templates
request.top_login_form = form
class LogoutFormMiddleware(object):
def process_request(self, request):
if request.method == 'POST' and request.POST.has_key('logout-button') and request.POST['logout-button'] == 'logout':
from django.contrib.auth import logout
logout(request)
request.method = 'GET'
and views.py:
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.db.models import Count
from django.core.urlresolvers import reverse
from django.views.generic import TemplateView
class Index(TemplateView):
template_name = 'KReport2/index.djhtml'
'''
def index(request):
return render(request, 'KReport2/index.djhtml')
'''
def logoutpage(request):
from django.contrib.auth import logout
logout(request)
return redirect(index)
You'll note that I've commented the old index view function and replaced it with a class-based view. Normally I wouldn't subclass, but instead pass template_name in urls.py, but that's besides the point here. My problem seems to be that the middleware breaks. Filling in UN/Pass on the index page, then submitting, the middleware captures the form and logs me in, but then django goes on to return a blank http response. No error, but also no rendered index page. I've just got no idea where the break is happening.
In class based views, the default dispatch method attempts to delegate to a method corresponds to the HTTP method.
The TemplateView
only implements a get()
method, so it only works for GET requests. When you log in with a POST
request, the dispatch method looks for TemplateView.post()
method. Because this does not exist, it returns an HTTP Error 405 (Method not allowed).
In your middleware, I suggest that you redirect to the same url after a successful login. This Post/Redirect/Get pattern is good advice in general. The browser will follow the redirect, and fetch the IndexView
with a GET request, which will be successful.
if form.is_valid():
# log the user in
from django.contrib.auth import login
login(request, form.get_user())
# if this is the logout page, then redirect to /
# so we don't get logged out just after logging in
if reverse('logout') in request.get_full_path():
return HttpResponseRedirect('/')
# redirect to the same url after a successful POST request.
return HttpResponseRedirect('')
Finally, the browser might display a blank page, but there's more information that can be useful for debugging. The Django dev server will show that it returned a 405 error code. Use the developer toolbars for your browser, which should show you the description of the error code 405 METHOD NOT ALLOWED
, and the Allow:get, head
header which tells you that the view does not allow post requests.
To finalize this question, Alasdair's answer is spot on. The final code I'm using is
from MyProj.forms import MyProjTopLoginForm
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
#Notes: http://stackoverflow.com/questions/2734055/putting-a-django-login-form-on-every-page
class LoginFormMiddleware(object):
def process_request(self, request):
# if the top login form has been posted
if request.method == 'POST' and 'top_login_form-username' in request.POST:
# validate the form
form = MyProjTopLoginForm(prefix="top_login_form", data=request.POST)
if form.is_valid():
# log the user in
from django.contrib.auth import login
login(request, form.get_user())
# if this is the logout page, then redirect to /
# so we don't get logged out just after logging in
if reverse('logout') in request.get_full_path():
return HttpResponseRedirect('/')
# Redirect to the same page after successfully handling the login form data.
return HttpResponseRedirect('')
# We could also do:
# request.method = 'GET'
# instead of a redirect, but if a user refreshes the page, they'll get prompted to re-send post data,
# which is super annoying.
else:
form = MyProjTopLoginForm(request, prefix="top_login_form")
# attach the form to the request so it can be accessed within the templates
request.top_login_form = form
class LogoutFormMiddleware(object):
def process_request(self, request):
if request.method == 'POST' and request.POST.has_key('logout-button') and request.POST['logout-button'] == 'logout':
from django.contrib.auth import logout
logout(request)
# Same as above. Handle the post data, then redirect to a new GET (not POST) request.
return HttpResponseRedirect('')
This solves the original issue, as well as issuing a redirect upon successful handling of (login, logout) data so that a user-triggered refresh doesn't prompt to resend the form.