问题
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.
回答1:
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.
回答2:
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.
来源:https://stackoverflow.com/questions/16156899/django-loginformmiddleware-breaks-with-class-based-views