68

I need to patch the standard User model of contrib.auth by ensuring the email field entry is unique:

User._meta.fields[4].unique = True

Where is best place in code to do that?

I want to avoid using the number fields[4]. It's better to user fields['email'], but fields is not dictionary, only list.

Another idea may be to open a new ticket and upload a patch with new parameter inside settings.py:

AUTH_USER_EMAIL_UNIQUE = True

Any suggestions on the most correct way to achieve email address uniqueness in the Django User model?

p.campbell
  • 98,673
  • 67
  • 256
  • 322
ramusus
  • 7,789
  • 5
  • 38
  • 45

19 Answers19

51

Caution: The code below was written for an older version of Django (before Custom User Models were introduced). It contains a race condition, and should only be used with a Transaction Isolation Level of SERIALIZABLE and request-scoped transactions.

Your code won't work, as the attributes of field instances are read-only. I fear it might be a wee bit more complicated than you're thinking.

If you'll only ever create User instances with a form, you can define a custom ModelForm that enforces this behavior:

from django import forms
from django.contrib.auth.models import User

class UserForm(forms.ModelForm):
    class Meta:
        model = User

    def clean_email(self):
        email = self.cleaned_data.get('email')
        username = self.cleaned_data.get('username')
        if email and User.objects.filter(email=email).exclude(username=username).exists():
            raise forms.ValidationError(u'Email addresses must be unique.')
        return email

Then just use this form wherever you need to create a new user.

BTW, you can use Model._meta.get_field('field_name') to get fields by name, rather than by position. So for example:

# The following lines are equivalent
User._meta.fields[4]
User._meta.get_field('email')

UPDATE

The Django documentation recommends you use the clean method for all validation that spans multiple form fields, because it's called after all the <FIELD>.clean and <FIELD>_clean methods. This means that you can (mostly) rely on the field's value being present in cleaned_data from within clean.

Since the form fields are validated in the order they're declared, I think it's okay to occasionally place multi-field validation in a <FIELD>_clean method, so long as the field in question appears after all other fields it depends on. I do this so any validation errors are associated with the field itself, rather than with the form.

elo80ka
  • 14,837
  • 3
  • 36
  • 43
  • 1
    It's more complicated. I want to setup application **django-registration** with built-in RegistrationForm. And I want to check unicity of *email* field too with *username*. – ramusus Jul 23 '09 at 10:31
  • This won't work properly when you want to edit User. The validation if email and User.objects.filter(email=email).count() will spoil the fun. How to check ID along with the email? – Viet Feb 17 '10 at 06:51
  • Btw, I excluded username from the ModelForm. How to do then? – Viet Feb 17 '10 at 13:20
  • Shouldn't this be in the form subclass's clean method? The Django docs say at http://docs.djangoproject.com/en/1.2/ref/forms/validation/ that if any validation requires access to multiple fields from the form at once, it should be done in the form subclass's clean method. – chefsmart Jun 28 '10 at 06:34
  • @chefsmart: You're sorta right, but see my update in the answer. – elo80ka Jun 30 '10 at 14:21
  • So...it was a simple as he thought? User._meta.get_field('email').unique=True does what he wants. – Cerin Jan 05 '11 at 22:00
  • If the user is going to register their own account, then a User ModelForm isn't so useful -- it is more work to block all the unwanted model attributes and to add in the password handling than it is just to write a "shadow" form for just username, password, email, first_name, last_name. – IanSR Mar 31 '11 at 03:16
  • @IanSR: not sure I understand. How would your "shadow form" handle unique email addresses? Wouldn't you still end up with the same (or similar) code? – elo80ka Apr 05 '11 at 08:41
  • The method with extra database request in clean() definitely bad. What if you have several million users? – sergzach Jul 07 '13 at 17:35
  • @sergzach: agreed, if you have several million users. With Django 1.5+, it's a lot easier to replace the `User` model, so you definitely want to do a custom model, and use DB constraints to handle this. – elo80ka Jul 10 '13 at 21:48
  • It's better to use `email__iexact` otherwise people can create two accounts with the same email address (using different capitalization). – mgalgs May 09 '16 at 04:59
  • This has a race condition - if two users are created at the same time, it may not enforce uniqueness. – Flash Dec 08 '17 at 05:09
  • @Flash: True, if you're using any isolation level other than `SERIALIZABLE`. Like I said above, my preferred solution is to create a custom User model, and use DB constraints to enforce this. Maybe I should update the answer with a warning and that recommendation? – elo80ka Dec 08 '17 at 11:41
41

What about using unique_together in a "different" way? So far it works for me.

class User(AbstractUser):
    ...
    class Meta(object):
        unique_together = ('email',)
eillarra
  • 5,027
  • 1
  • 26
  • 32
21

Simply use below code in models.py of any app

