I extended the Django AuthenticationForm and created two backends to allow users to sign in with either (or both) email and username. This works perfectly if I try to sign in to the Django admin using any of the credentials. Although I can sign in on the frontend using any of these credentials, validation errors fail to show anytime the wrong credentials are used.
forms.py
class CustomAuthenticationForm(AuthenticationForm):
username = forms.CharField(widget=TextInput(
attrs={'placeholder': 'Enter your email address'}))
password = forms.CharField(widget=PasswordInput(attrs={'placeholder': 'Password'}))
remember_me = forms.BooleanField(required=False, label="Keep me signed in", initial=False)
views.py
class CustomLoginView(LoginView):
form_class = CustomAuthenticationForm
redirect_field_name = REDIRECT_FIELD_NAME
template_name = 'login.html'
def get(self, request, *args, **kwargs):
if request.user.is_authenticated:
if request.user.is_personal:
return HttpResponseRedirect(reverse(...))
elif request.user.is_business:
return HttpResponseRedirect(reverse(...))
else:
return HttpResponseRedirect(reverse(...))
return super(LoginView, self).get(request, *args, **kwargs)
def form_valid(self, form):
remember_me = form.cleaned_data['remember_me']
if not remember_me:
self.request.session.set_expiry(0)
self.request.session.modified = True
auth_login(self.request, form.get_user())
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
if self.request.user.is_personal:
return reverse(...)
elif self.request.user.is_business:
return reverse(...)
else:
return reverse(...)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
current_site = get_current_site(self.request)
context.update({
self.redirect_field_name: self.get_redirect_url(),
'site': current_site,
'site_name': current_site.name,
'title': _('Log in to your account'),
**(self.extra_context or {})
})
return context
backends.py
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
User = get_user_model()
class CaseInsensitiveModelBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
UserModel = get_user_model()
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
try:
case_insensitive_username_field = '{}__iexact'.format(UserModel.USERNAME_FIELD)
user = UserModel._default_manager.get(**{case_insensitive_username_field: username})
UserModel().set_password(password)
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
class CustomEmailAuthenticationBackend(ModelBackend):
def authenticate(self, request, **kwargs):
email = kwargs['username'].lower()
password = kwargs['password']
try:
user_with_email = User.objects.get(email=email)
except User.DoesNotExist:
return None
else:
if user_with_email.is_active and user_with_email.check_password(password):
return user_with_email
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
settings.py
AUTHENTICATION_BACKENDS = (
'accounts.backends.CustomEmailAuthenticationBackend',
'accounts.backends.CaseInsensitiveModelBackend',
'django.contrib.auth.backends.AllowAllUsersModelBackend',
)
login.html
<form class="form" method="post" enctype="multipart/form-data" role="form"
action="{% URL:'accounts:login' %}">
{% csrf_token %}
{% bootstrap_messages %}
{% bootstrap_field form.username show_label=False %}
{% bootstrap_field form.password show_label=False %}
{% bootstrap_field form.remember_me show_label=False %}
<button class="btn block-btn" type="submit" role="button">{% trans 'Sign in' %}</button>
</form>
urls.py
path(_('login/'), CustomLoginView.as_view(), name='login'),
You don't render the errors so they don't show up. If you render the fields individually the forms errors won't be rendered automatically, you need to render them yourselves. You can do this by looping over form.non_field_errors
(for non-field errors) and form.<field_name>.errors
(for errors on specific fields):
<form class="form" method="post" enctype="multipart/form-data" role="form"
action="{% URL:'accounts:login' %}">
{% csrf_token %}
{% bootstrap_messages %}
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
{% bootstrap_field form.username show_label=False %}
{% for error in form.username.errors %}
{{ error }}
{% endfor %}
{% bootstrap_field form.password show_label=False %}
{% for error in form.password.errors %}
{{ error }}
{% endfor %}
{% bootstrap_field form.remember_me show_label=False %}
{% for error in form.remember_me.errors %}
{{ error }}
{% endfor %}
<button class="btn block-btn" type="submit" role="button">{% trans 'Sign in' %}</button>
</form>