Serializing and Deserialzing User using Djoser Django Rest - django

I am quiet new to django and i am using djoser to create a user. I have custom user Model and Custom User Serializer. My Custom User is following.
class User(AbstractBaseUser, PermissionsMixin):
"""
A fully featured User model with admin-compliant permissions that uses
a full-length email field as the username.
Email and password are required. Other fields are optional.
"""
email = models.EmailField(_('email address'), max_length=254, unique=True)
first_name = models.CharField(_('first name'), max_length=30, blank=True)
middle_name = models.CharField(_('middle name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin '
'site.'))
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
extra_field = models.BooleanField(_('changethis'), default=False,
help_text=_('Describes whether the user is changethis '
'site.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
username = models.CharField(_('username'), max_length=30, blank=True)
roles = models.ManyToManyField(Role, related_name='role_user')
objects = CustomUserManager()
and following is my CustomUserManager()
class CustomUserManager(BaseUserManager):
def _create_user(self, email, password,
is_staff, is_superuser, **extra_fields):
now = timezone.now()
if not email:
raise ValueError('The given email must be set')
email = self.normalize_email(email)
user = self.model(email=email,
is_staff=is_staff, is_active=True,
is_superuser=is_superuser, last_login=now,
date_joined=now, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, email, password=None, **extra_fields):
return self._create_user(email, password, False, False,
**extra_fields)
def create_superuser(self, email, password, **extra_fields):
return self._create_user(email, password, True, True,
**extra_fields)
And following is my serializer code.
class RegisterSerializer(serializers.Serializer):
email = serializers.EmailField(required=allauth_settings.EMAIL_REQUIRED)
first_name = serializers.CharField(required=True)
middle_name = serializers.CharField(required=False)
last_name = serializers.CharField(required=True)
password = serializers.CharField(required=True, write_only=True)
roles = serializers.ListSerializer(child=serializers.IntegerField())
# def create(self, validated_data):
def create(self, validated_data):
roles_ids = validated_data.pop('roles')
roles = Role.objects.filter(id__in=roles_ids)
user = User.objects.create_user(**validated_data)
user.roles.set(roles)
return user
Since the Roles objects are already created so i just take a list of ids of roles from request and filter these objects of roles and set them for the respective user. Now the user is being created just fine but when djoser tries to return response using same serializer it throws following exception.
TypeError: int() argument must be a string, a bytes-like object or a number, not 'Role'
because roles are supposed to be list of integers not List of Role Objects. How can i fix it?

Change this
roles = serializers.ListSerializer(child=serializers.IntegerField())
to
roles = serializers.ListField(child=serializers.IntegerField())
Or,
If you want to list all roles then create a new serializer for roles and add it to child like:
roles = serializers.ListSerializer(child=RolesSerializer())

Related

How to create 2 types of users in Django?

I want to create 2 types of users in my Django project namely Customer and Company. Both the users have different attributes for registration process.
Here is my user odel.py file -
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, email, password, **extra_fields):
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, password=None, is_staff=False, is_superuser=False, **extra_fields):
"""Create and save a regular User with the given email and password."""
extra_fields.setdefault('is_staff', is_staff)
extra_fields.setdefault('is_superuser', is_superuser)
return self._create_user(email, password, **extra_fields)
class User(AbstractUser):
"""User model."""
username = None
last_name = None
first_name = None
name = models.CharField(max_length=100)
email = models.EmailField(_('email address'), unique=True)
contact = models.CharField(max_length=100)
USERNAME_FIELD = 'id'
REQUIRED_FIELDS = ['contact']
objects = UserManager()
#receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
I want to create different registration pages for both the users. I searched nearly everywhere but couldn't find a proper solution. Can someone help?
You have to create two separate models with Foreign key of User. And make separate Forms adding whatever extra fields you want.
Here is similar case:
models.py:
class User(AbstractUser):
first_name = None
last_name = None
phoneNumberRegex = RegexValidator(
regex=r'^\+?1?\d{8,15}$', message='Enter valid phone')
username = models.CharField(
_('phone'),
validators=[phoneNumberRegex],
max_length=16,
unique=True,
error_messages={
'unique': _('A user with that phone already exists.'),
},
help_text=_('Required. 16 characters or fewer.'),
)
full_name = models.CharField(
_('full name'),
max_length=70,
blank=True,
null=True,
error_messages={
'max_length': _('Full name max length is 70 characters.'),
}
)
...
class Worker(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
error_messages={
'blank': _('Worker must have a user related to!'),
'null': _('Worker must have a user related to!'),
}
)
is_manager = models.BooleanField(
_('manager status'),
default=False,
help_text=_(
'Designates whether the user can log into this manager site.'),
)
...
serializers.py:
from allauth.account.adapter import get_adapter
class CustomRegisterSerializer(serializers.ModelSerializer):
password1 = serializers.CharField(write_only=True)
password2 = serializers.CharField(write_only=True)
class Meta:
model = User
fields = ('username', 'full_name', 'password1', 'password2',
'is_worker')
def get_cleaned_data(self):
return {
'username': self.validated_data.get('username'),
'full_name': full_name if full_name != "" else None,
'password1': self.validated_data.get('password1'),
'is_worker': self.validated_data.get('is_worker', False),
}
def save(self, request):
adapter = get_adapter()
user = adapter.new_user(request)
self.cleaned_data = self.get_cleaned_data()
for attr, value in self.cleaned_data.items():
setattr(user, attr, value)
adapter.save_user(request, user, self)
if self.cleaned_data.get(is_worker):
Worker.objects.create(user=user, is_manager=False) # for example
return user