from django.contrib.auth.models import User
User._meta.get_field('email')._unique = True
Nids Barthwal
  • 2,205
  • 20
  • 12
  • 3
    This is the easiest solution to this I've found. There may be time when one wants/needs to create a custom user model; but if the canned Django user model suffices for everything except the desire to make a standard field unique, then there's no need to complicate things. Thanks for the great answer - though you may want to add that migrations need to be performed to alter the field in that database. – Hildy Nov 16 '18 at 20:02
  • Best solution! Just putted into a model.py and it works with validation and so an - THX! – HolgerT Nov 27 '19 at 18:27
  • You saved my day, easy and direct solution – Omar Hafez Oct 11 '20 at 10:56
17

In settings module:

# Fix: username length is too small,email must be unique
from django.contrib.auth.models import User, models
User._meta.local_fields[1].__dict__['max_length'] = 75
User._meta.local_fields[4].__dict__['_unique'] = True
jdavidls
  • 1
  • 1
  • 2
  • this is the only answer that **truly enforces a unique email address** at the database level, **avoids triggering premature related model resolution** (circular imports abound!), and **works correctly for the initial syncdb**. the only problem is that the field is not guaranteed to be at position X, so you must loop thru all fields and check the `name` attribute. – anthonyrisinger Oct 08 '13 at 16:56
  • 4
    `User._meta.get_field('email').__dict__['_unique'] = True` or `User._meta.get_field('email')._unique = True` The two should work – Cherif KAOUA Mar 13 '15 at 19:38
  • `User._meta.get_field('email')._unique = True` This didn't work in my settings (ImportError) but perfectly worked if located in my main `__init__` module – Geoffroy CALA Jul 29 '16 at 17:53
  • need to use sudo when makemigrations or there would be permission denied – LiberiFatali Oct 06 '16 at 04:39
  • 3
    I needed to place this to my `models` module. – Petr Dlouhý Aug 22 '17 at 14:42
  • Adding this in my setting module I'm getting `django.core.exceptions.ImproperlyConfigured: The SECRET_KEY setting must not be empty`. on running server. – Yogesh Tripathi Sep 02 '19 at 08:01
16

It's amazing, but I found a best solution for me!

django-registration have form with checking uniqueness of email field: RegistrationFormUniqueEmail

example of usage here

Deepak Sood
  • 385
  • 5
  • 16
ramusus
  • 7,789
  • 5
  • 38
  • 45
  • 9
    not to be an asshole, but I think you mean "uniqueness", not "unicity". – pseudosudo Feb 10 '12 at 23:26
  • 7
    Link is dead. http://www.copyandwaste.com/posts/view/django-registration-require-a-unique-email-address-for-registration/ and http://stackoverflow.com/questions/2131533/django-registration-force-unique-e-mail for more info – freyley Jul 25 '12 at 17:30
  • 1
    But this will work for registration not for email update – Thomas Gak-Deluen Jan 23 '16 at 14:40
  • that's @tgdn reasonable! but anyway the question and answer to much outdated, since we have custom user model https://docs.djangoproject.com/en/1.9/topics/auth/customizing/#substituting-a-custom-user-model – ramusus Jan 25 '16 at 14:23
8

Your form should look something like this.

def clean_email(self):
    email = self.cleaned_data.get('email')
    username = self.cleaned_data.get('username')
    print User.objects.filter(email=email).count()
    if email and User.objects.filter(email=email).count() > 0:
        raise forms.ValidationError(u'This email address is already registered.')
    return email
Alan Viars
  • 1
  • 1
  • 1
6

To ensure a User, no matter where, be saved with a unique email, add this to your models:

@receiver(pre_save, sender=User)
def User_pre_save(sender, **kwargs):
    email = kwargs['instance'].email
    username = kwargs['instance'].username

    if not email: raise ValidationError("email required")
    if sender.objects.filter(email=email).exclude(username=username).count(): raise ValidationError("email needs to be unique")

Note that this ensures non-blank email too. However, this doesn't do forms validation as would be appropriated, just raises an exception.

zVictor
  • 3,610
  • 3
  • 41
  • 56
  • The fact that the ValidationError that is thrown isn't caught as part of ModelForm validation really means this isn't a usable solution, although it was interesting to learn about signals and pre_save. – IanSR Mar 31 '11 at 01:50
  • Ideally the `pre_save` signalling should be used in conjunction with the form validation mentioned previously. This would help to ensure integrity across the application. – James Feb 14 '14 at 00:42
3

Django has a Full Example on its documentation on how to substitute and use a Custom User Model, so you can add fields and use email as username.

Fernando Freitas Alves
  • 3,709
  • 3
  • 26
  • 45
2

I think that the correct answer would assure that uniqueness check was placed inside the database (and not on the django side). Because due to timing and race conditions you might end with duplicate emails in the database despite having for example pre_save that does proper checks.

If you really need this badly I guess you might try following approach:

  1. Copy User model to your own app, and change field email to be unique.
  2. Register this user model in the admin app (using admin class from django.contrib.auth.admin)
  3. Create your own authentication backend that uses your model instead of django one.
jb.
  • 23,300
  • 18
  • 98
  • 136
2

