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})
Related
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
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)
I'm trying to develop a Facebook social authentication feature on an application that uses a custom Django user model and django-rest-framework-social-oauth2 as the social authentication package. My custom user model is called 'Account' and it inherits from the AbstractBaseUser class. The Account model is shown below:
class Account(AbstractBaseUser):
# Account model fields:
email = models.EmailField(verbose_name='email', max_length=60, unique=True)
username = models.CharField(max_length=30, unique=True)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
date_joined = models.DateTimeField(verbose_name='date joined', auto_now_add=True)
last_login = models.DateTimeField(verbose_name='last login', auto_now=True)
is_admin = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
# The user will log in with their email instead of username:
USERNAME_FIELD = 'email'
# Required fields when registering, other than the email:
REQUIRED_FIELDS = ['username', 'first_name', 'last_name']
# Telling the Account object how to use the account manager:
objects = MyAccountManager()
The function that handles creating a new user is called 'create_user' and is defined within my custom written MyAccountManager class which extends the Django BaseUserManager class. This is given below:
class MyAccountManager(BaseUserManager):
def create_user(self, email, username, first_name, last_name, password=None):
# Checking to see if correct function parameters have been passed in:
if not email:
raise ValueError('Users must have an email address')
if not username:
raise ValueError('Users must have a username')
if not first_name:
raise ValueError('Users must have a first name')
if not last_name:
raise ValueError('Users must have a last name')
# Creating the new user:
user = self.model(
email = self.normalize_email(email),
username = username,
first_name = first_name,
last_name = last_name,
)
user.set_password(password)
user.save(using = self._db)
return user
I've set up a working django-rest-framework-social-oauth2 url for creating a new user with a Facebook account. The relevant Facebook configuration in the Django settings.py file is shown below:
SOCIAL_AUTH_FACEBOOK_KEY = config('SOCIAL_AUTH_FACEBOOK_KEY')
SOCIAL_AUTH_FACEBOOK_SECRET = config('SOCIAL_AUTH_FACEBOOK_SECRET')
SOCIAL_AUTH_FACEBOOK_SCOPE = ['email']
SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = { 'fields': 'id, name, email' }
The issue that I've been having is the following:
When the create_user function is called for a user that is using Facebook social login, the parameters email, first_name and last_name, that are required in the create_user function are not being provided by Facebook and I'm getting the error message shown in the image. The error message states the following:
create_user() missing 2 required positional arguments: 'first_name' and 'last_name'
Error Message from Django
Does anyone know how I would be able to access these additional parameters (email, first name, last name) from Facebook so that the correct parameters are passed into the create_user function?
Further Information
On implementing the pipeline suggestion I am still left with the same issue whereby the custom create_user function is missing both the first_name and last_name parameters. I think the reason that this occurring is due to the suggested pipeline cleanup_social_account function being called after create_user, where in my case both first_name and last_name are required fields, and as such a user object cannot be created in the database if they are not provided at the time the create_user function is called.
I am receiving this error due to the following function in the suggested custom pipeline:
social_core.pipeline.user.create_user
The code for this function in the social_core installed library is the following:
def create_user(strategy, details, backend, user=None, *args, **kwargs):
if user:
return {'is_new': False}
fields = dict((name, kwargs.get(name, details.get(name)))
for name in backend.setting('USER_FIELDS', USER_FIELDS))
if not fields:
return
return {
'is_new': True,
'user': strategy.create_user(**fields)
}
The details parameter passed into the above function contains the values that I need (first_name and last_name). However, they are not actually being added into the fields variable when it is created. The fields variable is shown above and is defined by the following:
fields = dict((name, kwargs.get(name, details.get(name)))
for name in backend.setting('USER_FIELDS', USER_FIELDS))
In summary:
The issue appears to be that first_name and last_name are not appearing within backend.settings('USER_FIELDS', USER_FIELDS), and therefore are not being added to the fields variable, and as such are not being passed into strategy.create_user(**fields).
So social_auth auto-populates those fields for me when I just get name and email from Facebook. It knows to bring in first_name and last_name. Since it doesn't seem to be working for you, you can create a custom pipeline function.
settings.py:
SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.social_auth.social_details',
'social_core.pipeline.social_auth.social_uid',
'social_core.pipeline.social_auth.auth_allowed',
'social_core.pipeline.social_auth.social_user',
'social_core.pipeline.user.get_username',
'social_core.pipeline.social_auth.associate_by_email',
'social_core.pipeline.user.create_user',
# YOUR CUSTOM PIPELINE FUNCTION HERE. I CREATED A FILE/MODULE
# NAMED pipeline.py AND STUCK IT IN THERE. MAKE SURE TO PUT THIS
# AFTER CREATE USER.
'path.to.custom.pipeline.cleanup_social_account',
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
'social_core.pipeline.user.user_details',
)
pipeline.py:
def cleanup_social_account(backend, uid, user=None, *args, **kwargs):
"""
3rd party: python-social-auth.
Social auth pipeline to cleanup the user's data. Must be placed
after 'social_core.pipeline.user.create_user'.
"""
# Check if the user object exists and a new account was just created.
if user and kwargs.get('is_new', False):
*** THIS IS UNTESTED, BUT FACEBOOK'S DATA SHOULD COME INTO THE DETAILS KWARG ***
user.first_name = kwargs['details']['first_name']
user.last_name = kwargs['details']['last_name']
user.save()
return {'user': user}
Just add SOCIAL_AUTH_GOOGLE_OAUTH2_USER_FIELDS = ['username', 'email', 'first_name', 'last_name'] in your settings.py file and these fields should be added to **kwargs in you create_user method.
Sample Usage
In settings.py file add this
SOCIAL_AUTH_GOOGLE_OAUTH2_USER_FIELDS = ['first_name', 'last_name', 'email']
In your UserManager class modify create_user like below
def create_user(self, password=None, **kwargs):
"""Create and return a `User` with an email, username and password."""
first_name = kwargs.get('first_name')
last_name = kwargs.get('last_name')
email = kwargs.get('email')
if first_name is None or last_name is None:
raise TypeError(f"Invalid FirstName: {first_name}, LastName: {last_name}, Email: {email}")
if email is None:
raise TypeError("Users must have an email address.")
user = self.model(
first_name=first_name,
last_name=last_name,
email=self.normalize_email(email),
)
user.set_password(password)
user.save()
return user
I'm working on a Django system where there are four levels of users:
1. Basic (access only)
2. Admin (can update/change/delete)
3. Gatekeeper (can only create Admin users, cannot update/change/delete)
4. Developer (true superuser)
I think I have the permissions somewhat figured out:
from django.conf import settings
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser
)
from django.core.validators import RegexValidator
from django.db import models
from django.db.models.signals import post_save
class CustomUserManager(BaseUserManager):
"""Customer User."""
def create_user(self, email, password=None):
"""Creates and saves a user."""
if not email:
raise ValueError('Users must have an email address')
user = self.model(
email=self.normalize_email(email),
)
user.set_password(password)
user.save(using=self._db)
return user
def create_admin(self, email):
"""Creates and saves an admin user with a temporary password."""
user = self.create_user(
email,
password=BaseUserManager.make_random_password(self, 12)
)
user.is_admin = True
user.save(using=self.db)
return user
def create_gatekeeper(self, email, password):
"""Creates and saves a gatekeeper."""
user = self.create_user(
email,
password=password,
)
user.is_admin = True
user.is_gatekeeper = True
user.save(using=self.db)
return user
def create_superuser(self, email, password):
"""Creates and saves a superuser."""
user = self.create_user(
email,
password=password,
)
user.is_admin = True
user.is_gatekeeper = True
user.is_developer = True
user.save(using=self._db)
return user
class CustomUser(AbstractBaseUser):
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True,
)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
is_gatekeeper = models.BooleanField(default=False)
is_developer = models.BooleanField(default=False)
objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
# def __str__(self):
# return self.email
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
# Does the user have a specific permission?
# Simplest possible answer: Yes, always
return True
def has_module_perms(self, app_label):
# "Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
return True
class Meta:
db_table = 'customuser'
verbose_name = 'CustomUser'
However, I'm not making the connection with how to create gatekeepers and admins. I know I can do it via the command line, but I want
a) a form where developers can create gatekeepers
b) a form where gatekeepers can create admins
Since our administrative site will need to be heavily customized, we will not be using the Django Admin and will likely be building our own functionality. How would I go about calling something like create_gatekeeper inside of a Django form? Would subclassing the Django Admin pages and customizing those be a better way to go?
You probabily should work with Groups and add permissions to those groups;;; but in 3 of yours Actors you can use django builtin attributes from User models...
User: You regular user just create as User class with anything else as is_something...
Admin You can use attribute is_staff that attribute comes from User Models, and allow your user to access django admin...
Developer You can create him as Super User, so all permissions is self added to your models
The thing is about gatekeeper, so if you start to create flags as is_admin, is_gatekeeper and etc... you will start to handle multiple attributes, and this is bad ideia, so when you are working with groups you can create User Group, Developer Group (since they are super admin you dont really need to do that), Admin Group (add each permission you want to give to this groups [Eg. Blog Models you can give to him blog add_blog, change_blog and delete_blog, but you can add your custom too]) same as other groups...
Eg.
# List of my Permissions that i want to add to my groups
PERMISSIONS = {
STUDENT : ['add_student', 'change_student',
'delete_student', 'editpreview_student'],
PROJECT: ['add_project', 'change_project', 'delete_project', 'editpreview_project'],
}
# Creating 2 Groups
grupo_admin, created = Group.objects.get_or_create(name='admin')
grupo_teachers, created = Group.objects.get_or_create(name='teacher')
for func_perm in PERMISSIONS [STUDENT]:
perm = Permission.objects.get(codename=func_perm)
grupo_admin.permissions.add(perm) # Adding Student Permission to my Admin Group
grupo_teachers.permissions.add(perm) # Adding Student Permission to my Teachers Group
for func_perm in PERMISSOES[PROJECT]:
perm = Permission.objects.get(codename=func_perm)
grupo_admin.permissions.add(perm) # Adding Project Permission only to my Admin Group
In your views you can check the permission like that
#user_passes_test(lambda u: u.has_perm('myapp.permission_code'))
def some_view(request):
# ...
And your HTML you can check like that
{% if perms.student.change_student %}
<li>
<a href="/admin/student/student/">
<i class="fa fa-graduation-cap" aria-hidden="true"></i>
<span>Students</span>
</a>
</li>
{% endif %}
I am trying to confirm user password in a secure way using PasswordInput() but Django seem to only allow for one PasswordInput() per form. What is the best way to implement confirm_password below:
class VerifyAccountsForm(forms.ModelForm):
username = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Username'}), label="")
password = forms.CharField(widget=forms.PasswordInput(attrs={'placeholder': 'Password'}), label="")
password_verify = forms.CharField(widget=forms.PasswordInput(attrs={'placeholder': 'Verify password'}), label="")
def clean_password(self):
print self.cleaned_data
password1 = self.cleaned_data.get('password')
password2 = self.cleaned_data.get('password_verify')
if not password2:
raise forms.ValidationError("You must confirm your password")
if password1 != password2:
raise forms.ValidationError(_('Your passwords do not match'), code='invalid')
return password2
The output of clean_data is only the username and password, no password_verify.
if you use django's own usercreationform, you can have both fields, here is how I used it
form's clean_password2() function compares two password inputs and validates them too, and returns the second password.
I don't know what gives you the idea that Django only allows one password input per form. You can have as many as you like, Django won't stop you.
Note that your current code is called on validation of password, but returns the value of password_verify.
However if you're getting something you don't expect from cleaned_data, you should post the code of the clean method.
Change the def clean_password(self): to def clean_password2(self): in
the forms.py and in HTML registration form(registraton.html) write {{ form.password2.errors.as_text }} just above the password input field.