How to update an object in Django rest framework - django

I have a custom user model, and have its model seriazer to with. Its modelviewset also works in displaying the user list.
class UserSerializer(serializers.ModelSerializer):
password1 = serializers.CharField(write_only=True, style={'input_type': 'password'})
password2 = serializers.CharField(write_only=True, style={'input_type': 'password'})
mobile = serializers.IntegerField()
class Meta:
model = User
fields = ('id', 'first_name', 'last_name', 'email', 'mobile', 'password1', 'password2')
def validate(self, data):
if data['password1'] != data['password2']:
raise serializers.ValidationError({'password1': 'Both password must be equal.'})
return data
def create(self, validated_data):
if self.is_valid(True):
return User.objects.create_user(
validated_data.get('email'),
validated_data.get('first_name'),
validated_data.get('last_name'),
validated_data.get('mobile'),
validated_data.get('password1'),
)
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
However, I am trying to update a User object by posting the model's fields and its value in json key value pair format in PUT http method.
{
"first_name": "Some"
"last_person": "name"
"mobile": "0123456789"
}
But this gives me a 400 bad request error:
{
"password1": [
"This field is required."
],
"password2": [
"This field is required."
],
"email": [
"This field is required."
]
}
How can I update an object with only the partial model fields?

How can I update an object with only the partial model fields?
That's what PATCH is for as opposed to PUT.
It'll pass the partial flag to the serializer will will bypass missing field's validation.

Serializer is considering this fields as obligatory, probably you are putting in your custom model null = False.

Related

Single Update and Delete API for two models connected with a OneToOne relationship in Django Rest Framework

I've looked extensively on here and probably exhausted all the answers and still haven't found a solution to my particular problem, which is to make an API that update/delete from both models, and I am getting the following error:
The .update()method does not support writable nested fields by default. Write an explicit.update()method for serializeruser_profile.serializers.UserSerializer, or set read_only=True on nested serializer fields.
In this particular instance this happens when I try to update a field from the user_profile model
I have separated my Django project into several apps/folders with each model being in its own folder.
I have a user app and a user_profile app each with their own models.
the user model is basically an AbstractUser sitting in its own app
the user_profile model is as follows:
class UserProfile(models.Model):
user = models.OneToOneField(to=User, on_delete=models.CASCADE, related_name='userprofile')
location = models.CharField(blank=True, max_length=30)
created_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True)
The serializers are as follows:
class UserProfileCrudSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('location', 'created_time', 'updated_time')
class UserSerializer(serializers.ModelSerializer):
profile = UserProfileCrudSerializer(source='userprofile', many=False)
class Meta:
model = User
fields = ('username', 'email', 'first_name', 'last_name', 'profile')
def update(self, instance, validated_data):
userprofile_serializer = self.fields['profile']
userprofile_instance = instance.userprofile
userprofile_data = validated_data.pop('userprofile', {})
userprofile_serializer.update(userprofile_instance, userprofile_data)
instance = super().update(instance, validated_data)
return instance
and my view is:
class RetrieveUpdateView(RetrieveUpdateAPIView):
serializer_class = UserSerializer
queryset = User.objects.all()
def get_object(self):
return self.request.user
when I do a GET I am getting the following response without any problems:
{
"username": "blue",
"email": "bluebear#bluebear.com",
"first_name": "Blue",
"last_name": "Bear",
"profile": {
"location": "London",
"created_time": "2023-02-03T00:39:15.149924Z",
"updated_time": "2023-02-03T00:39:15.149924Z"
}
}
and I do a patch request like this:
{
"profile": {
"location": "Paris"
}
}
The way the code is now I have no issue updating username, email, first_name, and last_name which come from the AbstractUser but I am getting the above error when I try to patch the location which is in the UserProfile model.
I've looked at many similar solutions online, but none that pertain to my particular situation.
The .update() method does not support writable nested fields by default. Write an explicit .update() method for serializeruser_profile.serializers.UserSerializer, or set read_only=True on nested serializer fields.
It already shows in the message, you need to explicitly write the update method for the writable nested serializer which is documented here https://www.django-rest-framework.org/topics/writable-nested-serializers/ or you can use another module that is also referred to in the docs https://github.com/beda-software/drf-writable-nested.
Your approach is correct already but there is some typo and wrong indentation in your code:
class UserSerializer(serializers.ModelSerializer):
profile = UserProfileCrudSerializer(source='userprofile', many=False)
class Meta:
model = User
fields = ('username', 'email', 'first_name', 'last_name', 'profile')
def update(self, instance, validated_data):
# update is a method of serializer not serializer.Meta
userprofile_serializer = self.fields['profile']
userprofile_instance = instance.userprofile
# should be 'profile' here instead of 'userprofile' as you defined in serializer
userprofile_data = validated_data.pop('profile', {})
userprofile_serializer.update(userprofile_instance, userprofile_data)
instance = super().update(instance, validated_data)
return instance

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 use a nested serialize for serializing and deserializing data? django-rest-framework