One possible way to do this is to have a pre-save hook on the User object and reject the save of the email already exists in the table.

Parag
  • 12,093
  • 16
  • 57
  • 75
  • Could you describe your suggestion in more detail, please? If I understand correctly, you are offering to use *pre-save signal* and raise *ValidationError* if email of new User exists in DB? – ramusus Jul 23 '09 at 10:37
  • Where is it described what a pre-save signal should raise or return to affect the in-progress save action? I can't find it in the django docs. Am I being dumb? – Jonathan Hartley Oct 06 '11 at 15:52
2

Add the below function in any of the models.py file. Then run makemigrations and migrate. Tested on Django1.7

def set_email_as_unique():
    """
    Sets the email field as unique=True in auth.User Model
    """
    email_field = dict([(field.name, field) for field in MyUser._meta.fields])["email"]
    setattr(email_field, '_unique', True)

#this is called here so that attribute can be set at the application load time
set_email_as_unique()
Arun
  • 836
  • 1
  • 12
  • 19
2

Since version 1.2 (May 11th, 2015) there has been a way to dynamically import any chosen registration form using the settings option REGISTRATION_FORM.

So, one could use something like this:

REGISTRATION_FORM = 'registration.forms.RegistrationFormUniqueEmail'

This is documented here.

And here's the link to the changelog entry.

bonidjukic
  • 1,461
  • 1
  • 13
  • 19
2

Django does not allow direct editing User object but you can add pre_save signal and achieve unique email. for create signals u can follow https://docs.djangoproject.com/en/2.0/ref/signals/. then add the following to your signals.py

 @receiver(pre_save, sender=User)
def check_email(sender,instance,**kwargs):
    try:
        usr = User.objects.get(email=instance.email)
        if usr.username == instance.username:
            pass
        else:
            raise Exception('EmailExists')
    except User.DoesNotExist:
        pass
  • Django does not allow direct access to User model but we can create signals on User model. https://docs.djangoproject.com/en/2.0/ref/signals/ – nikhil patil Jun 03 '18 at 05:58
2

This method won't make email field unique at the database level, but it's worth trying.

Use a custom validator:

from django.core.exceptions import ValidationError
from django.contrib.auth.models import User

def validate_email_unique(value):
    exists = User.objects.filter(email=value)
    if exists:
        raise ValidationError("Email address %s already exists, must be unique" % value)

Then in forms.py:

from django.contrib.auth.models import User
from django.forms import ModelForm
from main.validators import validate_email_unique

class UserForm(ModelForm):
    #....
    email = forms.CharField(required=True, validators=[validate_email_unique])
    #....
sframe
  • 171
  • 10
  • 4
    The problem with this approach is that it doesn't protect against a race condition in which two `User` objects are trying to be created at the same time. `exists` will return false for both requests (as neither one has been saved), so both forms validate, and the result is two user objects with duplicate emails. When the constraint is at the DB level an integrity error would be thrown. – Tom Jan 09 '17 at 16:59
1

Add somewhere this:

User._meta.get_field_by_name('email')[0]._unique = True     

and then execute SQL similar to this:

ALTER TABLE auth_user ADD UNIQUE (email);
Tomasz Zieliński
  • 16,136
  • 7
  • 59
  • 83
0

The first answer here is working for me when I'm creating new users, but it fails when I try to edit a user, since I am excluding the username from the view. Is there a simple edit for this that will make the check independent of the username field?

I also tried including the username field as a hidden field (since I don't want people to edit it), but that failed too because django was checking for duplicate usernames in the system.

(sorry this is posted as an answer, but I lack the creds to post it as a comment. Not sure I understand Stackoverflow's logic on that.)

mlissner
  • 17,359
  • 18
  • 106
  • 169
  • If you don't want to show username field on form you can make it hidden and always copy value from email to username fields in clean methods or with JS on client's side. It will keep uniqueness for username. – ramusus Apr 09 '10 at 11:20
0

You can use your own custom user model for this purpose. You can use email as username or phone as username , can have more than one attribute.

In your settings.py you need to specify below settings AUTH_USER_MODEL = 'myapp.MyUser'.

Here is the link that can help you . https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#auth-custom-user

user2772346
  • 791
  • 7
  • 7
-2

from an User inherited model, redefine the attribute correctly. It should work, as is it's not usefull to have that in django core because it's simple to do.

amirouche
  • 7,682
  • 6
  • 40
  • 94
-5

I went to \Lib\site-packages\django\contrib\auth\models and in class AbstractUser(AbstractBaseUser, PermissionsMixin): I changed email to be:

email = models.EmailField(_('email address'), **unique=True**, blank=True)

With this if you try to register with email address already present in the database you will get message: User with this Email address already exists.

Box Box Box Box
  • 5,094
  • 10
  • 49
  • 67
Azur
  • 53
  • 4
  • 2
    this is really bad, you should never update site-packages sources like that... will you start patching the code on every server you deploy your code to?! – David M. Oct 23 '16 at 21:26