I am wrote a serializer for the User model in Django with DRF:
the model:
from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import BaseUserManager
from django.db import models
from django.utils.translation import ugettext
class BaseModel(models.Model):
# all models should be inheritted from this model
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class User(AbstractBaseUser, BaseModel):
username = models.CharField(
ugettext('Username'), max_length=255,
db_index=True, unique=True
)
email = models.EmailField(
ugettext('Email'), max_length=255, db_index=True,
blank=True, null=True, unique=True
)
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ('email', 'password',)
class Meta:
app_label = 'users'
the serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = ['email', 'username', 'password']
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = super().create(validated_data)
user.set_password(validated_data['password'])
user.save()
return user
def update(self, user, validated_data):
user = super().update(user, validated_data)
user.set_password(validated_data['password'])
user.save()
return user
It works. But I probably do two calls instead of one on every create/update and the code looks a little bit weird(not DRY).
Is there an idiomatic way to do that?
$python -V
Python 3.7.3
Django==2.2.3
djangorestframework==3.10.1
You can create your own user manager by overriding BaseUserManager and use set_password() method there. There is a full example in django's documentation. So your models.py will be:
# models.py
from django.db import models
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser
)
class MyUserManager(BaseUserManager):
def create_user(self, email, username, password=None):
if not email:
raise ValueError('Users must have an email address')
user = self.model(
email=self.normalize_email(email),
username=username,
)
user.set_password(password)
user.save(using=self._db)
return user
class BaseModel(models.Model):
# all models should be inheritted from this model
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class User(AbstractBaseUser, BaseModel):
username = models.CharField(
ugettext('Username'), max_length=255,
db_index=True, unique=True
)
email = models.EmailField(
ugettext('Email'), max_length=255, db_index=True,
blank=True, null=True, unique=True
)
# don't forget to set your custom manager
objects = MyUserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ('email', 'password',)
class Meta:
app_label = 'users'
Then, you can directly call create_user() in your serializer's create() method. You can also add a custom update method in your custom manager.
# serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = ['email', 'username', 'password']
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = models.User.objects.create_user(
username=validated_data['username'],
email=validated_data['email'],
password=validated_data['password']
)
return user
I hope this will solve the issue,
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = ['email', 'username', 'password']
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
return models.User.objects.create_user(**validated_data)
def update(self, user, validated_data):
password = validated_data.pop('password', None)
if password is not None:
user.set_password(password)
for field, value in validated_data.items():
setattr(user, field, value)
user.save()
return user
The create_user() method uses the set_password() method to set the hashable password.
Related
I'm trying to write a REST API using Django and DRF. I'm trying to create a user model and use it in my application. But the problem is that it returns a 400 error status code which says:
{"username":["This field is required."]}
This is my code for models:
import uuid
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.conf import settings
from django.dispatch import receiver
from django.utils.encoding import python_2_unicode_compatible
from django.db.models.signals import post_save
from rest_framework.authtoken.models import Token
from api.fileupload.models import File
#python_2_unicode_compatible
class User(AbstractUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField('Email address', unique=True)
name = models.CharField('Name', default='', max_length=255)
phone_no = models.CharField('Phone Number', max_length=255, unique=True)
address = models.CharField('Address', default='', max_length=255)
country = models.CharField('Country', default='', max_length=255)
pincode = models.CharField('Pincode', default='', max_length=255)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
def __str__(self):
return self.email
#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)
The Serializer:
class CreateUserSerializer(serializers.ModelSerializer):
username = None
def create(self, validated_data):
validated_data['username'] = uuid.uuid4()
user = User.objects.create_user(**validated_data)
return user
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
instance.address = validated_data.get('address', instance.address)
instance.country = validated_data.get('country', instance.country)
instance.pincode = validated_data.get('pincode', instance.pincode)
instance.phone_no = validated_data.get('phone_no', instance.phone_no)
instance.email = validated_data.get('email', instance.email)
instance.save()
return instance
class Meta:
unique_together = ('email',)
model = User
fields = (
'id', 'password', 'email', 'name', 'phone_no', 'address', 'country', 'pincode',
)
extra_kwargs = {'password': {'write_only': True}}
Admin.py file:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User
#admin.register(User)
class UserAdmin(UserAdmin):
pass
class User(AbstractUser):
As your User model inherits from AbstractUser, it will inherit
the username field.
Just remove the username field from your User model by setting username = None like this:
class User(AbstractUser):
# ...
username = None
# ...
class UserAdmin(UserAdmin):
As your UserAdmin model inherits from django.contrib.auth.admin.UserAdmin, you will need to update fieldsets, list_display, search_fields, and ordering fields in your UserAdmin model because they use username which you have removed from your User model.
Abstract User always has the username field. Removing it will cause problems. I will suggest you store the email address of the user in username field as well and use that. Please make sure its always updated in both fields which is not very hard.
I am have created a custom user model to replace username with email. But when I am trying to add user from Django Admin I get error:
Please correct the errors below.
There is no other information with that error message.
The code for custom user:
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(
verbose_name="email_address",
max_length=255,
unique=True,
)
name = models.CharField(max_length=255)
is_staff = models.BooleanField(
default=False,
)
is_active = models.BooleanField(
default=True,
)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
def get_full_name(self):
return self.name
def __str__(self):
return self.email
Custom User Manager Code:
class UserManager(BaseUserManager):
def _create_user(self, email, password, **kwargs):
if not email:
raise ValueError("Email is required")
email = self.normalize_email(email)
user = self.model(email=email, **kwargs)
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)
def create_superuser(self, email, password, **kwargs):
kwargs.setdefault('is_staff', True)
kwargs.setdefault('is_superuser', True)
kwargs.setdefault('is_active', True)
if kwargs.get('is_staff') is not True:
raise ValueError("Superuser must have is_staff True")
if kwargs.get('is_superuser') is not True:
raise ValueError("Superuser must have is_superuser True")
return self._create_user(email, password, **kwargs)
and the admin code for same:
class CustomUserAdmin(BaseUserAdmin):
add_form = CustomUserCreationForm
form = CustomUserCreationForm
list_display = ('id', 'email', 'is_superuser')
list_filter = ('is_superuser',)
fieldsets = (
(None, {'fields': ('name', 'email', 'password')}),
('Permissions', {'fields': ('is_superuser', 'is_staff')}),
)
add_fieldsets = fieldsets
search_fields = ('name', 'email')
ordering = ('name', 'email')
filter_horizontal = ()
Custom User Form:
# users/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import User
class CustomUserCreationForm(UserCreationForm):
class Meta:
model = User
fields = ('name', 'email')
class CustomUserChangeForm(UserChangeForm):
class Meta:
model = User
fields = ('name', 'email')
I have tried many changes but couldn't figure out the issue. I would appreciate any help on this.
Change:
class Meta:
model = User
fields = ('name', 'email')
To:
class Meta(UserCreationForm.Meta):
model = User
fields = UserCreationForm.Meta.fields + ('name', 'email')
I am using Django user model and also extended with my own profile model for some other data.When i want to update user data which is in Profile Model It doesn't get updated because all the user id,username,email,password reside in user model while the fields which are needed to get updated are in profile model.I have used this approach, all it does is takes inputs and displays the response but does not show any change in User Data when viewing it as a whole.
models.py
class Profile(models.Model):
user = models.OneToOneField(User,related_name='profile',on_delete=models.CASCADE)
location = models.CharField(max_length=30,blank=True)
friends_count = models.PositiveIntegerField(default=0)
profile_pic = models.FileField(upload_to='profile_pics/',blank=True,null=True)
def natural_key(self):
return (self.user.username,)
views.py
class UserCreateAPIViewSet(viewsets.ModelViewSet,mixins.UpdateModelMixin):
""""A View which handles Creating and Updating User Profile"""
serializer_class = UserProfileCreateSerializer
queryset = User.objects.all()
authentication_classes = (TokenAuthentication,)
permission_classes = (permissions.UpdateOwnProfile,)
filter_backends = (filters.SearchFilter,)
search_fields = ('username','email',)
class UserUpdateAPI(generics.GenericAPIView,mixins.UpdateModelMixin):
"""Update User Profile Data"""
permission_classes = (permissions.UpdateOwnProfile,)
authentication_classes = (TokenAuthentication,)
queryset = Profile.objects.all()
serializer_class = ProfileUpdateSerializer
def put(self,request,*args,**kwargs):
return self.partial_update(request,*args,**kwargs)
urls.py
url(r'^user-update/(?P<pk>\d+)/$',views.UserUpdateAPI.as_view(),name="user-update"),
router = DefaultRouter()
router.register(r'profile',views.UserCreateAPIViewSet)
serializers.py
class UserProfileCreateSerializer(serializers.ModelSerializer):
""""A serializer for user data request"""
location = serializers.CharField(source='profile.location')
friends_count = serializers.IntegerField(source='profile.friends_count',read_only=True)
profile_pic = serializers.FileField(source='profile.profile_pic',allow_empty_file=True,allow_null=True)
class Meta:
model = User
fields = (
'pk',
'username',
'email',
'password',
'location',
'friends_count',
'profile_pic',
)
extra_kwargs = {
'password':{'write_only':True},
'friends_count':{'read_only':True},
}
def create(self, validated_data):
""""Create and return a new User"""
user = User(
email = validated_data['email'],
username = validated_data['username']
)
user.set_password(validated_data['password'])
user.save()
return user
class ProfileUpdateSerializer(serializers.ModelSerializer):
"""A serializer for updating user data"""
class Meta:
model = Profile
fields = ('location','profile_pic')
Looks like you want to extend from the AbstractBaseUser (see code below), using this as your UserProfileCreateSerializer:
class UserProfileCreateSerializer(serializers.ModelSerializer):
""""A serializer for user data request"""
location = serializers.CharField(source='profile.location')
friends_count = serializers.IntegerField(source='profile.friends_count',read_only=True)
profile_pic = serializers.FileField(source='profile.profile_pic',allow_empty_file=True,allow_null=True)
class Meta:
model = Profile
fields = (
'pk',
'username',
'email',
'password',
'location',
'friends_count',
'profile_pic',
)
extra_kwargs = {
'password':{'write_only':True},
'friends_count':{'read_only':True},
}
def create(self, validated_data):
""""Create and return a new User"""
user = Profile(
email = validated_data['email'],
username = validated_data['username']
)
user.set_password(validated_data['password'])
user.save()
return user
class ProfileUpdateSerializer(serializers.ModelSerializer):
"""A serializer for updating user data"""
class Meta:
model = Profile
fields = ('location','profile_pic')
Then extend from the AbstractBaseUser in models.py:
from __future__ import unicode_literals
from django.db import models
from django.core.mail import send_mail
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser
from django.utils.translation import ugettext_lazy as _
from .managers import ProfileManager
class Profile(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(_('email address'), unique=True)
username = models.CharField(_('first name'), max_length=30, blank=True)
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
date_joined = models.DateTimeField(_('date joined'), auto_now_add=True)
is_active = models.BooleanField(_('active'), default=True)
location = models.CharField(max_length=30,blank=True)
friends_count = models.PositiveIntegerField(default=0)
profile_pic = models.FileField(upload_to='profile_pics/',blank=True,null=True)
objects = UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = []
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
def get_full_name(self):
'''
Returns the first_name plus the last_name, with a space in between.
'''
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
'''
Returns the short name for the user.
'''
return self.first_name
def natural_key(self):
return (self.username,)
def email_user(self, subject, message, from_email=None, **kwargs):
'''
Sends an email to this User.
'''
send_mail(subject, message, from_email, [self.email], **kwargs)
And then create a file called managers.py in the same directory as models.py:
from django.contrib.auth.base_user import BaseUserManager
class ProfileManager(BaseUserManager):
use_in_migrations = True
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, password=None, **extra_fields):
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_superuser', 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)
To wrap all this up you need to add this to your settings.py file:
AUTH_USER_MODEL = 'app.Profile'
And also don't forget to re-run all database migrations before using $ python manage.py runserver.
This is my customized User Object in django.
class User(AbstractBaseUser, PermissionsMixin):
mobile = models.CharField(max_length=100, unique=True)
email = models.EmailField(max_length=255, null=True)
username = models.CharField(max_length=255, null=True)
full_name = models.CharField(max_length=255, blank=True, null=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
location = models.ForeignKey(Location, on_delete=models.SET_NULL, null=True)
USERNAME_FIELD = 'mobile'
REQUIRED_FIELDS = []
objects = UserManager()
And this is the UserManager,
class UserManager(BaseUserManager):
def create_user(self, mobile, email=None, username=None, full_name=None, password=None, is_staff=False,
is_superuser=False):
if not mobile:
raise ValueError("Can't create User without a mobile number!")
if not password:
raise ValueError("Can't create User without a password!")
user = self.model(
mobile=mobile,
email=self.normalize_email(email),
username=username,
full_name=full_name,
is_staff=is_staff,
is_superuser=is_superuser,
)
user.set_password(password)
user.save(self._db)
return user
This is my UserSerializer Class
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
id = serializers.IntegerField(read_only=True)
class Meta:
model = models.User
fields = (
'id',
'mobile',
'email',
'username',
'full_name',
'password',
)
And this is the view where I'm trying to register a User.
class RegisterView(views.APIView):
def post(self, request):
serialized = UserSerializer(data=request.data)
if serialized.is_valid():
user = UserManager().create_user(mobile=serialized.mobile, email=serialized.email, username=serialized.email, full_name=serialized.full_name, password=serialized.password)
if user:
return Response(serialized.data, status=status.HTTP_201_CREATED)
else:
return Response(serialized.errors, status=status.HTTP_400_BAD_REQUEST)
I end up getting the following error message,
AttributeError at /api/v1/bouncer/register/
'UserSerializer' object has no attribute 'mobile'
But of course I've a mobile attribute. What am I doing wrong here?
.mobile, .email, ... are not on the Serializer object but on the instance. UserSerializer(data=...) returns a Serializer instance. (not a model instance)
If you want to keep your code the solution is to do:
UserManager().create_user(
mobile=serialized.validated_data['mobile'],
email=serialized.validated_data['email'],
...
But this way, you're not taking advantage of the serializer.
Personally, I would get rid of the UserManager. (By the way, the best way to create a manager is to inherit from django.db.models.Queryset and then do object = UserQueryset.as_manager())
I would write a serializer, like yours:
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
id = serializers.IntegerField(read_only=True)
class Meta:
model = models.User
fields = (
'id', 'mobile', 'email', 'username', 'full_name', 'password',
)
def create(self, validated_data):
password = validated_data.pop('password')
user = super().create(validated_data)
user.set_password(password)
return user
Then in your view, you just have to do:
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(...)
else:
return Response(...)
Also, instead of writing a full function, you could use a generic API view. (CreateAPIView is most likely what you want.)
N.B.: Everything is pseudocode, I have not tested it, but the solution should be extremely similar delta some small changes
I'm trying to create a custom user using the Django Rest Framework. I got it to the point to where I can create a regular user, but I'm unsure on how to extend it to the custom user model.
models.py:
from django.db import models
from django.contrib.postgres.fields import ArrayField
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
languages = ArrayField(models.CharField(max_length=30, blank=True))
serializers.py:
from rest_framework import serializers
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = User
fields = ('first_name', 'last_name', 'email', 'username', 'password')
def create(self, validated_data, instance=None):
user = super(UserSerializer, self).create(validated_data)
user.set_password(validated_data['password'])
user.save()
return user
views.py:
#api_view(['POST'])
#permission_classes((AllowAny,))
def create_user(request):
serialized = UserSerializer(data=request.data)
if serialized.is_valid():
serialized.save()
return Response(serialized.data, status=status.HTTP_201_CREATED)
else:
return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST)
How do I extend the languages field to the UserSerializer to where it can process it? Would it be better to create a signal for every time a user is created it then creates a userprofile automatically?
You can do it two ways, either by creating a profile Serializer or without it.
With Serializer,
class UserProfileSerializer(serializers.ModelSerializer):
languages = serializers.ListField(child=serializers.CharField(max_length=30, allow_blank=True))
class Meta:
model = UserProfile
fields = ('languages',)
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
userprofile = UserProfileSerializer(required=False)
class Meta:
model = User
fields = ('first_name', 'last_name', 'email', 'username', 'password', 'userprofile')
def create(self, validated_data, instance=None):
profile_data = validated_data.pop('userprofile')
user = User.objects.create(**validated_data)
user.set_password(validated_data['password'])
user.save()
UserProfile.objects.update_or_create(user=user,**profile_data)
return user
without a profile serializer
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
languages = serializers.ListField(child=serializers.CharField(max_length=30, allow_blank=True), source="userprofile.languages")
class Meta:
model = User
fields = ('first_name', 'last_name', 'email', 'username', 'password', 'lanugages')
def create(self, validated_data, instance=None):
profile_data = validated_data.pop('userprofile')
user = User.objects.create(**validated_data)
user.set_password(validated_data['password'])
user.save()
UserProfile.objects.update_or_create(user=user,**profile_data)
return user