Skip to content
Advertisement

Removing case sensitivity from Email in Django login form

I’ve created a custom UserModel and used Email as main authenticating id instead of username.

The problem that it is case sensitive, as it counts test@gmail.com,Test@gmail.com as 2 different accounts.

I need to force it to deal with both as 1 account ignoring if it upper or lower case.

Here are my files :

models.py

class UserModelManager(BaseUserManager):
    def create_user(self, email, password, pseudo):
        user = self.model()
        user.name = name
        user.email = self.normalize_email(email=email)
        user.set_password(password)
        user.save()

        return user

    def create_superuser(self, email, password):
        '''
        Used for: python manage.py createsuperuser
        '''
        user = self.model()
        user.name = 'admin-yeah'
        user.email = self.normalize_email(email=email)
        user.set_password(password)

        user.is_staff = True
        user.is_superuser = True
        user.save()

        return user


class UserModel(AbstractBaseUser, PermissionsMixin):
    ## Personnal fields.
    email = models.EmailField(max_length=254, unique=True)
    name = models.CharField(max_length=16)
    ## [...]

    ## Django manage fields.
    date_joined = models.DateTimeField(auto_now_add=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELD = ['email', 'name']

    objects = UserModelManager()

    def __str__(self):
        return self.email

    def get_short_name(self):
        return self.name[:2].upper()

    def get_full_name(self):
        return self.name

signup view in views.py

def signup(request):
    if request.method == 'POST':
        signup_form = SignUpForm(request.POST)
        if signup_form.is_valid():
            signup_form.save()
            username = signup_form.cleaned_data.get('username')
            raw_password = signup_form.cleaned_data.get('password1')
            user = authenticate(username=username, password=raw_password)
            return redirect('signup_confirm')
    else:
        signup_form = SignUpForm()

    context = {
        'signup_form': signup_form,
    }
    return render(request, 'fostania_web_app/signup.html', context)


def signup_confirm(request):
    return render(request, 'fostania_web_app/signup_confirm.html')

the sign up form in forms.py:

class SignUpForm(UserCreationForm):
    email = forms.CharField(required=True, help_text='البريد الإلكترونى الخاص بك - يجب ان يكون حقيقى (يستخدم لتسجيل الدخول) ')
    name = forms.CharField(required=True, help_text='إسمك الحقيقى -  سيظهر كأسم البائع')
    password1 = forms.CharField(widget=forms.PasswordInput,
                                help_text='كلمة المرور - حاول ان تكون سهلة التذكر بالنسبة لك')
    password2 = forms.CharField(widget=forms.PasswordInput,
                                help_text='تأكيد كلمة المرور - إكتب نفس كلمة المرور السابقة مرة أخرى')

    class Meta:
        model = UserModel
        fields = ('email','name',  'password1', 'password2', )
        labels = {
            'name': 'إسمك الحقيقى -  سيظهر كأسم البائع',
            'email': 'البربد الإلكترونى Email',
            'password1': 'كلمة المرور',
            'password2': 'تأكيد كلمة المرور'
        }

All that I need now is to make it simply ignores the case sensitivity.

update

here is my login files

urls.py

   path('login/', auth_views.login, name='login'),

registartion/login.html

    <form method="post">
    {% csrf_token %}
    البريد الإلكترونى E-Mail &nbsp;<Br>{{ form.username }}
                    <br><br>
                     كلمة المرور &nbsp;<Br>{{ form.password }}
                    <Br><br>
      <div align="center">
    <button class ="btn btn-primary" submit>تسجيل الدخول</button><br><br>
      <a href="{% url 'signup' %}"><button type="button" class="btn btn-warning">
          إنشاء حساب جديد</button></a>
          </div>

  </form>

Advertisement

Answer

Addendum 2023:

According to this RFC, the local part of the address before the @ is to be handled by the mail host at its own terms, it mentions nothing about a standard requiring the local part to be lowercase, neither to handle addresses in a case insensitive way, even if most mail servers will do so nowadays.

This means that a mail server actually can discard mails sent to user.name@mail.tld if the account is set up as User.Name@mail.tld. This is also reflected in the original normalize_email method of Django’s BaseUser class, it only forces the domain part to be lowercase for this reason. Considering this, we have to store the local part of the address in is original format.

That being said, we can still enforce a case insensitive uniqueness check before saving the user to the DB. If we make an educated guess, we can assume that even if a mail server was set up to handle addresses case sensitive for historical reasons, that it has started to validate their uniqueness in a case insensitive way to avoid impersonation.

Django 4.0 and beyond

Django 4.0 comes with Functional unique constraints which is probably the most portable and specific solution:

from django.db.models import UniqueConstraint
from django.db.models.functions import Lower

class User:
    # ...
    class Meta:
        constraints = [
            UniqueConstraint(
                Lower("email"),
                name="user_email_ci_uniqueness",
            ),
        ]

Django before 4.0

This is not tested and syntax is from memory (also probably you have to display the validation error manually instead on the field because it will be hidden in the form) but it should convey the potential workaround.

from django.db import models

class User:
    # ...
    email = models.EmailField(
        blank=False,
        null=False,
    )
    email_lower = models.EmailField(
        blank=False,
        null=False,
        unique=True,
        editable=False,
        error_messages={
            "unique": "A user is already registered with this email address",
        },
    )

    def clean(self):
        super().clean()
        self.email_lower = self.email.lower()

Original Post:

You don’t need to change much to accomplish this – in your case you just need to change the form and make use of Django’s built-in form data cleaners or by making a custom field.

You should use the EmailField instead of a CharField for built-in validation. Also you did not post your AuthenticationForm, but i presume you have changed it to include email instead of username.

With data cleaners:

class SignUpForm(UserCreationForm):
    # your code
    email = forms.EmailField(required=True)
    def clean_email(self):
        data = self.cleaned_data['email']
        return data.lower()

class AuthenticationForm(forms.Form):
    # your code
    email = forms.EmailField(required=True)
    def clean_email(self):
        data = self.cleaned_data['email']
        return data.lower()

With a custom field:

class EmailLowerField(forms.EmailField):
    def to_python(self, value):
        return value.lower()

class SignUpForm(UserCreationForm):
    # your code
    email = EmailLowerField(required=True)

class AuthenticationForm(forms.Form):
    # your code
    email = EmailLowerField(required=True)

This way you can make sure that each email is saved to your database in lowercase and that for each login attempt the email is lowercased before compared to a database value.

User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement