Include auto increment field to JSON response - django

New to Django and Django Rest.
I am looking to modify the JSON return response of my view so that it also returns the id of the created user.
The things is, the id is an auto increment integer and so I don't know how I can retrieve the info to add it to the JSON.
Any help would be appreciated.
Thanks.
models.py
class CustomUsers(AbstractUser):
email = models.EmailField(unique=True)
username = models.CharField(max_length=100)
USERNAME_FIELD = 'email'
class Meta:
db_table = "custom_users"
serializers.py
class CustomUsersCreateSerializer(serializers.ModelSerializer):
def create(self, validated_data):
last_name = validated_data['last_name']
first_name = validated_data['first_name']
username = validated_data['username']
email = validated_data['email']
password = validated_data['password']
user_obj = USER(
last_name=last_name,
first_name=first_name,
username=username,
email=email,
)
user_obj.set_password(password)
user_obj.save()
return validated_data
class Meta:
model = USER
fields = ('id', 'last_name', 'first_name', 'username', 'password', 'email')
extra_kwargs = {'password': {'write_only': True, 'min_length': 10}}
views.py
class UserCreateAPIView(CreateAPIView):
serializer_class = serializers.CustomUsersCreateSerializer
queryset = CustomUsers.objects.all()

I usually use Model.objects.create() to add new record but i think it's the same as your way of using save() so you can get created user id after save:
def create(self, validated_data):
last_name = validated_data['last_name']
first_name = validated_data['first_name']
username = validated_data['username']
email = validated_data['email']
password = validated_data['password']
user_obj = User.objects.create(
last_name=last_name,
first_name=first_name,
username=username,
email=email,
)
user_obj.set_password(password)
user_obj.save()
validated_data['user_id'] = user_obj.id
return validated_data

Related

Not hashing password. Unexpected keyword argument 'password2'

I have three different questions. They're all related to each other.
1 - I get an error when I add password2 or confirm_password field.
Got a `TypeError` when calling `CustomUser.objects.create()`.
This may be because you have a writable field on the serializer class that is not a valid argument to `CustomUser.objects.create()`.
You may need to make the field read-only, or override the UserCreateSerializer.create() method to handle this correctly.
TypeError: CustomUser() got an unexpected keyword argument 'confirm_password'
2 - Without any validate when I only have password field. I am not getting any errors but passwords are not hashed.
3 - When I want to create user using shell. Even if I leave all the fields blank, I can create an empty user without any errors.
Custom User Manager
class CustomUserManager(BaseUserManager):
def create_user(self, email, password=None,**kwargs):
if not email:
raise ValueError('Email is required!')
user = self.model(
email = self.normalize_email(email),
**kwargs,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password=None, **kwargs):
user = self.create_user(
email,
password=password,
**kwargs
)
user.is_admin = True
user.save(using=self._db)
return user
Custom User
class CustomUser(AbstractBaseUser):
email = models.EmailField(_('Email'), max_length=50, unique=True)
username = models.CharField(_('Username'),max_length=50)
first_name = models.CharField(_('First Name'),max_length=50)
middle_name = models.CharField(_('Middle Name'),max_length=50, blank=True, null=True)
last_name = models.CharField(_('Last Name'),max_length=50)
desc = models.TextField(_('Description'), blank=True, default='Nothing here.')
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
objects = CustomUserManager()
USERNAME_FIELD = 'email'
EMAIL_FIELD = 'email'
REQUIRED_FIELDS = []
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
return True
def has_module_perms(self, app_label):
return True
#property
def is_staff(self):
"Is the user a member of staff?"
return self.is_admin
User Serializer
User = get_user_model()
class UserCreateSerializer(serializers.ModelSerializer):
email = serializers.EmailField(required=True, validators =[validators.UniqueValidator(queryset=User.objects.all())])
password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
confirm_password = serializers.CharField(write_only=True, required=True)
class Meta:
model = User
fields = [
'email',
'username',
'first_name',
'middle_name',
'last_name',
'password',
'confirm_password',
]
def validate(self, data):
email = data['email']
print(data)
user_qs = User.objects.filter(email=email)
if user_qs.exists():
raise serializers.ValidationError('This user has already registered!')
if data['password'] != data['confirm_password']:
raise serializers.ValidationError('Passwords didn\'t match!')
elif data.get('password') == data.get('confirm_password'):
data['password'] = make_password(
data.get('password')
)
data.pop('confirm_password', None)
return data
def create(self, validated_data):
username = validated_data['username']
email = validated_data['email']
first_name = validated_data['first_name']
middle_name = validated_data['middle_name']
last_name = validated_data['last_name']
user = User.objects.create(
username = username,
email = email,
first_name=first_name,
middle_name=middle_name,
last_name=last_name,
)
user.set_password(validated_data['password'])
user.save()
return user

Django Rest Framework API sets image to default image even if user uploads an image

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")

Django-rest-auth (dj-rest-auth) custom user registration

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

Save two model instances in one updateview

