How to double check password in serializer of django restframework? - django

I want to double check password and repeated_password sent from front-end.
{"user_data":{"user_name":"jim", "email":"jim#google.com", "password":"ddd","repeat_password":"ssss","role_list":[1,2,3]}}
And I add a validator in my serilazer as :
# serilazers.py
class SUser(serializers.ModelSerializer):
name = serializers.SerializerMethodField(read_only=True)
repeat_password = serializers.CharField(read_only=True)
role_list = serializers.SerializerMethodField(read_only=True)
def get_name(self, obj):
return obj.user_name
def get_role_list(self, obj):
role_queryset = MRole.objects.filter(group__in=obj.groups.all())
return [
{
"role_id": role.id,
"role_name": role.name,
}
for role in role_queryset
]
def validate(self, attrs):
print(attrs)
# OrderedDict([('user_name', 'jim'), ('email', 'jim#163.com'), ('password', 'ddd')]) there's only password here ,
# why repeat_password not appear?
if value.get('password') == value.get('repeat_password'):
return value
raise exceptions.ValidationError('need same passwd')
class Meta:
model = MUser
fields = ['id', 'name', 'user_name',
'email', 'password', 'repeat_password',
'groups', 'is_active',
'role_list']
# models.py
class Muser(AbstractUser):
user_name = models.CharField(max_length=150,unique=False)
email = models.EmailField(unique=True, blank=True)
class Meta:
db_table = 'User'
verbose_name_plural = verbose_name = 'user'
to validate data
user_data = request.data.get("user_data")
serializer_for_user = SUser(data={
**user_data
})
serializer_for_user.is_valid(raise_exception=True)
But when I try to validate the data, I can't get repeat_password in my validate method of serilazer.
How can I do my double check work of pwd?
Thanks.

try removing read_only=True
repeat_password = serializers.CharField()
In your case, I think you should go for Serializer, not ModelSerializer because toggling the value of read_only will always give you errors.
If you try with read_only=True then the field will be ignored.
If you try it with read_only=False then it will try to save that in your database and will raise the error that this field does not exist in the database.
Read-only fields are included in the API output, but should not be
included in the input during create or update operations. Any
read_only fields that are incorrectly included in the serializer
input will be ignored.
Set this to True to ensure that the field is used when serializing a
representation, but is not used when creating or updating an instance
during deserialization.
https://www.django-rest-framework.org/api-guide/fields/#read_only

Related

How to add extra field to django serializer?

