Source code for superdjango.contrib.accounts.auth.views

"""
These views wrap Django's `authentication`_ views to provide template output. This also allows ad hoc customization if
needed.

.. _authentication: https://docs.djangoproject.com/en/stable/topics/auth/default/#module-django.contrib.auth.views

"""
# Imports

from datetime_machine import DateTime
from django.conf import settings
from django.contrib.auth.views import LoginView, LogoutView
from django.contrib.auth.forms import AuthenticationForm as DefaultAuthenticationForm
from django.http import Http404
from django.shortcuts import resolve_url
from django.urls import reverse, NoReverseMatch
from django.utils.translation import ugettext_lazy as _
from superdjango.conf import SUPERDJANGO
from superdjango.interfaces.hooks import hooks
from superdjango.views import RedirectView
from ..utils import get_user_profile
from .forms import AuthenticationForm

# Exports

__all__ = (
    "Login",
    "LoginRedirect",
    "Logout",
)

# Constants

LOGIN_REDIRECT_URL = getattr(settings, "LOGIN_REDIRECT_URL", "/")

# Views


[docs]class Login(LoginView): """Login form and redirect on successful authentication. Supports "remember me" functionality when ``SUPERDJANGO_REMEMBER_ME_ENABLED`` is ``True``. """ history_class = None """A model that may be used to record both successful and unsuccessful login attempts. This class must define three (3) fields: 1. ``added_dt``: The date and time the record was created, set to ``auto_now_add``. 2. ``success``: A boolean indicating success or failure. 3. ``user``: A reference to ``AUTH_USER_MODEL`` that is the subject of the login. .. code-block: python # models.py class LoginHistory(models.Model): added_dt = models.DateTimeField(auto_now_add=True) success = models.BooleanField() user = models.ForeignKey(AUTH_USER_MODEL) # views.py from superdjango.contrib.accounts.auth.views import Login as LoginView from .models import LoginHistory class Login(LoginView): history_class = LoginHistory """ max_attempts = SUPERDJANGO.USER_MAX_LOGIN_ATTEMPTS """The maximum number of login attempts (int) before a user account is marked inactive. Requires a ``history_class``. """ max_minutes = SUPERDJANGO.USER_MAX_LOGIN_MINUTES """The number of minutes (int) between login attempts.""" namespace = None password_reset_text = _("Forgot your password?") """The text to display for the password reset URL, when available.""" password_reset_url = None """The URL if any that allows a user to reset his/her password. Tip: Use ``reverse_lazy()``.""" pattern_name = "accounts_login" pattern_value = 'login/' # noinspection PyUnresolvedReferences template_name = "accounts/login.html"
[docs] def get_context_data(self, **kwargs): """Add ``accounts_password_reset`` URL as ``cancel_url`` and ``cancel_text``, if available.""" context = super().get_context_data(**kwargs) context['cancel_url'] = self.get_password_reset_url() context['cancel_text'] = self.password_reset_text return context
[docs] def get_form_class(self): """The default form class is used unless ``SUPERDJANGO_USER_REMEMBER_ME_ENABLED`` is ``True``.""" if SUPERDJANGO.USER_REMEMBER_ME_ENABLED: return AuthenticationForm return DefaultAuthenticationForm
[docs] def from_invalid(self, form): """If ``history_callback`` is callable, the unsuccessful login attempt is recorded.""" if self.history_class is not None: user = form.get_user() # noinspection PyUnresolvedReferences self.history_class.objects.create(success=False, user=user) # TODO: Implement management command to unlock user accounts after a specified time has passed. This may # require adding an is_locked field to the user table? Or see accounts.locked.models for a separate locking # model. if self.max_attempts is not None and self.max_minutes is not None: end = DateTime() start = DateTime() start.decrement(minutes=self.max_minutes) # noinspection PyUnresolvedReferences total_attempts = self.history_class.filter(user=user, added_dt__range=[end.dt, start.dt]).count() if total_attempts >= self.max_attempts: user.is_active = False user.save(update_fields=["is_active"]) try: return reverse("accounts_locked") except NoReverseMatch: raise Http404("Max login attempts have been reached.") return super().form_invalid(form)
[docs] def form_valid(self, form): """With valid submit, optionally set session expiration to ``SUPERDJANGO_USER_REMEMBER_ME_SECONDS``. If ``history_callback`` is callable, the successful login is recorded..""" if SUPERDJANGO.USER_REMEMBER_ME_ENABLED: self.request.session.set_expiry(SUPERDJANGO.USER_REMEMBER_ME_SECONDS) if self.history_class is not None: # noinspection PyUnresolvedReferences self.history_class.objects.create(success=True, user=form.get_user()) return super().form_valid(form)
[docs] def get_password_reset_url(self): """Get the URL for password resets. :rtype: str | None """ if self.password_reset_url is not None: return self.password_reset_url pattern_name = "accounts_password_reset" if self.namespace is not None: pattern_name = "%s:%s" % (self.namespace, "accounts_password_reset") try: return reverse(pattern_name) except NoReverseMatch: return None
[docs] def get_redirect_url(self): """Override to return ``accounts_login_redirect`` (if it exists) when no originating URL has been provided. .. note:: ``get_success_url()`` behaves as usual. """ url = super().get_redirect_url() # An originating has been provided and is safe. if url: return url # Attempt to return accounts_login_redirect, which may or may not be available depending upon the local # configuration. try: return reverse("accounts_login_redirect") except NoReverseMatch: return ""
[docs]class LoginRedirect(RedirectView): """Intelligently redirect the user based on preferences or global settings.""" pattern_name = "accounts_login_redirect" pattern_value = 'login/redirect/' permanent = False
[docs] def get_redirect_url(self, *args, **kwargs): """Get the user's preferred redirect if possible or the `LOGIN_REDIRECT_URL`_ by default. .. _LOGIN_REDIRECT_URL: https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-LOGIN_REDIRECT_URL """ results = hooks.get_hooks("get_login_redirect_url") if results: return results[0](self.request) profile = get_user_profile(self.request.user) if profile is not None: try: return profile.redirect_url except AttributeError: pass return resolve_url(LOGIN_REDIRECT_URL)
[docs]class Logout(LogoutView): """Log the user out.""" pattern_name = "accounts_logout" pattern_value = 'logout/' # noinspection PyUnresolvedReferences template_name = "accounts/logout.html"
[docs] def get_context_data(self, **kwargs): """Add ``login_url``, if available.""" context = super(Logout, self).get_context_data(**kwargs) try: context['login_url'] = reverse("accounts_login") except NoReverseMatch: context['login_url'] = None return context