I have following serializers:
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'password')
class ProfileSerializer(ModelSerializer):
user = UserSerializer()
class Meta:
model = Profile
fields = ('id', 'user', 'name', 'address')
when I want to create a profile I should send following data:
{
"user":{
"username": "test_username",
"password": "123456789"
},
"name": "David",
"address": "Baker St"
}
my question is, is it possible to just send "user": 5 instead of sending dictionary in case of POST request?
You have two options, either you can use two different serializers for retrieve and create like this
class ProfileCreateSerializer(ModelSerializer):
class Meta:
model = Profile
fields = ('id', 'user', 'name', 'address')
and
class ProfileRetrieveSerializer(ProfileCreateSerializer):
user = UserSerializer()
and decide which serializer to use in view (hint: override get_serializer_class method)
OR
Use one serialzer and decide field type according to action type:
class ProfileCreateSerializer(ModelSerializer):
class Meta:
model = Profile
fields = ('id', 'user', 'name', 'address')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.context['request'].method == 'GET': # or whatever condition you want to use
self.fields['user'] = UserSerializer()
you can do, that you can create your custom serializer fields
class CustomForeignKeyField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
return self.queryset
def to_representation(self, value):
value = super().to_representation(value)
user = User.objects.get(pk=value)
return UserSerializer(user).data
in serializer you can use this fiedls
class ProfileSerializer(ModelSerializer):
user = CustomForeignKeyField(queryset=User.objects.all())
class Meta:
model = Profile
fields = ('id', 'user', 'name', 'address')
it will accept value as int and return the response in json

Add group to user in Django

I have created 3 groups 1. Staff, 2. Admin, 3. Operational Manager and assigned permission. Now whenever I wanted to add new user I wanted to show the choice field of the group and able to select the group.
As per the framework the User and Groups have many-to-many relations and I am trying to implement nested serializer. And as per documentation to add nested data I need to implement create or update method in the serializer but here I am not getting the choice field in group and the data is null.
GroupSerializer:
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ('name',)
UserSerializers:
class UserSerializers(serializers.ModelSerializer):
groups = GroupSerializer(many=True)
class Meta:
model = User
fields = ('first_name', 'last_name','address','contact', 'email', 'date_of_birth', 'branch', 'groups')
def create(self, validated_data):
groups_data = validated_data.pop('groups')
user = User.objects.create(**validated_data)
for group_data in groups_data:
Group.objects.create(user=user, **group_data)
return user
when I wanted to make a post request I am getting :
{
"first_name": "",
"last_name": "",
"address": "",
"contact": null,
"email": "",
"date_of_birth": null,
"branch": null,
"groups": []
}
here groups fields are empty.
If I try to make a post request it says
{
"groups": [
"This field is required."
]
}
Edit : Solved
I removed GroupSerializer because User model has many-to-many relation with Group so we don't need to explicitly specify Group object
# class GroupSerializer(serializers.ModelSerializer):
# class Meta:
# model = Group
# fields = ('name',)
class UserSerializers(serializers.ModelSerializer):
# groups = GroupSerializer(many=True)
class Meta:
model = User
fields = ('username','first_name', 'last_name','address','contact', 'email', 'date_of_birth', 'branch', 'groups')
def create(self, validated_data):
groups_data = validated_data.pop('groups')
user = User.objects.create(**validated_data)
for group_data in groups_data:
# Group.objects.create(user=user, **group_data)
user.groups.add(group_data)
return user
You can use PrimarykeyRelatedField and provide id of groups that you want to add in a list. like below:
class UserSerializers(serializers.ModelSerializer):
groups = serializers.PrimaryKeyRelatedField(queryset=Group.objects.all())
class Meta:
model = User
fields = ('first_name', 'last_name', 'address', 'contact', 'email', 'date_of_birth', 'branch', 'groups')
def create(self, validated_data):
groups_data = validated_data.pop('groups')
user = User.objects.create(**validated_data)
for group_data in groups_data:
Group.objects.create(user=user, **group_data)
return user
def to_representation(self, instance):
r = super(UserSerializers, self).to_representation(instance)
r.update({
'groups': GroupSerializer(many=True).to_representation(instance.groups)
})
return r
So just provide list of groups like this: "groups": [1,2,3]
and you should also override to_representation method if you want to have serialized json result of each group in response.