I am trying to add an additional field to my serializer but I am getting the following error:-
"The field 'provider' was declared on serializer CreateUserSerializer, but has not been included in the 'fields' option."
Here is my serializer.py:-
class CreateUserSerializer(serializers.ModelSerializer):
email = serializers.EmailField()
username = serializers.CharField()
company = serializers.CharField()
provider = serializers.CharField()
password = serializers.CharField(write_only=True)
company_detail = serializers.SerializerMethodField()
branch_detail = serializers.SerializerMethodField()
def get_company_detail(self):
return {}
def get_branch_detail(self):
return {}
def create(self, validated_data):
try:
with transaction.atomic():
user = User.objects.create(**validated_data)
user_profile = UserProfileModel.objects.create(user=user)
user_profile.__dict__.update(**validated_data)
user_profile.save()
identity = FederatedIdentityModel.objects.create(user=user, oauth_provider=validated_data['provider'])
company = CompanyModel.objects.create(user=user, name=validated_data['company'])
branch = BranchModel.objects.create(user=user, name=validated_data['company'], company=company)
return user
except APIException:
raise APIException(
detail="Failed to register",
code=status.HTTP_500_INTERNAL_SERVER_ERROR
)
class Meta:
model = User
fields = ['first_name', 'last_name', 'password', 'email', 'username',
'company_detail', 'branch_detail']
I don't want to add the company and provider fields in the field option as it is not a part of user model. I just want to use them as writable fields so that I can create object for the two models.
How can I get rid of the following error?
I think you can't use that field without adding it to fields. What you can do is simply split model & extra fields into two lists and then define:
provider = serializers.CharField()
company = serializers.CharField()
...
class Meta:
model = User
model_fields = ['first_name', 'last_name', 'password', 'email', 'username',
'company_detail', 'branch_detail']
extra_fields = ['company', 'provider']
fields = model_fields + extra_fields
I found an alternative to solve this problem:-
class CreateUserSerializer(serializers.ModelSerializer):
email = serializers.EmailField()
company = serializers.CharField(write_only=True)
provider = serializers.ChoiceField(write_only=True, choices=OAUTH_PROVIDER_CHOICES)
password = serializers.CharField(write_only=True)
company_detail = serializers.SerializerMethodField()
branch_detail = serializers.SerializerMethodField()
# to get the details of company in response body
def get_company_detail(self, obj):
return {}
# to get the details of branch in response body
def get_branch_detail(self, obj):
return {}
def create(self, validated_data):
# pop additional fields from validated data to avoid error
company_name = validated_data.pop('company', None)
provider = validated_data.pop('provider', None)
try:
with transaction.atomic():
# create a user object
user = User.objects.create(**validated_data)
# create a user profile object
user_profile = UserProfileModel.objects.create(user=user)
user_profile.__dict__.update(**validated_data)
user_profile.save()
# create federated identity object using provider and email
identity = FederatedIdentityModel.objects.create(user=user, oauth_provider=provider,
email=validated_data['email'])
# create company object
company = CompanyModel.objects.create(user=user, name=company_name)
return user
except APIException:
raise APIException(
detail="Failed to register",
code=status.HTTP_500_INTERNAL_SERVER_ERROR
)
class Meta:
model = User
fields = ['first_name', 'last_name', 'password', 'email', 'username',
'company', 'provider', 'company_detail', 'branch_detail']
while creating user, I am popping the extra fields and using the popped value as the value while creating specific objects and adding the write_only=True in fields.
In that case, I can add the fields in my field_list without getting any error

Django DRF | Modify parent model field while creating a Child model

I have a User model and another Admin model which has a OneToOne relationship with User model.
I am trying to modify a field in User model while an Admin model is being created.
Here is my Serializer for admin model:
class AdminSerializer(serializers.ModelSerializer):
"""A Serizlier class for vendor """
user = UserSerializer()
class Meta:
model = models.Admin
fields = ('user', 'first_name', 'last_name', 'dob', 'gender')
# This didn't work, also user_type is a field in User model and not Admin model
read_only_fields = ('user_type',)
def create(self, validated_data): # override this method <<<
"""
Since the "user" is a read_only field, the validated data doesn't contain it.
"""
# Line that causes the error. Trying to modify User model field
validated_data['user_type'] = constants.Constants.ADMIN
return super().create(validated_data)
but I get an error:
The .create() method does not support writable nested fields by
default. Write an explicit .create() method for serializer
letzbargain_api.serializers.AdminSerializer, or set read_only=True
on nested serializer fields
How can I fix this?
If I understand your problem correctly, you want to create Admin, but want to keep Admin's user's user_type read-only.
To do that you have to make sure user_type is read-only in your specified user serializer of AdminSerializer. For example, I am writing a new serializer:
class AdminUserSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = ('foo', 'bar', 'user_type')
read_only_fields = ('user_type',)
Now use that one in your AdminSerializer:
class AdminSerializer(serializers.ModelSerializer):
user = AdminUserSerializer() # make sure user_type is read-only in whatever serializer you specify here
class Meta:
model = models.Admin
fields = ('user', 'first_name', 'last_name', 'dob', 'gender')
def create(self, validated_data):
user_data = validated_data.pop('user')
user = models.User.objects.create(**user_data, user_type=constants.Constants.ADMIN)
admin = models.Admin.objects.create(user=user, **validated_data)
return admin

DRF: validate a field from another table

