# Source code for django_otp.views

    from functools import partial

    from django.contrib.auth import BACKEND_SESSION_KEY
    from django.contrib.auth import views as auth_views
    from django.utils.functional import cached_property

    from django_otp.forms import OTPAuthenticationForm, OTPTokenForm

    class LoginView(auth_views.LoginView):
        """
        This is a replacement for :class:`django.contrib.auth.views.LoginView` that
        requires two-factor authentication. It's slightly clever: if the user is
        already authenticated but not verified, it will only ask the user for their
        OTP token. If the user is anonymous or is already verified by an OTP
        device, it will use the full username/password/token form. In order to use
        this, you must supply a template that is compatible with both
        :class:`~django_otp.forms.OTPAuthenticationForm` and
        :class:`~django_otp.forms.OTPTokenForm`. This is a good view for
        :setting:`OTP_LOGIN_URL`.

        """
        otp_authentication_form = OTPAuthenticationForm
        otp_token_form = OTPTokenForm

        @cached_property
        def authentication_form(self):
            user = self.request.user
            if user.is_anonymous or user.is_verified():
                form = self.otp_authentication_form
            else:
                form = partial(self.otp_token_form, user)

            return form

        def form_valid(self, form):
            # OTPTokenForm does not call authenticate(), so we may need to populate
            # user.backend ourselves to keep login() happy.
            user = form.get_user()
            if not hasattr(user, 'backend'):
                user.backend = self.request.session[BACKEND_SESSION_KEY]

            return super().form_valid(form)

    # Backwards compatibility.
    login = LoginView.as_view()