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'
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:
Backup you DB
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)
Delete all Migrations File(except for __init__.py)
Delete all entry from table django_migrations
Create all migrations file using python manage.py makemigrations
Run fake migrations by python manage.py migrate --fake
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.
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
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'
]
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)
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')
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.
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']
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
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
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")
Related
I have extended the UserCreationForm of Django to add extra fields : Email and Choice field. Now when I am filling those details in the registration form and submitting it. Only the email is stored in the database and not the choice field(i.e status in the code). However when I am viewing the fields of my form in the console the choice field is showing.
But when I am accessing the attribute using the django shell I am getting an error
from django.contrib.auth.models import User
users = User.objects.all()
users[6].email
Output : masterhimanshupoddar#gmail.com # success
users[6].status
Error: AttributeError Traceback (most recent call last)
<ipython-input-7-c9d2701fa919> in <module>()
----> 1 users[6].status
AttributeError: 'User' object has no attribute 'status'
Here is my forms.py
from django.contrib.auth.forms import UserCreationForm
from django import forms
from django.contrib.auth.models import User
class SignUpForm(UserCreationForm):
STATUS_CHOICES = (
(1, ("CEO")),
(2, ("Dev Manager")),
(3, ("Testing Manager")),
(4, ("Developer")),
(5, ("Test Engineer"))
)
email = forms.EmailField(required=True,
label='Email',
error_messages={'exists': 'Oops'})
status = forms.ChoiceField(choices = STATUS_CHOICES, label="Designation", initial='Developer', widget=forms.Select(), required=True)
class Meta:
model = User
fields = ("username", "email", "password1", "password2", "status")
def save(self, commit=True):
user = super(UserCreateForm, self).save(commit=False)
user.email = self.cleaned_data["email"]
user.status = self.cleaned_data["status"]
if commit:
user.save()
return user
Why am I getting error?
It is not enough to add the field to your form, you also have to add it to the model.
For that, you need to create a custom user class in your models.py:
from django.contrib.auth.models import AbstractUser
STATUS_CHOICES = (
(1, ("CEO")),
(2, ("Dev Manager")),
(3, ("Testing Manager")),
(4, ("Developer")),
(5, ("Test Engineer"))
)
class User(AbstractUser):
status forms.ChoiceField(choices = STATUS_CHOICES, verbose_name ="Designation")
Then you have to change the settings AUTH_USER_MODEL to point to your model accordingly. Assuming the app which contains your new custom user model is called my_app, the setting would look like this:
AUTH_USER_MODEL = 'my_app.User'
You also have to make sure that any ForeignKey fields in your model use AUTH_USER_MODEL instead of django.contrib.auth.models.User:
from django.conf import settings
from django.db import models
class Article(models.Model):
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
Also don't forget to create and apply database migrations:
python manage.py makemigrations
python manage.py migrate
Unfortunately, changing the user model mid-project can lead to tricky problems, so if you are still early in your project, it might be easiest to delete and recreate your database.
Refer to your model with get_user_model() in your form:
from django.contrib.auth import get_user_model
class SignUpForm(UserCreationForm):
...
class Meta:
model = get_user_model()
Please refer to the documentation on Substituting a custom User model
I am wondering how I can get a user's location upon sign up using django.
May I have an example in code doing this in a form?
GeoIP likely does what I need but I am confused as to how to implement it.
Here is my current forms.py:
from django import forms
from captcha.fields import ReCaptchaField
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from .models import CustomUser
class UserCreateForm(UserCreationForm):
email = forms.EmailField(required=True)
captcha = ReCaptchaField()
class Meta:
model = CustomUser # this may need to be changed to customuser
fields = ("username", "email", "password1", "password2")
def save(self, commit=True):
user = super(UserCreateForm, self).save(commit=False)
user.email = self.cleaned_data["email"]
if commit:
user.save()
return user
I'm learning Django and need some help.
I need to include an extra boolean field in my User Model (auth_user table in the db I believe) and have it be displayed in the admin interface when managing users, like the staff status field in this image...
127:0.0.1:8000/admin/user :
and...
127.0.0.1:8000/admin/auth/user/2/change/ :
I'm unsure on how to approach this. I understand I'll have to extend the AbstractUser model and then perhaps manually add the field into the db, but then how do I update the new field for the view, form and templates for the admin interface? Will I have to rewrite the django admin source code for all of these or is there an simpler way?
This is how I did it:
Note: This should be done when you are creating a new project.
Adding field(s) to User model :-
models.py:
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
gender = models.BooleanField(default=True) # True for male and False for female
# you can add more fields here.
Overriding the default User model :-
settings.py:
# the example_app is an app in which models.py is defined
AUTH_USER_MODEL = 'example_app.User'
Displaying the model on admin page :-
admin.py:
from django.contrib import admin
from .models import User
admin.site.register(User)
The best way is to create new Model with User OneToOneField. e.g
class UserProfile(models.Model):
user = models.OneToOneField(User)
phone = models.CharField(max_length=256, blank=True, null=True)
gender = models.CharField(
max_length=1, choices=(('m', _('Male')), ('f', _('Female'))),
blank=True, null=True)
You can play with django admin either in User Model or UserProfile Model and can display the fields in Admin accordingly
You have two options, they are:
Extend the existing User model by adding another model and linking it to the User model using one-to-one relation. See here.
Write your own user model and use it, which would be difficult for a newbie. See here.
#managers.py Create new file.
from django.contrib.auth.base_user import BaseUserManager
from django.utils.translation import ugettext_lazy as _
class CustomUserManager(BaseUserManager):
def create_user(self, email, password, **extra_fields):
if not email:
raise ValueError(_('The Email must be set'))
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save()
return user
def create_superuser(self, email, password, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_active', True)
if extra_fields.get('is_staff') is not True:
raise ValueError(_('Superuser must have is_staff=True.'))
if extra_fields.get('is_superuser') is not True:
raise ValueError(_('Superuser must have is_superuser=True.'))
return self.create_user(email, password, **extra_fields)
#models.py Create your models here.
from django.db import models
from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from django.utils.translation import gettext_lazy as _
from .managers import CustomUserManager
class User(AbstractBaseUser,PermissionsMixin):
first_name =models.CharField(max_length=250)
email = models.EmailField(_('email address'), unique=True)
mobile =models.CharField(max_length=10)
status = models.BooleanField(default=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
object =CustomUserManager()
# add more your fields
#admin.py
from django.contrib import admin
from .models import User
#admin.register(User)
class UserAdmin(admin.ModelAdmin):
list_display = ('email','mobile','password')
#setting.py
AUTH_USER_MODEL = 'users.User'
# run command
python manage.py makemigrations
python manage.py migrate
I'm using a custom sign up form with django-allauth.
settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'project.userprofile.form.UserSignupForm'
form.py
from django import forms
from models import UserProfile
class UserSignupForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('mobile_number',)
models.py
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
mobile_number = models.CharField(max_length=30, blank=True, null=True)
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
The User and the UserProfile objects are created, however the UserProfile isn't associated with any User object. It's late and I'm probably missing something silly, right?
UPDATE: As Kevin pointed out, the solution was to add the save method in the form.py. This is how it looks now:
from django import forms
from django.contrib.auth.models import User
from models import UserProfile
class UserSignupForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('mobile_number',)
def save(self, user):
profile = UserProfile(user=user)
profile.mobile_number = self.cleaned_data['mobile_number']
profile.save()
The documentation says:
[ACCOUNT_SIGNUP_FORM_CLASS] should implement a ‘save’ method, accepting the newly signed up user as its only parameter.
It looks like you haven't provided such a method, so the user never gets connected to the profile. And I think you're not seeing an error because ModelForm has a save(commit=True) method that happens to match this signature, even though it doesn't do what you want.
I have django custom user model MyUser with one extra field:
# models.py
from django.contrib.auth.models import AbstractUser
class MyUser(AbstractUser):
age = models.PositiveIntegerField(_("age"))
# settings.py
AUTH_USER_MODEL = "web.MyUser"
I also have according to these instructions custom all-auth Signup form class:
# forms.py
class SignupForm(forms.Form):
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
age = forms.IntegerField(max_value=100)
class Meta:
model = MyUser
def save(self, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.age = self.cleaned_data['age']
user.save()
# settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'web.forms.SignupForm'
After submitting SignupForm (field for property MyUser.age is rendered corectly), I get this error:
IntegrityError at /accounts/signup/
(1048, "Column 'age' cannot be null")
What is the proper way to store Custom user model?
django-allauth: 0.12.0; django: 1.5.1; Python 2.7.2
Though it is a bit late but in case it helps someone.
You need to create your own Custom AccountAdapter by subclassing DefaultAccountAdapter and setting the
class UserAccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=True):
"""
This is called when saving user via allauth registration.
We override this to set additional data on user object.
"""
# Do not persist the user yet so we pass commit=False
# (last argument)
user = super(UserAccountAdapter, self).save_user(request, user, form, commit=False)
user.age = form.cleaned_data.get('age')
user.save()
and you also need to define the following in settings:
ACCOUNT_ADAPTER = 'api.adapter.UserAccountAdapter'
This is also useful, if you have a custom SignupForm to create other models during user registration and you need to make an atomic transaction that would prevent any data from saving to the database unless all of them succeed.
The DefaultAdapter for django-allauth saves the user, so if you have an error in the save method of your custom SignupForm the user would still be persisted to the database.
So for anyone facing this issue, your CustomAdpater would look like this
class UserAccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=False):
"""
This is called when saving user via allauth registration.
We override this to set additional data on user object.
"""
# Do not persist the user yet so we pass commit=False
# (last argument)
user = super(UserAccountAdapter, self).save_user(request, user, form, commit=commit)
user.age = form.cleaned_data.get('age')
# user.save() This would be called later in your custom SignupForm
Then you can decorate your custom SignupForm's with #transaction.atomic
#transaction.atomic
def save(self, request, user):
user.save() #save the user object first so you can use it for relationships
...
Side note
With Django 1.5 custom user model, the best practice is to use the get_user_model function:
from django.contrib.auth import get_user_model
# forms.py
class SignupForm(forms.Form):
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
age = forms.IntegerField(max_value=100)
class Meta:
model = get_user_model() # use this function for swapping user model
def save(self, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.age = self.cleaned_data['age']
user.save()
# settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'web.forms.SignupForm'
Maybe it's not related, but I thought it would be worth noticing.
i think you should define fields property in class Meta in SignupForm and set list of fields that contains age, like this :
class SignupForm(forms.Form):
...
class Meta:
model = MyUser
fields = ['first_name', 'last_name', 'age']
and if it's not worked, look at
this