I have created an API to upload the file. Now I want to add few checks before user can upload it. So in payload I am asking his email and token to validate him.
Now email and token are in separate table. How can I validate them. I am getting errors like
TypeError: 'email' is an invalid keyword argument for this function
my models file
class File(models.Model):
filename = models.FileField(blank=False, null=False,upload_to='files')
remark = models.CharField(max_length=20)
timestamp = models.DateTimeField(auto_now_add=True)
my serializer file
class FileSerializer(serializers.ModelSerializer):
token = serializers.CharField(label=_("Token"))
email = serializers.CharField(label=_('email'))
def validate(self, attrs):
print("validating params")
token = attrs.get('token')
email= attrs.get('email')
validate(token, email)
return attrs
class Meta():
model = File
fields = ('filename', 'remark', 'timestamp', 'token', 'email')
read_only_fields = ('token', 'email')
you only want email and token when creating a file(uploading) and also they are not fields in your model, so you should make them write_only and also you should override the create method in serializers and pop them out before saving into the model.
class FileSerializer(serializers.ModelSerializer):
token = serializers.CharField(label=_("Token"), write_only=True)
email = serializers.CharField(label=_('router_macid'), write_only=True)
def validate(self, attrs):
print("validating params")
token = attrs.get('token')
email= attrs.get('email')
validate(token, email)
return attrs
def create(self, validated_data):
validated_data.pop('email', None)
validated_data.pop('token', None)
return super().create(validated_data)
class Meta():
model = File
fields = ('filename', 'remark', 'timestamp', 'token', 'email')

Check if user exists before creating new user djangorestframework