Adding a extra field to user (create user including that field)

I want to use an extra field let me distinguish between user roles.
For that, I've overriden the User class:
class User(AbstractUser):
role = models.CharField(max_length=21, blank=False)
I've also put this in settings.py:
AUTH_USER_MODEL = 'shopping.User'
The problem is that I have the new field in my database, but, if I want to create a new user using the admin, it doesn't ask me for the role.
I suppose that I have to override the save method too, but I don't know how.
Thanks in advance
I would recommend you to rewrite used Model-Like this and it will much easier and you can add filed like you want
from django.contrib.auth.models import (
AbstractBaseUser,
BaseUserManager,
PermissionsMixin,
)
#You can set here required field for register new user.
class UserManager(BaseUserManager):
def create_user(self, email, username, password=None, **extra_fields):
if not email:
raise ValueError("Users must have an email address")
if not username:
raise ValueError("Users must have a username")
user = self.model(
email=self.normalize_email(email), username=username.lower(), **extra_fields
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, username, password):
user = self.create_user(email, username, password)
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class User(AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length=30, unique=True)
email = models.EmailField(max_length=255, unique=True)
fullname = models.CharField(max_length=60, blank=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
date_joined = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
role = models.CharField(max_length=21, blank=False)
objects = UserManager()
USERNAME_FIELD = "username"
REQUIRED_FIELDS = ["email"] #Requiref field set here
AUTH_USER_MODEL = "shopping.User"

Roles in Django

I'm creating web based system, where I have 5 or 6 different roles and access type. I made a research how to achieve this and everything is made only when create group in django admin and assign user into it but only from the administrative part. I need to create this from the views. I already have a form to add users with email and drop down menu to set the role type.
Any suggestions will be appreciated, because till now I don't have idea how to achieve this without to assign the users from django admin.
my model.py for users:
class CustomUserManager(BaseUserManager):
"""
Custom user model manager where email is the unique identifiers
for authentication instead of usernames.
"""
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 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):
"""
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, **extra_fields)
class CustomUser(AbstractUser):
username = None
email = models.EmailField(_('email address'), unique=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = CustomUserManager()
def __str__(self):
return self.email
ADMIN = 1
RECEPTION = 2
HEADOFDEPARTMENT = 3
EMPLOYEE = 4
PATIENT = 5
NOTACTIVE = 6
ROLE_CHOICES = (
(ADMIN, 'Admin'),
(RECEPTION, 'Reception'),
(HEADOFDEPARTMENT, 'HeadOfDepartment'),
(EMPLOYEE, 'Employee'),
(PATIENT, 'Patient'),
(NOTACTIVE, 'NotActive'),
)
role = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, blank=True, default=True, null=True)
#email = models.EmailField(verbose_name="email", max_length=60, unique=True)
username = models.CharField(max_length=30, blank=True, default='')
is_superuser = models.BooleanField(default=True)
is_admin = models.BooleanField(default=True)
is_employee = models.BooleanField(default=True)
is_headofdepartment = models.BooleanField(default=True)
is_reception = models.BooleanField(default=True)
is_patient = models.BooleanField(default=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=True)
like this
from django.contrib.auth.models import Group
def ViewFunc(request):
... #your func
role = Group.objects.get(name='role')
role.user_set.add(user)
You retrieve the user object in the way you want in views, you add it in the user set in the group, done.
EDIT
Adding user Foo to Admins Group.
from django.contrib.auth.models import Group, User
def ViewFunc(request):
user = User.objects.get(username = 'Foo')
role = Group.objects.get(name='Admins')
role.user_set.add(user)

django-rest-auth custom registration fails to save extra fields

I am using DRF and for login/registration I am using Django-rest-auth.
I have customized User model to have extra fields
I have custom registration serializer to store extra fields along with username, password while registering a new user.
Registration is successful however, extra fields are not saved along with username, first_name, last_name and password.
My model:
class UserManager(BaseUserManager):
def _create_user(self, username, email, password, is_staff, is_superuser, address, **extra_fields):
now = timezone.now()
if not username:
raise ValueError(_('The given username must be set'))
email = self.normalize_email(email)
user = self.model(username=username, email=email,
is_staff=is_staff, is_active=True,
is_superuser=is_superuser, last_login=now,
date_joined=now, address=address, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, username, email=None, password=None, **extra_fields):
return self._create_user(username, email, password, False, False, True,
**extra_fields)
def create_superuser(self, username, email, password, **extra_fields):
user=self._create_user(username, email, password, True, True,
**extra_fields)
user.is_active=True
user.save(using=self._db)
return user
class User(AbstractBaseUser, PermissionsMixin):
username = models.CharField(_('username'), max_length=30, unique=True,
help_text=_('Required. 30 characters or fewer. Letters, numbers and #/./+/-/_ characters'),
validators=[
validators.RegexValidator(re.compile('^[\w.#+-]+$'), _('Enter a valid username.'), _('invalid'))
])
first_name = models.CharField(_('first name'), max_length=30, blank=True, null=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True, null=True)
email = models.EmailField(_('email address'), max_length=255, unique=True)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin site.'))
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as active. Unselect this instead of deleting accounts.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
receive_newsletter = models.BooleanField(_('receive newsletter'), default=False)
birth_date = models.DateField(_('birth date'), auto_now=False, null=True)
address = models.CharField(_('address'), max_length=30, blank=True, null=True)
phone_regex = RegexValidator(regex=r'^\+?1?\d{9,15}$', message="Phone number must be entered in the format: '+999999999'. Up to 15 digits allowed.")
phone_number = models.CharField(_('phone number'), validators=[phone_regex], max_length=30, blank=True, null=True) # validators should be a list
USER_TYPES = (
('Farmer', 'Farmer'),
('Windmill owner', 'Windmill owner'),
('Solar panel owner', 'Solar panel owner'),)
user_type = models.CharField(_('user type'), choices=USER_TYPES, max_length=30, blank=True, null=True)
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email',]
objects = UserManager()
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
def get_full_name(self):
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
return self.first_name
def email_user(self, subject, message, from_email=None):
send_mail(subject, message, from_email, [self.email])
My Serializer:
class RegisterSerializer(serializers.Serializer):
email = serializers.EmailField(required=allauth_settings.EMAIL_REQUIRED)
first_name = serializers.CharField(required=True, write_only=True)
last_name = serializers.CharField(required=True, write_only=True)
address = serializers.CharField(required=True, write_only=True)
user_type = serializers.ChoiceField(
choices=(('Farmer', 'Farmer'),('Windmill owner', 'Windmill owner'),('Solar panel owner', 'Solar panel owner'),),
style={'base_template': 'radio.html'},
required=True, write_only=True)
password1 = serializers.CharField(required=True, write_only=True)
password2 = serializers.CharField(required=True, write_only=True)
def validate_email(self, email):
email = get_adapter().clean_email(email)
if allauth_settings.UNIQUE_EMAIL:
if email and email_address_exists(email):
raise serializers.ValidationError(
_("A user is already registered with this e-mail address."))
return email
def validate_password1(self, password):
return get_adapter().clean_password(password)
def validate(self, data):
if data['password1'] != data['password2']:
raise serializers.ValidationError(
_("The two password fields didn't match."))
return data
def get_cleaned_data(self):
return {
'first_name': self.validated_data.get('first_name', ''),
'last_name': self.validated_data.get('last_name', ''),
'address': self.validated_data.get('address', ''),
'user_type': self.validated_data.get('user_type', ''),
'password1': self.validated_data.get('password1', ''),
'email': self.validated_data.get('email', ''),
}
def save(self, request):
adapter = get_adapter()
user = adapter.new_user(request)
self.cleaned_data = self.get_cleaned_data()
adapter.save_user(request, user, self)
setup_user_email(request, user, [])
user.save()
return user
What is wrong?
It seems like django-allauth doesn't allow saving custom fields by default:
(ref: https://github.com/pennersr/django-allauth/blob/master/allauth/account/adapter.py#L227)
To go around it, simply assign the custom field values before doing user.save()
self.cleaned_data = self.get_cleaned_data()
adapter.save_user(request, user, self)
setup_user_email(request, user, [])
user.address = self.cleaned_data.get('address')
user.user_type = self.cleaned_data.get('user_type')
user.save()
return user
That was a dirty fix. A cleaner way would be to override the allauth adapter to support your custom fields.
To override the default adapter and save the custom fields try the following
Create an adapters.py file in your app root folder and paste the code below
from allauth.account.adapter import DefaultAccountAdapter
class CustomUserAccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=True):
"""
Saves a new `User` instance using information provided in the
signup form.
"""
from allauth.account.utils import user_field
user = super().save_user(request, user, form, False)
user_field(user, 'address', request.data.get('address', ''))
user_field(user, 'first_name', request.data.get('first_name', ''))
user_field(user, 'last_name', request.data.get('last_name', ''))
user_field(user, 'user_type', request.data.get('user_type', ''))
user.save()
return user
Lastly set the settings configuration to use your custom adapter by adding this line in the settings.py file
ACCOUNT_ADAPTER = 'users.adapters.CustomUserAccountAdapter'

Django: Making the CustomUser appear in admin under Auth

How to make my CustomUser appear in admin under auth app like built-in user? I know there was a question like that here and I followed the solution people suggested, but what their solution do, is that it makes a Customer User in my app, not in an auth app, so like any other model that I create.
Here are my models:
class CustomUserManager(BaseUserManager):
def create_user(self, email, first_name, last_name, password=None,
**extra_fields):
'''
Create a CustomUser with email, name, password and other extra fields
'''
now = timezone.now()
if not email:
raise ValueError('The email is required to create this user')
email = CustomUserManager.normalize_email(email)
cuser = self.model(email=email, first_name=first_name,
last_name=last_name, is_staff=False,
is_active=True, is_superuser=False,
date_joined=now, last_login=now,)
cuser.set_password(password)
cuser.save(using=self._db)
return cuser
def create_superuser(self, email, first_name, last_name, password=None,
**extra_fields):
u = self.create_user(email, first_name, last_name, password,
**extra_fields)
u.is_staff = True
u.is_active = True
u.is_superuser = True
u.save(using=self._db)
return u
class CustomUser(AbstractBaseUser, PermissionsMixin):
'''
Class implementing a custom user model. Includes basic django admin
permissions and can be used as a skeleton for other models.
Email is the unique identifier. Email, password and name are required
'''
email = models.EmailField(_('email'), max_length=254, unique=True,
validators=[validators.validate_email])
username = models.CharField(_('username'), max_length=30, blank=True)
first_name = models.CharField(_('first name'), max_length=45)
last_name = models.CharField(_('last name'), max_length=45)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Determines if user can access the admin site'))
is_active = models.BooleanField(_('active'), default=True)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name', 'last_name']
def get_full_name(self):
'''
Returns the user's full name. This is the first name + last name
'''
full_name = "%s %s" % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
'''
Returns a short name for the user. This will just be the first name
'''
return self.first_name.strip()
And I also want to add ManyToManyField to 2 other models I have and make them appear in the user form in admin.
Does it mean that I have to write my own form? Or maybe I can just copy the source code for the built-in user form and change it to my names?
Thanks a lot in advance!
Why do you need it in the auth app? Why does that matter? If you really need to do that, you can just add an app_label variable in the `Meta
class Meta:
app_label = 'auth'
This will change the table names, so you will need to migrate those.
for the ManyToManyField, I would just override the appropriate auth forms and add those fields.