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

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

Related

Django rest framework updating a OneToOne field

I have a User model that inherits from AbstractUser which has an email field. And a profile model that has an OneToOne relation with the User model
class User(AbstractUser):
email = models.EmailField(unique=True)
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
phone = models.CharField(max_length=13, validators=[phone_regex], unique=True, null=True, blank=True)
birth_date = models.DateField(blank=True, null=True)
about = models.TextField(max_length=2000, blank=True)
def __str__(self):
return f"{self.user.first_name} {self.user.last_name}"
view.py
class ProfileViewSet(ModelViewSet):
....
#action(detail=False, methods=["GET", "PUT"], permission_classes=[IsAuthenticated])
def me(self, request, *args, **kwargs):
profile = Profile.objects.get(user_id=request.user.id)
if request.method == "GET":
serializer = ProfileSerializer(profile)
return Response(serializer.data)
elif request.method == "PUT":
serializer = ProfileSerializer(profile, request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
serializers.py
class ProfileSerializer(serializers.ModelSerializer):
user = CurrentUserSerializer()
def update(self, instance, validated_data):
user_data = validated_data.pop('user')
user_ser = CurrentUserSerializer(instance=instance.user, data=user_data)
if user_ser.is_valid():
user_ser.save()
instance.phone = validated_data.get('phone', instance.phone)
instance.birth_date = validated_data.get('birth_date', instance.birth_date)
instance.about = validated_data.get('about', instance.about)
instance.save()
return instance
class Meta:
model = Profile
fields = [
'id',
'user',
'phone',
'birth_date',
'about',
]
Now when I try to update a user profile I get status: 400 Bad Request error
{
"user": {
"email": [
"user with this email already exists."
]
}
}
using patch instead of put or partial=True doesn't change anything I still get this error. What can I do here?
The thing is you are creating new record on data base and It is not permitted
class ProfileSerializer(serializers.ModelSerializer):
user = CurrentUserSerializer()
def update(self, instance, validated_data):
user_data = validated_data.pop('user')
try:
user=User.objects.get(pk=instance.user.id)
except User.DoesNotExist:
raise ValidationError("User already exist")
#now with user instance you can save the email or do whatever
you want
user.email = "xyaz#gmail.com")
user.save()
instance.phone = validated_data.get('phone', instance.phone)
instance.birth_date = validated_data.get('birth_date', instance.birth_date)
instance.about = validated_data.get('about', instance.about)
instance.save()
return instance
class Meta:
model = Profile
fields = [
'id',
'user',
'phone',
'birth_date',
'about',
]
Instead of a nested serializer I used serializer fields and my problem was solved. I'm not sure if it's a good approach or not.
class ProfileSerializer(serializers.ModelSerializer):
email = serializers.EmailField(source='user.email')
first_name = serializers.CharField(source='user.first_name')
last_name = serializers.CharField(source='user.last_name')
def update(self, instance, validated_data):
user_data = validated_data.pop('user')
user = instance.user
user_ser = CurrentUserSerializer(instance=user, data=user_data)
if user_ser.is_valid():
user_ser.save()
instance.phone = validated_data.get('phone', instance.phone)
instance.birth_date = validated_data.get('birth_date', instance.birth_date)
instance.about = validated_data.get('about', instance.about)
instance.save()
class Meta:
model = Profile
fields = [
'id',
'email',
'first_name',
'last_name',
'phone',
'birth_date',
'about',
]

Django Attach variables from one step to another

How would I grab the 2nd form and add it to the first form then selectively not allow that user to login. In the Doctorwizard done function.
maybe add a variable status?
etc.
username,password,email,first_name,last_name,verified
views.py
from django.core.files.storage import FileSystemStorage
import os
from django.conf import settings
class DoctorWizard(SessionWizardView):
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'doctor'))
template_name = "registration/signup.html"
form_list = [SignUpForm,verify]
def done(self, form_list, **kwargs):
data=process_data(form_list)
return redirect('home')
forms.py
class SignUpForm(UserCreationForm):
first_name = forms.CharField(max_length=30, required=False, help_text='Optional.')
last_name = forms.CharField(max_length=30, required=False, help_text='Optional.')
email = forms.EmailField(max_length=254, help_text='Required. Inform a valid email address.')
class Meta:
model = Profile
fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2', )
class verify(forms.Form):
verified = forms.ImageField(required=True)
class Meta:
model = Profile
fields = ('verified',)
models.py
class Profile(AbstractUser):
bio = models.TextField(max_length=100, blank=True)
phone_number = PhoneNumberField(max_length=25, region="US")
birth_date = models.DateField(blank = True, null = True)
is_doctor = models.BooleanField(default=False)
verified = models.ImageField(upload_to='media/doctor')
date_created = models.DateTimeField(auto_now_add=True)
avatar = models.ImageField(default='default.png', upload_to='')
def done(self, form_list, **kwargs):
process_data(form_list)
userCreate = form_list[0]
userCreate.save()
username = userCreate.cleaned_data.get('username')
raw_password = userCreate.cleaned_data.get('password1')
user = authenticate(username=username, password=raw_password)
if user:
user.verified=form_list[1].cleaned_data.get('verified')
user.is_doctor=True
user.is_active=False
user.save()
Just grab the user and access it's fields.

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

Include auto increment field to JSON response

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

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