DRF: validate a field from another table - django

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

Related

Django rest framework: mixing some fields of one model with another and make a serializer

I have the User model and UserProfile model.
User model contains the following:
email
password
first_name
last_name
and
UserProfile model contains
class UserProfile(models.Model):
user = models.OneToOneField(on_delete=models.CASCADE, primary_key=True)
photo = models.ImageField()
location = models.TextField()
phone = models.CharField(max_length=15)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
I want to create an api endpoint where User can edit the following fields and save after login
--> from User
first_name
last_name
--> from Userprofile
photo
location
phone
In normal Django forms. I can use multiple forms and validate them and save. User will see them as one form.
Eg:
if request.POST():
formA = UserForm(instance=request.user)
formB = UserProfileForm(instance=request.user.userprofile_set)
a_valid = formA.is_valid()
b_valid = formB.is_valid()
# we do this since 'and' short circuits and we want to check to whole page for form errors
if a_valid and b_valid:
a = formA.save()
b = formB.save()
In serializers how can I do this
You can do something like this:
UserSerializer
class UserSerializer(serializer.ModelSerializer):
class Meta:
model = User
fields = ['first_name', 'last_name']
def update(instance, validated_data):
instance.first_name = validated_data.get('first_name', instance.first_name)
instance.last_name = validated_data.get('last_name', instance.last_name)
instance.save()
return instance
UserProfileSerializer
class UserProfileSerializer(serializer.ModelSerializer):
class Meta:
model = UserProfileSerializer
fields = ['photo', 'location', 'phone']
def update(instance, validated_data):
instance.photo = validated_data.get('photo', instance.photo)
instance.location = validated_data.get('location', instance.location)
instance.phone = validated_data.get('phone', instance.phone)
instance.save()
return instance
Then create a view like this which receives data in this format:
{
user: {first_name: "", last_name: ""},
profile:{photo: "", location: "", phone: ""}
}
The view
class UserDetailUpdateView(APIView):
def post(self, request, username):
user_data = request.data['user']
profile_data = request.data['profile']
user = get_object_or_404(User, username=username)
serialized_user = UserSerializer(user, data=user_data, partial=True)
serialized_profile = UserProfileSerializer(user.userprofile, data=profile_data, partial=True)
if serialized_user.is_valid() and serialized_profile.is_valid():
serialized_user.save()
serialized_profile.save()
return Response('Updated', status=status.HTTP_200_OK)
return Response('Error occurred', status=status.HTTP_400_BAD_REQUEST)

How to double check password in serializer of django restframework?

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

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

How to fix 'Error validation unque field by serializer when update instance'

I have a model of Organisation and three models have Foreign keys to Organisation model. Three nested models is Users ( custom model ), Description and Contacts. Users has unique field email. Description has unique pair of two fields. I have custom serializer to Organisation.
class OrganisationSuperAdminSerializer(serializers.ModelSerializer):
users = UsersSerializer(many=True, required=False)
contacts = ContactsSerializer(many=True, required=False)
description = DescriptionOrganisationSerializer(many=False, required=False)
class Meta:
model = Organisation
fields = '__all__'
def create(self, validated_data):
error_msg = 'Save error'
users_data = validated_data.pop('users')
contacts_data = validated_data.pop('contacts')
description_data = validated_data.pop('description')
organisation = Organisation.objects.create(**validated_data)
try:
for user_data in users_data:
Users.objects.create(organisation=organisation, **user_data)
for contact_data in contacts_data:
Contacts.objects.create(organisation=organisation, **contact_data)
DescriptionOrganisation.objects.create(organisation=organisation, **description_data)
except:
organisation.delete()
raise serializers.ValidationError(error_msg)
return {}
def update(self, instance, validated_data):
pass
When I save, everything goes well. But when I try to update, the serializer fails validation. The error text in the comments.
"""
Класс для работы с данными для супер админа
"""
queryset = Organisation.objects.all()
serializer_class = OrganisationSuperAdminSerializer
permission_classes = [permissions.AllowAny, ]
def update(self, request, pk=None, *args, **kwargs):
serializer: serializers.ModelSerializer = self.get_serializer(self.get_object(), data=request.data)
print(serializer.is_valid()) # False
print(serializer.errors) # {'users': [{'email': [ErrorDetail(string='email must be unique', code='unique')]}], 'description': {'non_field_errors': [ErrorDetail(string='The fields inn, kpp must make a unique set.', code='unique')]}}
return response.Response(status=200)
I don't want to disable validation of unique fields. But I can't find information how to validate through the serializer update.
Other serializers:
class UsersSerializer(serializers.ModelSerializer):
email = serializers.CharField(max_length=128,
validators=[validators.UniqueValidator(
queryset=Users.objects.all(),
message='email must be unique'
)]
)
class Meta:
model = Users
fields = '__all__'
class DescriptionOrganisationSerializer(serializers.ModelSerializer):
organisation = serializers.PrimaryKeyRelatedField(required=False, queryset=DescriptionOrganisation.objects.all())
class Meta:
model = DescriptionOrganisation
fields = '__all__'
class ContactsSerializer(serializers.ModelSerializer):
organisation = serializers.PrimaryKeyRelatedField(required=False, queryset=Contacts.objects.all())
class Meta:
model = Contacts
fields = '__all__'

DRF IntegrityError: NOT NULL constraint failed: user_id

I can't figure out how to pass user object to the following serializer:
class ReviewSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = Review
fields = ('pk', 'title', 'user', 'movie', 'timestamp', 'review_text',)
I have this viewset:
class ReviewsViewSet(viewsets.ModelViewSet):
queryset = Review.objects.all()
serializer_class = ReviewSerializer
and this model:
class Review(models.Model):
title = models.CharField(max_length=255)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reviews')
movie = models.ForeignKey(Movie, on_delete=models.CASCADE, related_name='reviews')
review_text = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '{movie} review by {user}'.format(user=self.user, movie=self.movie)
My javascript request looks like this:
return axios({
method: 'post',
url: 'http://localhost:8000/api/reviews/',
data: { // Using data from Vue
title: this.review_title,
movie: this.id,
review_text: this.review_text,
user: JSON.stringify(this.user)
},
headers: {
'Content-Type': 'application/json',
Authorization: `JWT ${token}`
}
})
It gives me this traceback.
How should I pass the user object to the request?
Thanks in advance.
Remove read_only=True from serializer
class ReviewSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Review
fields = ('pk', 'title', 'user', 'movie', 'timestamp', 'review_text',)
If you set read_only=True, the DRF will not takes the value from input source even if it's there
From the doc,
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.
Defaults to False
UPDATE
You should override the create() method of ReviewSerializer as
class ReviewSerializer(serializers.ModelSerializer):
user = UserSerializer()
def create(self, validated_data):
user_dict = validated_data.pop('user')
user_obj, created = User.objects.get_or_create(**user_dict)
return Review.objects.create(user=user_obj, **validated_data)
class Meta:
model = Review
fields = ('pk', 'title', 'user', 'movie', 'timestamp', 'review_text',)
for debug purpose only
class ReviewsViewSet(viewsets.ModelViewSet):
queryset = Review.objects.all()
serializer_class = ReviewSerializer
def create(self, request, *args, **kwargs):
print(request.data) # print here <<<<
return super(ReviewsViewSet, self).create(request, *args, **kwargs)