So far I have ->
serializer:
class UserSerializer(serializers.ModelSerializer):
"""Serializer to map the model instance into json format."""
class Meta:
"""Map this serializer to a model and their fields."""
model = User
fields = ('id','username', 'mobile', 'password',
'first_name','last_name','middle_name',
'profile_pic','short_bio','friends_privacy',
'address_1','address_2','city',
'state','country','pin','verification_code',
'is_active','is_blocked','is_reported',
'date_created','date_modified')
extra_kwargs = {'password': {'write_only': True}}
read_only_fields = (
'date_created', 'date_modified',
'is_staff', 'is_superuser', 'is_active',
'date_joined',)
def create(self, validated_data):
mobile_ = validated_data['mobile']
password_ = validated_data['password']
username_ = validated_data['username']
motp = self.context['request'].GET['motp']
eotp = self.context['request'].GET['eotp']
email_ = self.context['request'].GET['email']
mflag = api.views.checkOTP_(mobile_,motp)
eflag = api.views.checkOTP_(email_,eotp)
if (mflag and eflag):
user = User(
username=username_,
email =email_,
password = make_password(password_),
mobile = mobile_,
)
user.set_password(validated_data['password'])
user.save()
return user
view:
class UserView2(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
model = User
def get_permissions(self):
# allow non-authenticated user to create via POST
return (AllowAny() if self.request.method == 'POST'
else IsStaffOrTargetUser()),
I need to check for OTP of mobile and email and also if a user with same mobile or email already exists.
If user already exists return a json response with error: already exists!.
If user is new and OTP is wrong again raise an error.
If user is new and OTP is correct, create an account.
Problem here is I tried to add the function to check for otp verification inside the def create of UserSerializer. But a serializer is supposed to return the model instance. But if you see the code, I am able to create a user only if OTP is right and user instance is returned. And there is no else.
So is there a better way to check for OTP in the view itself?
I don't agree with #Anjaneyulu there..
Serializer handles creation of objects as well.. hence the reason you have serializer.save().
But for the purpose of raising an exception for existing user with same OTP email/phone, you should write your own def validate_mobile(self, data) and def validate_email(self, data). DRF serializer will look for these methods in the class first and will run them if they exist. So your custom logic for checking those fields could be:
class UserSerializer(serializers.ModelSerializer):
def validate_mobile(self, value):
ModelClass = self.Meta.model
if ModelClass.objects.filter(mobile=value).exists():
raise serializers.ValidationError('already exists')
return value
def validate_email_(self, value):
ModelClass = self.Meta.model
if ModelClass.objects.filter(email_=value).exists():
raise serializers.ValidationError('already exists')
return value
class Meta:
model = User
fields = (
...,
)
That is not the correct way of implementing it. Serializers are meant only for validation purposes. you should not implement the create method in serializer instead write it in ViewSet. Creating object is a business logic. It should always go in a ViewSet. Write a validation method to the serializer. I'm writing an example code below
serializers.py
class UserSerializer(serializers.ModelSerializer):
def validate_mobile(self, mobile_num):
is_already_exists = Model.objects.filter(mobile=mobile_num).exists()
if is_already_exists:
raise serializers.ValidationError('already exists')
return mobile_num
class Meta:
model = User
fields = (
'id','username', 'mobile', 'password',
'first_name','last_name','middle_name','profile_pic',
'short_bio','friends_privacy','address_1',
'address_2','city','state','country',
'pin','verification_code','is_active',
'is_blocked','is_reported',
'date_created','date_modified'
)
extra_kwargs = {'password': {'write_only': True}}
read_only_fields = (
'date_created', 'date_modified','is_staff',
'is_superuser', 'is_active', 'date_joined',
)
Viewsets.py(Business Logic)
class UserView2(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def get_permissions(self):
# allow non-authenticated user to create via POST
return (AllowAny() if self.request.method == 'POST'
else IsStaffOrTargetUser()),
def create(self, serializer):
# your logic goes here.

How to save a modelSerializer that has relations? - django

I want to save a sent json data to db by django-rest-framework.
the problem is, not saving the relation and returns error.
The bellow snippet is my models:
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='profile', on_delete=models.CASCADE)
name = models.CharField(max_length=30)
family = models.CharField(max_length=50)
class Klass(models.Model):
title = models.CharField(max_length=50)
description = models.CharField(max_length=500)
teacher = models.ForeignKey(Profile, related_name='teacher', on_delete=models.CASCADE)
I use below serializer for serializing/deserializing the Klass model.
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('pk', 'name', 'family')
class KlassSerializer(serializers.ModelSerializer):
teacher = ProfileSerializer()
class Meta:
model = Klass
fields = ('id', 'title', 'description', 'teacher')
now when I prepare a JSON object and send it to the view, it returns error. the below is the view class:
class KlassView(APIView):
"""for SELECT, INSERT Queries"""
def get(self, request, pk):
# somthing
#csrf_exempt
def post(self,request, pk=None):
"""For Creating A Class"""
serializer = KlassSerializer(data=request.data)
if serializer.is_valid():
teacher = ProfileSerializer(request.data['teacher']['pk'])
serializer.teacher = teacher.data
serializer.save()
return Response({'data': serializer.data})
else:
return Response({'data': serializer.errors})
and the error is:
The .create() method does not support writable nested fields by default.
Write an explicit .create() method for serializer mainp.serializers.KlassSerializer, or set read_only=True on nested serializer fields.
How can I save relation in KlassSerializer in order to save to db?
At first change your serializer like below:
class KlassSerializer(serializers.ModelSerializer):
# teacher = ProfileSerializer() # No need to this!
class Meta:
model = Klass
# fields = ('id', 'title', 'description', 'teacher')
fields = ('id', 'title', 'description') # Omit teacher
Then get profile from requested user and pass it to your serializer:
def post(self,request, pk=None):
"""For Creating A Class"""
serializer = KlassSerializer(data=request.data)
if serializer.is_valid():
teacher = ProfileSerializer(request.data['teacher']['pk'])
serializer.teacher = teacher.data
serializer.save(teacher=request.user.profile) # Retrieve teacher and stroe
return Response({'data': serializer.data})
else:
return Response({'data': serializer.errors})
Just override the create method of ModelSerializer in KlassSerializer.
class KlassSerializer(serializers.ModelSerializer):
teacher = ProfileSerializer()
class Meta:
model = Klass
fields = ('id', 'title', 'description', 'teacher')
def create(self, validated_data):
profile = Profile.objects.filter(pk=validated_data['teacher']['pk'])
if profile:
k = Klass()
k.teacher = profile
...