I had the following custom user model:
class CustomUser(AbstractUser):
"""User model."""
username = None
email = models.EmailField(unique=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = UserManager()
I was under the impression that I would at least get some errors if I tried to create a CustomUser with an invalid email. However, I have the following line it my unit tests:
CustomUser.objects.create_user(email='dave') # creates user!
Shouldn't the above make django throw an error, since 'dave' is clearly not an email? I know I can write a validator, but shouldn't there be already no need to do so?
Here's the UserManager:
class UserManager(BaseUserManager):
"""Define a model manager for User model with no username field."""
use_in_migrations = True
def _create_user(self, email, password, **extra_fields):
"""Create and save a User with the given email and password."""
if not email:
raise ValueError('The given 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_user(self, email, password=None, **extra_fields):
"""Create and save a regular User with the given email and password."""
extra_fields.setdefault('is_staff', False)
extra_fields.setdefault('is_superuser', False)
return self._create_user(email, password, **extra_fields)
Update: CustomUser(email='dave').clean_fields(), however, will throw the following error as expected: (but I am still curious what's up with my original question)
ValidationError: {'password': ['This field cannot be blank.'], 'email': ['Enter a valid email address.']}
Django does not run validators automatically outside of ModelForm's.
So when you do this:
CustomUser.objects.create_user(email='dave') # creates user!
The EmailField's validators (i.e. EmailValidator) will not be run. An error will only be thrown if the database has an issue with the value you are trying to assign. In this case it won't, because under the hood an EmailField is identical to a CharField and you're passing it a string ('dave').
How validators are run (Django docs):
Note that validators will not be run automatically when you save a model, but if you are using a ModelForm, it will run your validators on any fields that are included in your form.
I can understand the confusion. Your intuition is telling you that something defined in the models will be enforced by the ORM regardless of how it's represented in the database. The reality is that a lot of the Model field options are only used in forms (blank, choices, editable, help_text etc.).
From the source, the class AbstractUser doesn't define a validator for the emailfield, but one for username.
So if you want a validation from the model's field, you have to do it yourself.
(and overwritte the username if you want to change it)
Related
P.S: I'm new to Django, hope anyone can help me out:-)
I had created a customer user model to override the already existing one Django provides, but I wanted to make a lot of changes on it including changing the name of that custom model, like completely starting over in another app but in the same project, looks like I'm getting errors while trying to do migrations.
Thought that if I changed the database and performed migrations it would work but it didn't, they say this:
ERRORS:
user.UserAccountModel.groups: (fields.E304) Reverse accessor 'Group.user_set' for 'user.UserAccountModel.groups' clashes with reverse accessor for 'userAccount.UserModel.groups'.
HINT: Add or change a related_name argument to the definition for 'user.UserAccountModel.groups' or 'userAccount.UserModel.groups'.
user.UserAccountModel.user_permissions: (fields.E304) Reverse accessor 'Permission.user_set' for 'user.UserAccountModel.user_permissions' clashes with reverse accessor for 'userAccount.UserModel.user_permissions'.
HINT: Add or change a related_name argument to the definition for 'user.UserAccountModel.user_permissions' or 'userAccount.UserModel.user_permissions'.
userAccount.UserModel.groups: (fields.E304) Reverse accessor 'Group.user_set' for 'userAccount.UserModel.groups' clashes with reverse accessor for 'user.UserAccountModel.groups'.
HINT: Add or change a related_name argument to the definition for 'userAccount.UserModel.groups' or 'user.UserAccountModel.groups'.
userAccount.UserModel.user_permissions: (fields.E304) Reverse accessor 'Permission.user_set' for 'userAccount.UserModel.user_permissions' clashes with reverse accessor for 'user.UserAccountModel.user_permissions'.
HINT: Add or change a related_name argument to the definition for 'userAccount.UserModel.user_permissions' or 'user.UserAccountModel.user_permissions'.
Helper: My first custom model was 'UserModel' in userAccount app and now i changed it to 'UserAccountModel' in user app
Is it maybe not allowed to change the Custom user model's name in django once you have already made migrations and established as you default user model?
Here is my models.py code:
from django.db import models
from django.contrib.auth.models import AbstractBaseUser,PermissionsMixin
from django.contrib.auth.base_user import BaseUserManager
from django.utils.translation import gettext_lazy as _
class UserManager(BaseUserManager):
def create_user(self, email, password, **extra_fields):
if email is None:
raise ValueError(_('Email field is required'))
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):
"""
Create and save a SuperUser with the given email and password.
"""
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.'))
if extra_fields.get('is_active') is not True:
raise ValueError(_('Superuser must have is_active=True.'))
return self.create_user(email, password, **extra_fields)
class UserAccountModel(AbstractBaseUser,PermissionsMixin):
USER_ROLE=(
('admin','Admin'),
('talent','Talent'),
('employer','Employer'),
)
# Talent, employer
email = models.EmailField(_('email address'),unique=True)
first_name = models.CharField(_('first name'), max_length=100)
last_name = models.CharField(_('last name'), max_length=100)
role = models.CharField(choices=USER_ROLE,max_length=10,default=USER_ROLE[2][1])
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
is_verified = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
USERNAME_FIELD='email'
# REQUIRED_FIELDS=[]
objects = UserManager()
def __str__ (self):
return self.email
def tokens(self):
return ''
First of all, I've created a User model, which inherits from AbstractUser. I use another model which is called CustomUserManager (which inherits from UserManager). In CustomUserManager, i rewrote the create_user method, and the create_superuser method. When i create a simple active user, it works fine (i can connect the user). When i create a superuser, it works fine ("Superuser created successfully") BUT when i go to the django administration pannel, and i enter the correct information of the superuser, it doesn't work and display the following message : "Please enter the correct email and password for a staff account. Note that both fields may be case-sensitive. "
Here's my models.py :
class CustomUserManager(UserManager):
def _create_user(self,email,password,first_name,last_name,**extra_fields):
email = self.normalize_email(email)
user = User(email=email)
user.is_active=True
user.password = make_password(password)
user.save(using=self._db)
return user
def create_user(self,email,password,first_name,last_name,**extra_fields):
return self._create_user(email,password,first_name,last_name,**extra_fields)
def create_superuser(self,email,password,first_name,last_name,**extra_fields):
"""
Create and save a SuperUser with the given email and password.
"""
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,first_name, last_name, **extra_fields)
class User(AbstractUser):
username = None
email = models.EmailField(unique=True, blank=False)
first_name = models.CharField(blank=False,max_length=50)
last_name = models.CharField(blank=False,max_length=50)
REQUIRED_FIELDS=["first_name","last_name"]
USERNAME_FIELD = 'email'
objects = CustomUserManager()
EDIT : Thanks to Max Koniev, I forgot to say, but i already had AUTH_USER_MODEL set to my User model in my application private_portal : AUTH_USER_MODEL = 'private_portal.User'
Be sure you set AUTH_USER_MODEL in settings.py to your new User model
def create_superuser(self, username, email, password=None):
user = self.create_user(
username, email, password=password
)
user.is_admin = True
user.is_staff = True
user.active = True
user.save(using=self._db)
return user
I have made a custom user model. When I call createsuperuser, if the username I entered is already in the database, it will prompt
'Error: That username is already taken.'
But if I entered a email address which already exists in the database, it will not prompt the email has been taken error. Instead, it let me pass through and gave me the following error after I entered the password:
django.db.utils.IntegrityError: UNIQUE constraint failed: users_customuser.email
Is there a way to make it also check the email address like the way it checks the username?
def create_superuser(self, username, email, password=None):
duplicate = self.filter(email=email) # I'm assuming `self` is User.objects
if len(duplicate) != 0:
return "Duplicate email" # Customize your error handling logic here
user = self.create_user(
username, email, password=password
)
user.is_admin = True
user.is_staff = True
user.active = True
user.save(using=self._db)
return user
The uniqueness is being enforced by a constraint set at the database (probably because unique=True in the models.py), so that's where the error is being raised and Django doesn't realize it until the transaction is attempted.
The email field doesn't necessarily have to be unique if you don't want it to be, though it is a common practice.
edit: Instead of a naive email=email, you could do something more robust (but completely optional) like this:
email_field = self.model.get_email_field_name()
duplicate = self.filter(**{email_field: email})
I have a Signup form Where I want to make data of email and mobile Number Field to be Unique....
class SignUpForm(UserCreationForm):
email = forms.EmailField(max_length=254, help_text='Required. Inform a valid email address.', unique=True)
mobile_no = forms.CharField(validators=[max_length=17, initial='+91', unique=True)
I am Currently using unique=True but it raise Error as...
TypeError: __init__() got an unexpected keyword argument 'unique'
Easiest and fastest way(both for you and server) is to implement it in your model by setting unique=True.
If you want it in form anyway you need to override clean
Cleaning email:
class SignUpForm(UserCreationForm):
...
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise ValidationError("Email already exists")
return email
Now form.is_valid() will throw error if an user account with given email already exists.
I think you can figure out how to do same thing for mobile number now.
Hi Here's a snippet from my admin.py
#admin.py
class UserForm(forms.ModelForm):
class Meta:
model = User
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError("This email already used")
return email
class UserAdmin(admin.ModelAdmin):
form = UserForm
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
I use this to check that a new user cannot be created with an email address already used. The problem is that when I edit an existing user the validation check fails, because there is a user with that mail address, but that's OK because it's the one I'm editing.
How can I tell the form validation to ignore the match against the current user?
Exclude the current instance from your query:
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exclude(pk=self.instance.pk).exists():
raise forms.ValidationError("This email already used")
return email
It's much more better to validate uniqueness using unique on model field.
You can use custom User model with unique email constraint.
Look at this for more info about implementing unique validation on your own https://stackoverflow.com/a/1560617/527064