Django Rest Framework make OnetoOne relation ship feel like it is one model

I have my User saved in two different models, UserProfile and User. Now from API perspective, nobody really cares that these two are different.
So here I have:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'first_name', 'last_name', 'email')
and
class UserPSerializer(serializers.HyperlinkedModelSerializer):
full_name = Field(source='full_name')
class Meta:
model = UserProfile
fields = ('url', 'mobile', 'user','favourite_locations')
So in UserPSerializer the field user is just a link to that resource. But form a User perspective there is really no reason for him to know about User at all.
Is there some tricks with which I can just mash them together and present them to the user as one model or do I have to do this manually somehow.
You can POST and PUT with #kahlo's approach if you also override the create and update methods on your serializer.
Given a profile model like this:
class Profile(models.Model):
user = models.OneToOneField(User)
avatar_url = models.URLField(default='', blank=True) # e.g.
Here's a user serializer that both reads and writes the additional profile field(s):
class UserSerializer(serializers.HyperlinkedModelSerializer):
# A field from the user's profile:
avatar_url = serializers.URLField(source='profile.avatar_url', allow_blank=True)
class Meta:
model = User
fields = ('url', 'username', 'avatar_url')
def create(self, validated_data):
profile_data = validated_data.pop('profile', None)
user = super(UserSerializer, self).create(validated_data)
self.update_or_create_profile(user, profile_data)
return user
def update(self, instance, validated_data):
profile_data = validated_data.pop('profile', None)
self.update_or_create_profile(instance, profile_data)
return super(UserSerializer, self).update(instance, validated_data)
def update_or_create_profile(self, user, profile_data):
# This always creates a Profile if the User is missing one;
# change the logic here if that's not right for your app
Profile.objects.update_or_create(user=user, defaults=profile_data)
The resulting API presents a flat user resource, as desired:
GET /users/5/
{
"url": "http://localhost:9090/users/5/",
"username": "test",
"avatar_url": "http://example.com/avatar.jpg"
}
and you can include the profile's avatar_url field in both POST and PUT requests. (And DELETE on the user resource will also delete its Profile model, though that's just Django's normal delete cascade.)
The logic here will always create a Profile model for the User if it's missing (on any update). With users and profiles, that's probably what you want. For other relationships it may not be, and you'll need to change the update-or-create logic. (Which is why DRF doesn't automatically write through a nested relationship for you.)
I just came across this; I have yet to find a good solution particularly for writing back to my User and UserProfile models. I am currently flattening my serializers manually using the SerializerMethodField, which is hugely irritating, but it works:
class UserSerializer(serializers.HyperlinkedModelSerializer):
mobile = serializers.SerializerMethodField('get_mobile')
favourite_locations = serializers.SerializerMethodField('get_favourite_locations')
class Meta:
model = User
fields = ('url', 'username', 'first_name', 'last_name', 'email', 'mobile', 'favourite_locations')
def get_mobile(self, obj):
return obj.get_profile().mobile
def get_favourite_locations(self, obj):
return obj.get_profile().favourite_locations
This is horribly manual, but you do end up with:
{
"url": "http://example.com/api/users/1",
"username": "jondoe",
"first_name": "Jon",
"last_name": "Doe",
"email": "jdoe#example.com",
"mobile": "701-680-3095",
"favourite_locations": [
"Paris",
"London",
"Tokyo"
]
}
Which, I guess is what you're looking for.
I would implement the modifications on the UserPSerializer as the fields are not going to grow:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'first_name', 'last_name', 'email')
class UserPSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.CharField(source='user.url')
username = serializers.CharField(source='user.username')
first_name = serializers.CharField(source='user.first_name')
last_name = serializers.CharField(source='user.last_name')
email = serializers.CharField(source='user.email')
class Meta:
model = UserProfile
fields = ('mobile', 'favourite_locations',
'url', 'username', 'first_name', 'last_name', 'email')