I'm using Django REST with the rest_auth package, and I'm having an issue with serializing custom fields when registering a new user.
The field "is_employer" is always set to false even when you set it to true. Also in the API if you set the is_employer checkbox to true, it doesn't change anything. I added it in models, serializers and adapter, what am I missing?
MODELS:
class MyUserManager(BaseUserManager):
def create_user(self, email, password=None):
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_superuser(self, email, password):
user = self.create_user(
email=self.normalize_email(email),
password=password,
)
user.is_admin = True
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class CustomUser(AbstractBaseUser):
username = None
first_name = None
last_name = None
email = models.EmailField(verbose_name='email address', max_length=255, unique=True)
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)
is_employer = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = MyUserManager()
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
return self.is_admin
def has_module_perms(selfself, app_label):
return True;
SERIALIZERS:
from django.contrib.auth.hashers import make_password
from rest_framework import serializers
from rest_auth.registration.serializers import RegisterSerializer
from users.models import CustomUser
class CustomRegisterSerializer(RegisterSerializer):
def get_cleaned_data(self):
data_dict = super().get_cleaned_data()
data_dict['is_employer'] = self.validated_data.get('is_employer', '')
return data_dict
ADAPTER:
class CustomAccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=False):
user = super().save_user(request, user, form, commit)
data = form.cleaned_data
user.is_employer = data.get('is_employer')
user.save()
return user
First, set the is_employer field explicitly in the CustomRegisterSerializer class
from rest_auth.registration.serializers import RegisterSerializer
class CustomRegisterSerializer(RegisterSerializer):
is_employer = serializers.BooleanField(default=True)
def get_cleaned_data(self):
data_dict = super().get_cleaned_data()
data_dict['is_employer'] = self.validated_data.get('is_employer', False)
return data_dict
then in your settings.py update the serializer as
# settings.py
REST_AUTH_REGISTER_SERIALIZERS = {
"REGISTER_SERIALIZER": "dotted.path.to.CustomRegisterSerializer"
}
Related
When I try to create a new user through DRF, the avatar field displays the default image even if the user uploads an image. When I try to create a user with an image through the admin panel it works fine. So I'm assuming that I'm missing something in my serializer.
Custom User Model
class myAccountManager(BaseUserManager):
def create_user(self, email, username, password=None):
if not email:
raise ValueError('Users must have an email')
if not username:
raise ValueError('Users must have an username')
user = self.model(
email = self.normalize_email(email),
username=username,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, username, password):
user = self.create_user(
email = self.normalize_email(email),
password=password,
username=username,
)
user.is_admin=True
user.is_staff=True
user.is_superuser=True
user.save(using=self._db)
return user
class User(AbstractBaseUser):
email = models.EmailField(verbose_name="email", max_length=60, unique=True)
username = models.CharField(max_length=30, unique=True)
avatar = models.ImageField(default='default.jpg')
date_joined = models.DateTimeField(verbose_name='date joined', auto_now_add=True)
last_login = models.DateTimeField(verbose_name='last login', auto_now_add=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)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
objects = myAccountManager()
def __str__(self):
return self.username
def has_perm(self, perm, obj=None):
return self.is_admin
def has_module_perms(self, app_label):
return True
User serializer
UserModel = User
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
def create(self, validated_data):
user = UserModel.objects.create_user(
username=validated_data['username'],
password=validated_data['password'],
email=validated_data['email'],
)
return user
class Meta:
model = UserModel
# Tuple of serialized model fields (see link [2])
fields = ( "id", "email", "username", "password", "avatar")
Static file path
STATIC_URL = '/static/'
MEDIA_URL = '/images/'
STATICFILES_DIRS = [
BASE_DIR / 'static'
]
MEDIA_ROOT = BASE_DIR /'static/images'
The issue is with the serializer's create() method. You were not passing the avatar data into the create_user(...) method. So, update your method as below,
from rest_framework import serializers
UserModel = User
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
def create(self, validated_data):
return UserModel.objects.create_user(**validated_data)
class Meta:
model = UserModel
# Tuple of serialized model fields (see link [2])
fields = ("id", "email", "username", "password", "avatar")
I am working on building a project and for this, i need to create a custom user model, since the one that Django comes with isn't suitable for my situation, so whenever i use the AbstractBaseUser, I am forced to use the username field, which i really don't need in my case. how can I create a custom user model without using the username field and thank you
After many attempts, i finally could fix it by overiding it in the accountmanager
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
import uuid
class MyAccountManager(BaseUserManager):
def _create_user(self, email, first_name, last_name, password=None):
if not email:
raise ValueError("Users must have an email address")
if not first_name:
raise ValueError("Users must have an userusername")
if not last_name:
raise ValueError("Users must have an userusername")
user = self.model(
email=self.normalize_email(email),
first_name=first_name,
last_name=last_name,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email,first_name, last_name, password):
user = self._create_user(
email=self.normalize_email(email),
password=password,
first_name= first_name,
last_name=last_name,
)
user.is_admin = True
user.is_staff = True
user.is_superuser = True
user.is_active = True
user.save(using=self._db)
return user
def verifyAccount(self, user, user_input, code):
if user_input == code:
user.is_active = True
user.save(using=self._db)
return user
class Account(AbstractBaseUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField(verbose_name="email", max_length=60, 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=False)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name', 'last_name']
objects = MyAccountManager()
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
return self.is_admin
def has_module_perms(self, app_label):
return True
I'm using dj-rest-auth (https://dj-rest-auth.readthedocs.io/en/latest/) and trying to implement a custom registration form. When I'm trying to register a new user I have the base form.
I've seen with the older version (https://django-rest-auth.readthedocs.io/en/latest/) that if you use password1 and password2, you don't have to retype all the code.
serializers.py
from rest_framework import serializers
from dj_rest_auth.registration.serializers import RegisterSerializer
class CustomRegisterSerializer(RegisterSerializer):
first_name = serializers.CharField()
last_name = serializers.CharField()
def get_cleaned_data(self):
super(CustomRegisterSerializer, self).get_cleaned_data()
return {
'username': self.validated_data.get('username', ''),
'password1': self.validated_data.get('password1', ''),
'password2': self.validated_data.get('password2', ''),
'email': self.validated_data.get('email', ''),
'first_name': self.validated_data.get('first_name', ''),
'last_name': self.validated_data.get('last_name', '')
}
settings.py
REST_AUTH_SERIALIZERS = {
'REGISTER_SERIALIZER': 'accounts.serializers.CustomRegisterSerializer',
}
The problem was in settings.py:
REST_AUTH_REGISTER_SERIALIZERS = {
'REGISTER_SERIALIZER': 'accounts.serializers.CustomRegisterSerializer'
}
You can create your own User and make it the AUTH_USER_MODEL of your project with something like this:
from django.contrib.auth.models import AbstractUser, BaseUserManager
class MyUserManager(BaseUserManager):
def create_user(self, email, username,first_name, last_name, password=None):
user = self.model(
email=self.normalize_email(email),
username=username,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self,email, username, password, first_name, last_name, is_tutor, is_student):
user = self.create_user(
email=self.normalize_email(email),
username=username,
password=password,
first_name=first_name,
last_name=last_name,
)
user.is_staff = True
user.is_admin = True
user.is_superuser = True
user.save(using=self._db)
return user
class User(AbstractBaseUser):
email = models.EmailField(verbose_name='email', max_length=60, unique=True)
username = models.CharField(max_length=30, unique=True)
date_joined = models.DateField(verbose_name='date joined', auto_now_add=True)
last_login = models.DateField(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)
rating = models.FloatField(default=0, blank=True, null=True)
reviews_count = models.IntegerField(default=0)
first_name = models.CharField(verbose_name='first_name', max_length=30)
last_name = models.CharField(verbose_name='last_name', max_length=30)
USERNAME_FIELD = 'email'
#this field means that when you try to sign in the username field will be the email
#change it to whatever you want django to see as the username when authenticating the user
REQUIRED_FIELDS = ['username', 'first_name', 'last_name',]
objects = MyUserManager()
def __str__(self):
return self.first_name + ' - ' + self.email
def has_perm(self, perm, obj=None):
return self.is_admin
def has_module_perms(self, app_label):
return True
Then in settings.py you declare the AUTH_USER_MODEL = "to the model you just created" and in serializers.py create a serializer for the user registration:
class UserRegistrationSerializer(serializers.ModelSerializer):
password2 = serializers.CharField(style={'input_type':'password'}, write_only=True)
class Meta:
model = User
fields = ['username', 'email', 'first_name','last_name',
'password', 'password2',]
extra_kwargs = {
'password': {
'write_only':True
}
}
def save(self):
user = User(
email=self.validated_data['email'],
username=self.validated_data['username'],
first_name=self.validated_data['first_name'],
last_name=self.validated_data['last_name'],
is_tutor=self.validated_data['is_tutor'],
is_student=self.validated_data['is_student'],
)
password = self.validated_data['password']
password2 = self.validated_data['password2']
if password != password2:
raise serializers.ValidationError({'password':'Passwords must match.'})
user.set_password(password)
user.save()
return user
then you register your custom user model in the django admin
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
class AccountAdmin(UserAdmin):
list_display = ('email', 'username','pk', 'date_joined', 'last_login', 'is_admin', 'is_staff')
search_fields = ('email', 'username')
readonly_fields = ('date_joined', 'last_login')
filter_horizontal = ()
list_filter = ()
fieldsets = ()
admin.site.register(User, AccountAdmin)
I hope this helps or at least point you in the right direction to where you want to be
I modified the user model as shown below, to make users log in via phone.
from django.db import models
import datetime
from django.core.mail import send_mail
from django.contrib.auth.models import (
AbstractBaseUser, BaseUserManager
)
from django.contrib.staticfiles.templatetags.staticfiles import static
from django.core.validators import RegexValidator
class UserManager(BaseUserManager):
def get_by_natural_key(self, username):
case_insensitive_username_field = '{}__iexact'.format(self.model.USERNAME_FIELD)
return self.get(**{case_insensitive_username_field: username})
def create_user(self,phone_number,password=None,is_staff=False,is_active = True, is_admin = False, is_vendor = False):
if not phone_number:
raise ValueError("Users Must Have a phone number")
if not password:
raise ValueError("Users must have a password")
user = self.model(
phone_number )
user.set_password(password)
user.is_staff = is_staff
user.is_active = is_active
user.is_admin = is_admin
user.is_vendor = is_vendor
user.save(using=self._db)
return user
def create_staffuser(self,phone_number, password=None):
user = self.create_user(
phone_number,
password=password,
is_staff=True
)
return user
def create_superuser(self,phone_number, password=None):
user = self.create_user(
phone_number,
password=password,
is_admin=True,
is_staff=True
)
return user
def create_vendoruser(self,phone_number, password=None):
user = self.create_user(
phone_number,
password=password,
is_vendor=True
)
return user
class User(AbstractBaseUser):
phone_number = models.CharField(max_length=20, unique=True)
date_joined = models.DateField(auto_now_add = True)
is_active = models.BooleanField(default = True)
is_staff = models.BooleanField(default = False)
is_admin = models.BooleanField(default = False)
is_vendor = models.BooleanField(default = False)
is_basic = models.BooleanField(default = False)
REQUIRED_FIELDS = []
objects = UserManager()
def __str__(self):
return self.phone_number
# def save(self, *args, **kwargs):
# self.email = self.email.lower()
# return super(User, self).save(*args, **kwargs)
def get_full_name(self):
return self.phone_number
def get_short_name(self):
return self.phone_number
def has_perm(self, perm, obj=None):
return True
def has_module_perms(self, app_label):
return True
def get_type(self):
if self.vendor:
return self.vendor_user
else:
return self.basic_user
#property
def staff(self):
return self.is_staff
#property
def admin(self):
return self.is_admin
#property
def active(self):
return self.is_active
#property
def vendor(self):
return self.is_vendor
#property
def basic(self):
return self.is_basic
USERNAME_FIELD = 'phone_number'
Then I created a superuser and tried to log in, but couldn't.
Upon checking the database from the python shell, I realized that the database had an empty phone number field for the superuser I created.
So the problem is: phone number is not properly saving when I create superuser
In your code you write:
user = self.model(phone_number)
(reformatted)
But if you create a model instance, all parameters must be named parameters (since fields are unordered, it would make it very hard to know where the values would "land"). So you have to specify the field name you want to update:
user = self.model(phone_number=phone_number)
I feel like I'm missing somehting obvious on this one.
I've created a custom user and user manger
class UserManager(BaseUserManager):
# create a normal user
# an email and password must be provided
def create_user(self, email, password, first_name, last_name,
location, date_of_birth):
if not email:
raise ValueError("User must have an email")
if not password:
raise ValueError("User must have a password")
email = email.lower()
user = self.model(
email=email,
first_name=first_name,
last_name=last_name,
location=location,
date_of_birth=date_of_birth
)
user.set_password(password)
user.save(using=self._db)
return user
# Make an administrator
def create_superuser(self, email, password, first_name, last_name,
location, date_of_birth):
user = self.create_user(
email=email,
password=password,
first_name=first_name,
last_name=last_name,
location=location,
date_of_birth=date_of_birth
)
user.is_admin = True
user.is_moderator = True
user.save(using=self._db)
return user
class User(AbstractBaseUser):
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True
)
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
location = models.ForeignKey(Location)
date_of_birth = models.DateField()
date_joined = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
is_moderator = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name', 'last_name', 'location', 'date_of_birth']
def __unicode__(self):
return self.email
def get_full_name(self):
return self.first_name + ' ' + self.last_name
def get_age(self):
age = date.today() - self.date_of_birth
return age.days / 365
def is_staff(self):
return self.is_admin
def has_perm(self, perm, obj=None):
return True
def has_module_perms(self, app_label):
return True
However if I visit the admin site, It will happily authorize a user who is not an admin is_admin=False
Has anyone run into this problem, Is there something I need to change when using django admin with a custom user?
EDIT
setting.py
AUTH_USER_MODEL = 'userAccount.User'
AUTHENTICATION_BACKEND = (
'django.contrib.auth.backends.ModelBackend',
)
is_admin is not something that django's authentication system knows about. In the authentication form that is used, it is only checking if the user is active or is staff:
class AdminAuthenticationForm(AuthenticationForm):
"""
A custom authentication form used in the admin app.
"""
error_messages = {
'invalid_login': _("Please enter the correct %(username)s and password "
"for a staff account. Note that both fields may be "
"case-sensitive."),
}
required_css_class = 'required'
def confirm_login_allowed(self, user):
if not user.is_active or not user.is_staff:
raise forms.ValidationError(
self.error_messages['invalid_login'],
code='invalid_login',
params={'username': self.username_field.verbose_name}
)
In the original user model, is_staff is a model field. You do not have such a field, but rather a method. This could be a why its not working.
You can solve this problem two ways:
Create your own AdminAuthenticationForm and adjust the confirm_login_allowed method to check for is_admin rather than is_staff.
Create a is_staff property in your custom user model:
#property
def is_staff(self):
return self._is_admin