Am trying to update the User model and UserProfile model in one view but it's not working. No error is shown and no changes are made to the objects. What am I not doing right.
Here is my models.py:
class UserProfile(models.Model):
"""User information not related to authentication"""
user = models.OneToOneField(User, related_name='user_profile', on_delete=models.CASCADE)
age = models.IntegerField()
# other fields ignored
Here is my serializer.py:
class UserSerializer(ModelSerializer):
first_name = CharField(max_length=20)
last_name = CharField(max_length=20)
email = EmailField(required=True, validators=[UniqueValidator(queryset=User.objects.all())])
username = CharField(max_length=32,validators=[UniqueValidator(queryset=User.objects.all())])
password = CharField(min_length=8, write_only=True)
confirm_password = CharField(write_only=True)
def create(self, validated_data):
user = User.objects.create_user(
validated_data['username'],
email = validated_data['email'],
first_name = validated_data['first_name'],
last_name = validated_data['last_name']
)
password = validated_data['password']
confirm_password = validated_data['confirm_password']
if password != confirm_password:
raise ValidationError({'password': 'Passwords must match'})
else:
user.set_password(password)
user.save()
return user
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'password', 'confirm_password')
class UserProfileSerializer(ModelSerializer):
username = CharField(source='user.username')
first_name = CharField(source='user.first_name')
last_name = CharField(source='user.last_name')
email = CharField(source='user.email')
class Meta:
model = UserProfile
exclude = ('user',)
# fields = '__all__'
# depth = 1
def update(self, instance, validated_data):
user = instance.user
instance.user.username = validated_data.get('username', instance.user.username)
instance.user.email = validated_data.get('email', instance.user.email)
instance.user.first_name = validated_data.get('first_name', instance.user.first_name)
instance.user.last_name = validated_data.get('last_name', instance.user.last_name)
instance.save()
user.save()
return instance
Here is view.py:
class UserProfileUpdate(UpdateAPIView):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
lookup_field = 'user'
#Eric
Try changing your update method to this, the actual update data is under validated_data['user']
def update(self, instance, validated_data):
user = instance.user
instance.user.username = validated_data['user'].get('username', instance.user.username)
instance.user.email = validated_data['user'].get('email', instance.user.email)
instance.user.first_name = validated_data['user'].get('first_name', instance.user.first_name)
instance.user.last_name = validated_data['user'].get('last_name', instance.user.last_name)
instance.save()
user.save()
return instance

Django-Rest-Framework Custom User not Hashing Password (Serializer Issue)

I am trying to use token authentication, but it is not working due to my create user serializer not hashing the passwords. I am able to login with the superuser as that has a hashed password. Using rest_auth and rest_framework.authtoken. The user.set_password command is supposed to hash the password, so is there an issue with the prior code?
class CreateUserSerializer(serializers.HyperlinkedModelSerializer):
username = serializers.CharField()
password = serializers.CharField(write_only = True, style = {'input_type': 'password'})
class Meta:
model = get_user_model()
fields = (
'id','username', 'password',
'email', 'first_name', 'last_name'
)
write_only_fields = ('password')
read_only_fields = ('is_staff', 'is_superuser', 'is_active')
def create(self, validated_data):
password = validated_data.pop('password')
user = super().create(validated_data)
user.set_password(validated_data['password'])
user.save()
return user
class CreateUserAPIView(CreateAPIView):
"""
Create a new user.
"""
serializer_class = CreateUserSerializer
permission_classes = [AllowAny]
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data = request.data)
serializer.is_valid(raise_exception = True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
# Create a token that will be used for future auth
token = Token.objects.create(user = serializer.instance)
token_data = {"token": token.key}
return Response(
{**serializer.data, **token_data},
status = status.HTTP_201_CREATED,
headers = headers
)
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20,
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = (
'url', 'username', 'email', 'groups', 'workflow_step',
'first_name', 'last_name',
'birthdate',
'address_street1', 'address_street2', 'address_city',
'address_state', 'address_postal_code', 'address_country', 'phone'
)
class User(AbstractUser):
# Application process
workflow_step = models.CharField(max_length=100, default='', blank=True)
is_verified = models.BooleanField(default=False)
# Basic information
# first_name (in django.contrib.auth.models.User)
# last_name (in django.contrib.auth.models.User)
# email (in django.contrib.auth.models.User)
# Advanced Information
birthdate = models.DateField(blank=True, null=True)
address_street1 = models.CharField(max_length=100, blank=True)
address_street2 = models.CharField(max_length=100, default='', blank=True)
address_city = models.CharField(max_length=100, blank=True)
address_state = models.CharField(max_length=50, blank=True)
address_postal_code = models.CharField(max_length=30, blank=True)
address_country = models.CharField(max_length=100, blank=True)
phone = models.CharField(max_length=30, blank=True)
This is probably too late, but for anyone who has this issue. you need to put the create function directly inside the serializer class, in your case you have it in the Meta subclass
The second thing you need to do is to use
def create(self, validated_data):
password = validated_data.pop('password')
user = super().create(validated_data)
user.set_password(password)
user.save()
return user
best of luck
In CreateUserSerializer.create you're doing this:
password = validated_data.pop('password')
...
user.set_password(validated_data['password'])
By the time you call set_password the passwordkey has been removed from validated_data. You probably want to change the set_password line to this instead:
user.set_password(password)
You can use the make_passowrd function in order of hashing it:
class CreateUserSerializer(serializers.HyperlinkedModelSerializer):
username = serializers.CharField()
password = serializers.CharField(write_only = True, style = {'input_type': 'password'})
class Meta:
model = get_user_model()
fields = (
'id','username', 'password',
'email', 'first_name', 'last_name'
)
write_only_fields = ('password')
read_only_fields = ('is_staff', 'is_superuser', 'is_active')
def create(self, validated_data):
password = validated_data.pop('password')
user = super().create(validated_data)
user.set_password( make_password(validated_data['password']))
user.save()
return user
Read all about password managing here
You are removing the 'password' key before hashing it.
You need to change
user.set_password(validated_data['password']) this to
user.set_password(password) as you popped that from validated data and stored to password variable.
This works for me..try this
class UserSerializer(serializers.ModelSerializer):
# <Your other UserSerializer stuff here>
def create(self, validated_data):
password = validated_data.pop('password', None)
instance = self.Meta.model(**validated_data)
if password is not None:
instance.set_password(password)
instance.save()
return instance