'Make User email unique django

How can I make a Django User email unique when a user is signing up?

forms.py

class SignUpForm(UserCreationForm):
    email = forms.EmailField(required=True)

    class Meta:
        model = User
        fields = ("username", "email", "password1", "password2")

    def save(self, commit=True):
        user = super(SignUpForm, self).save(commit=False)
        user.email = self.cleaned_data["email"]
        if commit:
            user.save()
        return user

I'm using the from django.contrib.auth.models User. Do I need to override the User in the model. Currently the model doesn't make a reference to User.

views.py

class SignUp(generic.CreateView):
    form_class = SignUpForm
    success_url = reverse_lazy('login')
    template_name = 'signup.html'


Solution 1:[1]

The best answer is to use CustomUser by subclassing the AbstractUser and put the unique email address there. For example:

from django.contrib.auth.models import AbstractUser


class CustomUser(AbstractUser):
    email = models.EmailField(unique=True)

and update the settings with AUTH_USER_MODEL="app.CustomUser".

But if its not necessary for you to store the unique email in Database or maybe not use it as username field, then you can update the form's clean method to put a validation. For example:

from django.core.exceptions import ValidationError


class YourForm(UserCreationForm):

    def clean(self):
       email = self.cleaned_data.get('email')
       if User.objects.filter(email=email).exists():
            raise ValidationError("Email exists")
       return self.cleaned_data

Update

If you are in mid project, then you can follow the documentation on how to change migration, in short which is to:

  1. Backup you DB
  2. Create a custom user model identical to auth.User, call it User (so many-to-many tables keep the same name) and set db_table='auth_user' (so it uses the same table)
  3. Delete all Migrations File(except for __init__.py)
  4. Delete all entry from table django_migrations
  5. Create all migrations file using python manage.py makemigrations
  6. Run fake migrations by python manage.py migrate --fake
  7. Unset db_table, make other changes to the custom model, generate migrations, apply them

But if you are just starting, then delete the DB and migrations files in migration directory except for __init__.py. Then create a new DB, create new set of migrations by python manage.py makemigrations and apply migrations by python manage.py migrate.

And for references in other models, you can reference them to settings.AUTH_USER_MODEL to avoid any future problems. For example:

user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING)

It will automatically reference to the current User Model.

Solution 2:[2]

Here is a working code

Use the below code snippets in any of your models.py

models.py

from django.contrib.auth.models import User
User._meta.get_field('email')._unique = True

django version : 3.0.2

Reference : Django auth.user with unique email

Solution 3:[3]

There is a great example of this in Django's docs - https://docs.djangoproject.com/en/2.1/topics/auth/customizing/#a-full-example.

You have to declare the email field in your AbstractBaseUser model as unique=True.

class MyUser(AbstractBaseUser):
    email = models.EmailField(
        verbose_name='email address',
        max_length=255,
        unique=True,
    )
    date_of_birth = models.DateField()
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

Solution 4:[4]

Working Code for Django 3.1

models.py

from django.contrib.auth.models import User

User._meta.get_field('email')._unique = True
SETTINGS.PY
AUTHENTICATION_BACKENDS = [
     'django.contrib.auth.backends.ModelBackend'
]

Solution 5:[5]

You might be interested in:

django-user-unique-email

Reusable User model with required unique email field and mid-project support.

It defines custom User model reusing of the original table (auth_user) if exists. If needed (when added to existing project), it recreates history of applied migrations in the correct order.

I'll appreciate any feedback.

Solution 6:[6]

A better way of doing then using AbstractBaseUser

#forms.py
from django.core.exceptions import ValidationError
from django.contrib.auth.models import User
from django.contrib.auth.form import UserCreationForm
from some_app.validators import validate_email

def validate_email(value):
    if User.objects.filter(email = value).exists():
        raise ValidationError((f"{value} is taken."),params = {'value':value})

class UserRegistrationForm(UserCreationForm):
        email = forms.EmailField(validators = [validate_email])
        
        class Meta:
            model = User
        fields = ['username', 'email', 'password1', 'password2']    
    

Solution 7:[7]

In case of use CustomUser model inherit from AbstractBaseUser you can override the full_clean() method to validate unique constraints on the model fields you specified unique=True. This is safer than form (i.e. FormClass) validation.

Example:

from django.contrib.auth.models import AbstractBaseUser
from django.db import models

class CustomUser(AbstractBaseUser):
    email = models.EmailField(unique=True)
    # ...


    def full_clean(self, **kwargs):
        """
        Call clean_fields(), clean(), and validate_unique() on the model.
        Raise a ValidationError for any errors that occur.
        """
        super().full_clean()

Note: Tested on Django 3.1

Solution 8:[8]

Easy way: you can user signal Example

from django.db.models.signals import post_save, pre_save
from django.dispatch import  receiver
from django.contrib.auth.models import User
from django.forms import ValidationError

@receiver(pre_save, sender=User)
def check_email(sender, instance, **kwargs):
    email = instance.email
    if sender.objects.filter(email=email).exclude(username=instance.username).exists():
        raise ValidationError('Email Already Exists')

Solution 9:[9]

Improvement for solution with form validation

Instead of raising a ValidationError, it would be better to use the add_error method so that all errors of the forms are sent, and not only the one raised by ValidationError.

class SignUpForm(UserCreationForm):
    email = forms.EmailField(max_length=254, help_text='Required. Inform a valid email address.')

    class Meta:
        model = User
        fields = ('username', 'email', 'password1', 'password2', )

    def clean(self):
        cleaned_data = super().clean()
        email = cleaned_data.get('email')

        if User.objects.filter(email=email).exists():
            msg = 'A user with that email already exists.'
            self.add_error('email', msg)           
    
        return self.cleaned_data

Solution 10:[10]

You can edit model in meta as follow

  • Note: This will not update the original model
class SignUpForm(UserCreationForm):
    email = forms.EmailField(required=True)

    class Meta:
        model = User
        model._meta.get_field('email')._unique = True
        fields = ("username", "email", "password1", "password2")