Created
February 17, 2012 06:07
-
-
Save bmispelon/1851113 to your computer and use it in GitHub Desktop.
Django's auth using class-based views
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# https://code.djangoproject.com/ticket/17209 | |
import urlparse | |
from django.conf import settings | |
from django.core.urlresolvers import reverse_lazy | |
from django.http import HttpResponseRedirect, QueryDict | |
from django.utils.decorators import method_decorator | |
from django.utils.http import base36_to_int | |
from django.utils.translation import ugettext as _ | |
from django.views import generic | |
from django.views.decorators.debug import sensitive_post_parameters | |
from django.views.decorators.cache import never_cache | |
from django.views.decorators.csrf import csrf_protect | |
# Avoid shadowing the login() and logout() views below. | |
from django.contrib.auth import REDIRECT_FIELD_NAME, login as auth_login, logout as auth_logout | |
from django.contrib.auth.decorators import login_required | |
from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm, SetPasswordForm, PasswordChangeForm | |
from django.contrib.auth.models import User | |
from django.contrib.auth.tokens import default_token_generator | |
from django.contrib.sites.models import get_current_site | |
class CurrentAppMixin(object): | |
""" | |
Add a current_app property on the view and pass it to the response class. | |
""" | |
current_app = None | |
def render_to_response(self, context, **response_kwargs): | |
return self.response_class( | |
request=self.request, | |
template=self.get_template_names(), | |
context=context, | |
current_app=self.current_app, | |
**response_kwargs | |
) | |
class RedirectToMixin(object): | |
""" | |
Provide a success_url that takes into account a request parameter (whose | |
name is configurable). | |
In the absence of this parameter, a (configurable) default URL is used. | |
""" | |
redirect_field_name = REDIRECT_FIELD_NAME | |
default_redirect_to = None | |
def get_redirect_field_name(self): | |
return self.redirect_field_name | |
def get_default_redirect_to(self): | |
return self.default_redirect_to | |
def get_success_url(self): | |
default = self.get_default_redirect_to() | |
redirect_to = self.request.REQUEST.get(self.get_redirect_field_name()) | |
return redirect_to or default | |
class ProtectectedRedirectToMixin(RedirectToMixin): | |
""" | |
Ensure the URL to be redirected to is on the same host. | |
""" | |
def get_success_url(self): | |
redirect_to = super(ProtectectedRedirectToMixin, self).get_success_url() | |
if not redirect_to: | |
return redirect_to | |
netloc = urlparse.urlparse(redirect_to)[1] | |
if not netloc or netloc == self.request.get_host(): | |
return redirect_to | |
return self.get_default_redirect_to() | |
class LoginView(ProtectectedRedirectToMixin, CurrentAppMixin, generic.FormView): | |
""" | |
Displays the login form and handles the login action. | |
""" | |
form_class = AuthenticationForm | |
template_name = 'registration/login.html' | |
current_app = None | |
extra_context = None | |
default_redirect_to = settings.LOGIN_REDIRECT_URL | |
@method_decorator(sensitive_post_parameters()) | |
@method_decorator(csrf_protect) | |
@method_decorator(never_cache) | |
def dispatch(self, request, *args, **kwargs): | |
request.session.set_test_cookie() | |
return super(LoginView, self).dispatch(request, *args, **kwargs) | |
def get_form_kwargs(self): | |
kwargs = super(LoginView, self).get_form_kwargs() | |
kwargs.update({"request": self.request}) | |
return kwargs | |
def form_valid(self, form): | |
auth_login(self.request, form.get_user()) | |
if self.request.session.test_cookie_worked(): | |
self.request.session.delete_test_cookie() | |
return super(LoginView, self).form_valid(form) | |
def get_context_data(self, **kwargs): | |
context = super(LoginView, self).get_context_data(**kwargs) | |
current_site = get_current_site(self.request) | |
context.update({ | |
self.get_redirect_field_name(): self.get_success_url(), | |
"site": current_site, | |
"site_name": current_site.name, | |
}) | |
context.update(self.extra_context or {}) | |
return context | |
class LogoutView(ProtectectedRedirectToMixin, CurrentAppMixin, generic.TemplateView): | |
""" | |
Logs out the user and displays 'You are logged out' message. | |
""" | |
template_name = 'registration/logged_out.html' | |
current_app = None | |
extra_context = None | |
def get_context_data(self, **kwargs): | |
context = super(LogoutView, self).get_context_data(**kwargs) | |
current_site = get_current_site(self.request) | |
context.update({ | |
'site': current_site, | |
'site_name': current_site.name, | |
'title': _('Logged out') | |
}) | |
context.update(self.extra_context or {}) | |
return context | |
def get(self, request, *args, **kwargs): | |
auth_logout(request) | |
redirect_to = self.get_success_url() | |
if redirect_to is not None: | |
# Redirect to the current page if no default has been provided | |
redirect_to = redirect_to or self.request.path | |
return HttpResponseRedirect(redirect_to) | |
# Render the template | |
return super(LogoutView, self).get(request, *args, **kwargs) | |
# XXX: define post(), put(), ... ? | |
class LogoutThenLoginView(LogoutView): | |
""" | |
Logs out the user if he is logged in. Then redirects to the log-in page. | |
""" | |
login_url = None | |
@property | |
def next_page(self): | |
return self.login_url or settings.LOGIN_URL | |
class PasswordResetView(CurrentAppMixin, generic.FormView): | |
""" | |
Ask for the user's email address and send a message containing a token | |
allowing to reset the user's password. | |
""" | |
template_name = "registration/password_reset_form.html" | |
form_class = PasswordResetForm | |
success_url = reverse_lazy('django.contrib.auth.views.password_reset_done') | |
is_admin_site = False | |
email_template_name = "registration/password_reset_email.html" | |
subject_template_name = "registration/password_reset_subject.txt" | |
token_generator = default_token_generator | |
from_email = None | |
current_app = None | |
extra_context = None | |
def form_valid(self, form): | |
opts = { | |
'use_https': self.request.is_secure(), | |
'token_generator': self.token_generator, | |
'from_email': self.from_email, | |
'email_template_name': self.email_template_name, | |
'subject_template_name': self.subject_template_name, | |
'request': self.request, | |
} | |
if self.is_admin_site: | |
opts['domain_override'] = self.request.META['HTTP_HOST'] | |
form.save(**opts) | |
return super(PasswordResetView, self).form_valid(form) | |
def get_context_data(self, **kwargs): | |
context = super(PasswordResetView, self).get_context_data(**kwargs) | |
context.update(self.extra_context or {}) | |
return context | |
@method_decorator(csrf_protect) | |
def dispatch(self, request, *args, **kwargs): | |
return super(PasswordResetView, self).dispatch(request, *args, **kwargs) | |
class PasswordResetDoneView(CurrentAppMixin, generic.TemplateView): | |
""" | |
Show a confirmation message that a password reset email has been sent. | |
""" | |
template_name = "registration/password_reset_done.html" | |
current_app = None | |
extra_context = None | |
def get_context_data(self): | |
# XXX: does this work? | |
return self.extra_context | |
class PasswordResetConfirmView(CurrentAppMixin, generic.UpdateView): | |
# XXX: This one might have some backwards-compatibility issues in some corner cases. | |
# In this CBV, form.user is a User instance if the token matches the uidb36, | |
# even in the GET request, as opposed to the old view where form.user was | |
# only set in POST). | |
""" | |
Check that the given token is valid then prompt the user for a new pasword. | |
""" | |
template_name = "registration/password_reset_confirm.html" | |
form_class = SetPasswordForm | |
success_url = reverse_lazy('django.contrib.auth.views.password_reset_complete') | |
token_generator = default_token_generator | |
current_app = None | |
extra_context = None | |
def get_object(self, queryset=None): | |
try: | |
pk = base36_to_int(self.kwargs['uidb36']) | |
user = User.objects.get(pk=pk) | |
except (ValueError, User.DoesNotExist): | |
return None | |
if not self.token_generator.check_token(user, self.kwargs['token']): | |
return None | |
return user | |
def get_form_kwargs(self): | |
kwargs = super(PasswordResetConfirmView, self).get_form_kwargs() | |
kwargs['user'] = kwargs.pop('instance') | |
return kwargs | |
def form_valid(self, form): | |
if self.object is None: | |
return self.form_invalid(form) | |
return super(PasswordResetConfirmView, self).form_valid(form) | |
def get_context_data(self, **kwargs): | |
context = super(PasswordResetConfirmView, self).get_context_data(**kwargs) | |
context.update({ | |
'validlink': self.object is not None | |
}) | |
context.update(self.extra_context or {}) | |
return context | |
# Doesn't need csrf_protect since no-one can guess the URL | |
@method_decorator(sensitive_post_parameters()) | |
@method_decorator(never_cache) | |
def dispatch(self, request, *args, **kwargs): | |
return super(PasswordResetConfirmView, self).dispatch(request, *args, **kwargs) | |
class PasswordResetComplete(CurrentAppMixin, generic.TemplateView): | |
""" | |
Show a confirmation message that the user's password has been reset. | |
""" | |
template_name = "registration/password_reset_complete.html" | |
current_app = None | |
extra_context = None | |
def get_context_data(self, **kwargs): | |
context = super(PasswordResetComplete, self).get_context_data(**kwargs) | |
context['login_url'] = settings.LOGIN_URL | |
context.update(self.extra_context or {}) | |
return context | |
class PasswordChangeView(CurrentAppMixin, generic.UpdateView): | |
""" | |
Prompt the logged-in user for their old password and a new one and change | |
the password if the old password is valid. | |
""" | |
template_name = "registration/password_change_form.html" | |
success_url = reverse_lazy('django.contrib.auth.views.password_change_done') | |
form_class = PasswordChangeForm | |
current_app = None | |
extra_context = None | |
def get_object(self, queryset=None): | |
return self.request.user | |
def get_form_kwargs(self): | |
kwargs = super(PasswordChangeView, self).get_form_kwargs() | |
kwargs['user'] = kwargs.pop('instance') | |
return kwargs | |
@method_decorator(sensitive_post_parameters()) | |
@method_decorator(csrf_protect) | |
@method_decorator(login_required) | |
def dispatch(self, request, *args, **kwargs): | |
return super(PasswordChangeView, self).dispatch(request, *args, **kwargs) | |
class PasswordChangeDoneView(CurrentAppMixin, generic.TemplateView): | |
""" | |
Show a confirmation message that the user's password has been changed. | |
""" | |
template_name = "registration/password_change_done.html" | |
current_app = None | |
extra_context = None | |
def get_context_data(self, **kwargs): | |
context = super(PasswordChangeDoneView, self).get_context_data(**kwargs) | |
context.update(self.extra_context or {}) | |
return context | |
@method_decorator(login_required) | |
def dispatch(self, request, *args, **kwargs): | |
return super(PasswordChangeDoneView, self).dispatch(request, *args, **kwargs) | |
# Backwards-compatible stubs that call the class-based views: | |
# login | |
# logout | |
# logout_then_login | |
# redirect_to_login (not actually a view) | |
# password_reset | |
# password_reset_done | |
# password_reset_confirm | |
# password_reset_complete | |
# password_change | |
# password_change_done | |
def login(request, template_name=None, redirect_field_name=None, | |
authentication_form=None, current_app=None, extra_context=None): | |
kwargs = {} | |
if template_name is not None: | |
kwargs["template_name"] = template_name | |
if redirect_field_name is not None: | |
kwargs["redirect_field_name"] = redirect_field_name | |
if authentication_form is not None: | |
kwargs["form_class"] = authentication_form | |
if current_app is not None: | |
kwargs["current_app"] = current_app | |
if extra_context is not None: | |
kwargs["extra_context"] = extra_context | |
view = LoginView.as_view(**kwargs) | |
return view(request) | |
def logout(request, next_page=None, template_name=None, | |
redirect_field_name=None, current_app=None, extra_context=None): | |
kwargs = {} | |
if next_page is not None: | |
kwargs["default_redirect_to"] = next_page | |
if template_name is not None: | |
kwargs["template_name"] = template_name | |
if redirect_field_name is not None: | |
kwargs["redirect_field_name"] = redirect_field_name | |
if current_app is not None: | |
kwargs["current_app"] = current_app | |
if extra_context is not None: | |
kwargs["extra_context"] = extra_context | |
view = LogoutView.as_view(**kwargs) | |
return view(request) | |
def logout_then_login(request, login_url=None, current_app=None, extra_context=None): | |
kwargs = {} | |
if login_url is not None: | |
kwargs["login_url"] = login_url | |
if current_app is not None: | |
kwargs["current_app"] = current_app | |
if extra_context is not None: | |
kwargs["extra_context"] = extra_context | |
view = LogoutThenLoginView.as_view(**kwargs) | |
return view(request) | |
def redirect_to_login(next, login_url=None, | |
redirect_field_name=REDIRECT_FIELD_NAME): | |
""" | |
Redirects the user to the login page, passing the given 'next' page | |
""" | |
if not login_url: | |
login_url = settings.LOGIN_URL | |
login_url_parts = list(urlparse.urlparse(login_url)) | |
if redirect_field_name: | |
querystring = QueryDict(login_url_parts[4], mutable=True) | |
querystring[redirect_field_name] = next | |
login_url_parts[4] = querystring.urlencode(safe='/') | |
return HttpResponseRedirect(urlparse.urlunparse(login_url_parts)) | |
def password_reset(request, is_admin_site=None, template_name=None, | |
email_template_name=None, subject_template_name=None, | |
password_reset_form=None, token_generator=None, | |
post_reset_redirect=None, from_email=None, current_app=None, | |
extra_context=None): | |
kwargs = {} | |
if is_admin_site is not None: | |
kwargs["is_admin_site"] = is_admin_site | |
if template_name is not None: | |
kwargs["template_name"] = template_name | |
if email_template_name is not None: | |
kwargs["email_template_name"] = email_template_name | |
if password_reset_form is not None: | |
kwargs["form_class"] = password_reset_form | |
if token_generator is not None: | |
kwargs["token_generator"] = token_generator | |
if post_reset_redirect is not None: | |
kwargs["success_url"] = post_reset_redirect | |
if from_email is not None: | |
kwargs["from_email"] = from_email | |
if current_app is not None: | |
kwargs["current_app"] = current_app | |
if extra_context is not None: | |
kwargs["extra_context"] = extra_context | |
password_reset = PasswordResetView.as_view(**kwargs) | |
return password_reset(request) | |
def password_reset_done(request, template_name=None, current_app=None, | |
extra_context=None): | |
kwargs = {} | |
if template_name is not None: | |
kwargs["template_name"] = template_name | |
if current_app is not None: | |
kwargs["current_app"] = current_app | |
if extra_context is not None: | |
kwargs["extra_context"] = extra_context | |
password_reset_done = PasswordResetDoneView.as_view(**kwargs) | |
return password_reset_done(request) | |
def password_reset_confirm(request, uidb36=None, token=None, | |
template_name=None, token_generator=None, | |
set_password_form=None, post_reset_redirect=None, | |
current_app=None, extra_context=None): | |
assert uidb36 is not None and token is not None # checked by URLconf | |
kwargs = {} | |
if template_name is not None: | |
kwargs["template_name"] = template_name | |
if token_generator is not None: | |
kwargs["token_generator"] = token_generator | |
if set_password_form is not None: | |
kwargs["form_class"] = set_password_form | |
if post_reset_redirect is not None: | |
kwargs["success_url"] = post_reset_redirect | |
if current_app is not None: | |
kwargs["current_app"] = current_app | |
if extra_context is not None: | |
kwargs["extra_context"] = extra_context | |
password_reset_confirm = PasswordResetConfirmView.as_view(**kwargs) | |
return password_reset_confirm(request, uidb36=uidb36, token=token) | |
def password_reset_complete(request, template_name=None, current_app=None, | |
extra_context=None): | |
kwargs = {} | |
if template_name is not None: | |
kwargs["template_name"] = template_name | |
if current_app is not None: | |
kwargs["current_app"] = current_app | |
if extra_context is not None: | |
kwargs["extra_context"] = extra_context | |
password_reset_complete = PasswordResetComplete.as_view(**kwargs) | |
return password_reset_complete(request) | |
def password_change(request, template_name=None, post_change_redirect=None, | |
password_change_form=None, current_app=None, | |
extra_context=None): | |
kwargs = {} | |
if template_name is not None: | |
kwargs["template_name"] = template_name | |
if post_change_redirect is not None: | |
kwargs["success_url"] = post_change_redirect | |
if password_change_form is not None: | |
kwargs["form_class"] = password_change_form | |
if current_app is not None: | |
kwargs["current_app"] = current_app | |
if extra_context is not None: | |
kwargs["extra_context"] = extra_context | |
password_change = PasswordChangeView.as_view(**kwargs) | |
return password_change(request) | |
def password_change_done(request, template_name=None, post_change_redirect=None, | |
password_change_form=None, current_app=None, | |
extra_context=None): | |
kwargs = {} | |
if template_name is not None: | |
kwargs["template_name"] = template_name | |
if current_app is not None: | |
kwargs["current_app"] = current_app | |
if extra_context is not None: | |
kwargs["extra_context"] = extra_context | |
password_change_done = PasswordChangeDoneView.as_view(**kwargs) | |
return password_change_done(request) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment