I am using Django as backend for my mobile app. When creating users info I need the following info:name, DoB and firebaseUID. Please note that I do not want their email or password. But I want to create superusers who act as admins and want the superusers to be created using email and password. So I am not sure if I have to:
Create a custom user by extending AbstractBaseUser and have a user manager attached to that custom user OR
Create a custom user by extending AbstractBaseUser and then create another Django user class which is only for superusers.
I implemented option 1 with the below code, but I get the following error: "TypeError: MyUserManager.create_superuser() missing 1 required positional argument: 'email'" Also when I am creating superuser, Django is asking for name, firebaseUID and DOB fields although I want to create superusers only email and password fields.
from django.contrib.auth.models import AbstractBaseUser
from django.db import models
from django.utils import timezone
class MyUserManager(models.Manager):
def _create_user(self, email, password, **extra_fields):
"""
Creates and saves 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(using=self._db)
return user
def create_user(self, email=None, password=None, **extra_fields):
extra_fields.setdefault('is_staff', False)
extra_fields.setdefault('is_superuser', False)
return self._create_user(email, password, **extra_fields)
def create_superuser(self, email, password, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', 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)
def get_by_natural_key(self, email):
return self.get(email=email)
class CustomUser(AbstractBaseUser):
name = models.CharField(null=True, blank=True, max_length=100)
userName = models.CharField(
null=True, blank=True, max_length=100, unique=True)
firebaseUid = models.CharField(
null=True, blank=True, max_length=100, unique=True)
dob = models.DateField(max_length=8)
created_at = models.DateTimeField(default=timezone.now)
email = models.EmailField(unique=True) #used only for createing super user at this point
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
# countryCode = models.CharField(null=True, blank=True, max_length=5)
USERNAME_FIELD = 'userName'
REQUIRED_FIELDS = ['name', 'firebaseUid', 'dob']
objects = MyUserManager()
def __str__(self):
return self.userName
def get_fBaseUid_name(self):
return self.firebaseUid
def has_perm(self, perm, obj=None):
return self.is_superuser
def has_module_perms(self, app_label):
return self.is_superuser
Please let me know if you need any other info. Thank you in advance!
Edit: I already added the following in my settings.py file
AUTH_USER_MODEL = "accounts.CustomUser"
AUTHENTICATION_BACKENDS = ['accounts.backends.FirebaseBackend']
Tl;dr: I want regular users to be created using different fields than creating a superuser.
Has your User model been specified in your settings.py?
If it hasn't, specify it
AUTH_USER_MODEL = "yourapp.UserModel"
Also, in your CustomUser model
Do this
# For creating a superuser using Django
REQUIRED_FIELDS = []
And if you want to create users with "email" and "password", not "username" and "password"
Do this
USERNAME_FIELD = "email"
You can use this code for your create_superuser as well
def create_superuser(self, email, password=None):
if password is None:
raise TypeError("Password should not be none")
user = self.create_user(email, password)
user.is_superuser = True
user.is_staff = True
user.save()
return user
I hope that helps
I am using jwt.io on my DRF project. As you know jwt.io has already a Login API view called 'TokenObtainPairView' and it requires 2 fields: username and password. But in our project, we want users to log in with their email instead of their username. I handle this with the following code:
class LoginAPIView(TokenObtainPairView):
def post(self, request, *args, **kwargs):
email=request.data['email']
request.POST._mutable = True
profile=ProfileModel.objects.get(email=email)
request.data['username']=profile.username
request.POST._mutable = False
return super().post(request, *args, **kwargs)
It works but on my swagger when front-end devs check the endpoint they see that the endpoint requires 2 fields: username and password. But I want them to see required fields such as email and password.
here is the how my endpoint look like
Is there any way to change its required fields?
I think if you don't need the username you can define your own user model like this.
The USERNAME_FIELD = 'email' on user model says to Django that use the email as username.
Don't forget change settings.py to use your own user model adding this const: AUTH_USER_MODEL = 'users.User' where users is the app name and User is the model name.
from django.contrib.auth.models import (
AbstractBaseUser,
BaseUserManager,
PermissionsMixin
)
class UserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
"""Creates and saves a new user"""
if not email:
raise ValueError('Users must have an email address')
user = self.model(email=self.normalize_email(email), **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password):
if not email:
raise ValueError('Users must have an email address')
if not password:
raise ValueError('Users must have a password')
"""Creates and saves a new super user"""
user = self.create_user(email, password)
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class User(AbstractBaseUser, PermissionsMixin):
"""Custom user model that suppors using email instead of username"""
email = models.EmailField(max_length=255, unique=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
objects = UserManager()
USERNAME_FIELD = 'email'
By default TokenObtainPairSerializer uses the default username field defined by the model, but in case you need to change the behavior without changing the model you can override the token serializer to use the preferred field, i.e.:
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
username_field = 'email'
class LoginAPIView(TokenObtainPairView):
_serializer_class = CustomTokenObtainPairSerializer
I complete makemigrations and migrate then create a superuser. After this
http://127.0.0.1:8000/admin i try to log in then show this error
Please enter the correct Email and password for a staff account. Note that both fields may be case-sensitive
seetings.py
AUTH_USER_MODEL = 'user.CustomUser'
**models.py**
from django.db import models
from django.contrib.auth.models import AbstractUser,BaseUserManager
class CustomUserManager(BaseUserManager):
"""
Creates and saves a User with the given email, date of
birth and password.
"""
def create_user(self, email, password=None):
if not email:
raise ValueError('User must have an email id')
user=self.model(
email=self.normalize_email(email)
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password=None):
user=self.create_user(
email,
password=password
)
user.is_admin = True
user.save(using=self._db)
return user
class CustomUser(AbstractUser):
#only add email field for login
email = models.EmailField(
verbose_name='Email',
max_length=50,
unique=True
)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
def __str__(self):
return self.email
***admin.py***
from django.contrib import admin
from django.contrib.auth import get_user_model
user=get_user_model()
admin.site.register(user)
i had this problem.
you should define is_staff=True in create_superuser function and you should pass **kwargs as argument in this function.
i hope your problem solve.
I am implementing user authentication with django-rest_framework_simple-jwt with custom user,
My models.py:
class UserManager(BaseUserManager):
def create_user(self, email, username, password, alias=None):
user = self.model(
email = self.normalize_email(email),
username = username,)
user.set_password(password)
user.save()
return user
def create_superuser(self, email, username, password):
self.create_user(email, username, password)
user.is_staff()
user.is_superuser = True
user.save()
return user
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(null=False, unique=True)
username = models.CharField(max_length=25, unique=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["username",]
So I am implementing restframework simple-jwt authentication,my settings .py is as follows as:
REST_FRAMEWORK={
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
]}
my urls.py:
urlpatterns = [
url(r'^api/token/$', TokenObtainPairView.as_view(), name='token_obtain_pair'),
url(r'^api/token/refresh/$', TokenRefreshView.as_view(), name='token_refresh'),]
on login process, it returns error that "detail": "No active account found with the given credentials" all my users were active. I have no clue to sort this out, I need help.Thanks in advance.
Ensure your password is being hashed before it is stored in your db. I ran into the same problem and discovered my passwords were being stored in plain text. Adding the following to my UserSerializer solved the issue
from django.contrib.auth.hashers import make_password
def validate_password(self, value: str) -> str:
"""
Hash value passed by user.
:param value: password of a user
:return: a hashed version of the password
"""
return make_password(value)
Either you did not create a superuser for your Django application or you are provided the wrong credentials for authentication
Did you remember to set in settings:
AUTH_USER_MODEL = 'your_app_name.User'
Also make sure, is_active = True for the user object that you are saving in your serializer, because
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['fullname', 'username', 'email', 'password']
def create(self, validated_data):
password = validated_data.pop('password', None)
instance = self.Meta.model(**validated_data)
# Adding the below line made it work for me.
instance.is_active = True
if password is not None:
# Set password does the hash, so you don't need to call make_password
instance.set_password(password)
instance.save()
return instance
Note ( As per docs )
The login_required decorator does NOT check the is_active flag on a user, but the default AUTHENTICATION_BACKENDS reject inactive users.
It seems my error was being caused by a write_only parameter on my password field
class RegisterSerializer(serializers.ModelSerializer):
password = serializers.CharField(
max_length=68, min_length=6, write_only=True)
class Meta:
model = User
fields = ['email', 'username', 'password']
Removed it:
class RegisterSerializer(serializers.ModelSerializer):
password = serializers.CharField(
max_length=68, min_length=6)
class Meta:
model = User
fields = ['email', 'username', 'password']
and then it was all sunshine and rainbows after that :-)
try this
from django.contrib.auth.hashers import make_password
class UserManager(BaseUserManager):
def create_user(self, email, username, password, alias=None):
user = self.model(
email = self.normalize_email(email),
username = username,)
user.set_password(make_password(password))
user.save()
return user
def create_superuser(self, email, username, password):
self.create_user(email, username, password)
user.is_staff()
user.is_superuser = True
user.save()
return user
Downgraded manually from PyJWT==2.0.0 to PyJWT==1.7.1 and solved our problem
pip install PyJWT==1.7.1
We are using djangorestframework==3.12.1 and djangorestframework-simplejwt==4.4.0 on our requirements.txt and that gave us automatically the 2.0.0 version dependency.
In my opinion, there is a problem where an email address and username is provided for the serializer, but an email is expected as a username for authentication.
I had the same error too. I also made some kind of custom user and tried to login to get a couple of json web tokens. But I only used email, not username. So what I did in the end and it works for me. Perhaps this example explains something in the place where authentication is done ...
Model and manager like this:
from django.db import models
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import (
AbstractBaseUser,
BaseUserManager
)
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ObjectDoesNotExist
from rest_framework_simplejwt.tokens import RefreshToken
class CustomUserManager(BaseUserManager):
def get_or_create(self, email=None, **kwargs):
allowed_kwargs = ['first_name', 'last_name', 'img', 'about']
if email is not None:
try:
user_obj = super(CustomUserManager, self).get(email=email)
if kwargs:
for k, v in kwargs.items():
setattr(user_obj, k, v)
user_obj.save()
except ObjectDoesNotExist:
email = self.normalize_email(email)
user_obj = self.model(email=email)
password = kwargs.pop('password', None)
if password is not None:
user_obj.set_password(password)
if kwargs:
for k, v in kwargs.items():
if k in allowed_kwargs:
setattr(user_obj, k, v)
user_obj.save()
else:
return False
return user_obj
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(_('email address'), null=True, unique=True)
first_name = models.CharField(max_length=150, null=True, blank=True)
last_name = models.CharField(max_length=150, null=True, blank=True)
img = models.URLField(null=True, blank=True)
about = models.TextField(_('about'), max_length=500, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
def __str__(self):
return f'<CustomUser(id={self.id} - email={self.email})>'
class Meta:
ordering = ['-created_at']
#property
def full_name(self):
if hasattr(self, 'first_name') and hasattr(self, 'last_name'):
return f'{self.first_name} {self.last_name}'
return 'No fullname'
#property
def jwt_tokens(self):
refresh = RefreshToken.for_user(self)
return {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
Customizing token serializer:
from django.contrib.auth import authenticate
from django.contrib.auth.models import update_last_login
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import serializers
class CustomTokenSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
email = serializers.EmailField(required=True)
password = serializers.CharField(min_length=8, write_only=True)
def validate(self, email, password):
try:
self.user = CustomUser.objects.get(email=email)
except ObjectDoesNotExist as e:
message = {'error': f'User with email={email} does not exist.'}
return message
check_auth = authenticate(username=email, password=password)
if check_auth is None:
message = {'error':
'The user exists, but the password is incorrect.'}
return message
data = self.user.jwt_tokens
update_last_login(None, self.user)
return data
Urls:
urlpatterns += [
path('login/token/', views.LoginTokenView.as_view(), name='login-token')
]
I faced a similar issue. Turns out that I had not included the password field among the fields in the Writer serializer.
Before I had code like this;
class UserWriterSerializer(serializers.ModelSerializer):
class Meta:
model = AppUser
fields = [
'id',
'username',
'first_name',
'last_name',
'email',
'is_active',
'telephone',
'userType',
'gender',
'registration_date'
]
Then I added the password field to have this;
class UserWriterSerializer(serializers.ModelSerializer):
class Meta:
model = AppUser
fields = [
'id',
'username',
'password',
'first_name',
'last_name',
'email',
'is_active',
'telephone',
'userType',
'gender',
'registration_date'
]
So in summary, some different value was being saved in the database which was not the password I added. Thus having that error, because the password you place as an input is not matching with what is in the database. Make sure that the serializer is correct
I am making a custom user model using the AbstractBaseUser and PermissionsMixin by following these two tutorials (tutorial-1 and tutorial-2).
This the model so far:
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField('email address', unique=True, db_index=True)
username = models.CharField('username', unique=True, db_index=True)
joined = models.DateField(auto_now_add=True)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
def __unicode__(self):
return self.email
Now what I am confused about is that in tutorial-1, the author didn't made any custom manager for the custom User model. Instead he use forms for creating user.
class RegistrationForm(forms.ModelForm):
email = forms.EmailField(label = 'Email')
password1 = forms.CharField(widget = forms.PasswordInput(), label = "Password")
password2 = forms.CharField(widget = forms.PasswordInput(), label = 'Retype password')
class Meta:
model = User
fields = ['email', 'username', 'password1', 'password2']
def clean(self):
"""
Verify that the values entered into the password fields match
"""
cleaned_data = super(RegistrationForm, self).clean()
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise ValidationError("Password don't match.")
return self.cleaned_data
def save(self, commit=True):
user = super(RegistrationForm, self).save(commit=False)
user.set_password(self.cleaned_data['password1'])
if commit:
user.save()
return user
But in tutorial-2, its author made a custom manager for the custom User model.
class UserManager(BaseUserManager):
def create_user(self, email, password, **kwargs):
user = self.model(
email=self.normalize_email(email),
is_active=True,
**kwargs
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password, **kwargs):
user = self.model(
email=email,
is_staff=True,
is_superuser=True,
is_active=True,
**kwargs
)
user.set_password(password)
user.save(using=self._db)
return user
Referencing with Django Docs, there's an example of custom user model, and it uses custom manager. My question is, whether it is ok not to make any other custom manager and if not what is the use of creating a custom manager?
I think this is the relevant section from the docs for you:
You should also define a custom manager for your User model. If your User model defines username, email, is_staff, is_active, is_superuser, last_login, and date_joined fields the same as Django’s default User, you can just install Django’s UserManager; however, if your User model defines different fields, you will need to define a custom manager that extends BaseUserManager providing [the create_user() and create_superuser() methods].
The example in the docs needs to define a custom manager, so that it can set the date_of_birth field.
It appears that the example in the tutorial requires a custom manager, because it uses the email as the unique identifier, and does not have a separate username field.