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

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

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 set read_only dynamically in django rest framework?

I am trying to check if the user id not equal to 1 then he should not be able to update few fields. I tried something similar to the following code but it did not work because of the following issues
self.user.id don't actually return the user I need to get the authenticated user in different why?
the def function maybe should have a different name like update?
also the general way maybe wrong?
class ForAdmins(serializers.ModelSerializer)):
class Meta:
model = User
fields = '__all__'
class ForUsers(serializers.ModelSerializer)):
class Meta:
read_only_fields = ['email','is_role_veryfied','is_email_veryfied']
model = User
fields = '__all__'
class UsersSerializer(QueryFieldsMixin, serializers.ModelSerializer):
def customize_read_only(self, instance, validated_data):
if (self.user.id==1):
return ForAdmins
else:
return ForUsers
class Meta:
# read_only_fields = ['username']
model = User
fields = '__all__'
You can make the decision which serializer you want to pass from your views
or
you can do it inside modelSerializer update method.
for getting user from Serializer class Try:
request = self.context.get('request', None)
if request:
user = request.user
for getting user from View class Try:
user = self.request.user

Django register api with two User profile models

I am trying to create user, where User model has two User profiles, e.g. Teacher and Student. At the time of creating user, I want to create User instance in Teacher or Student profile also.
Here's my Serializer classes:
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = ('id', 'user_id')
class TeacherSerializer(serializers.ModelSerializer):
class Meta:
model = Teacher
fields = ('id', 'user_id')
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'id',
'username',
'password',
'email',
'user_type'
)
extra_kwargs = {
'password': {'write_only': True},
}
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
return user
Here's the custom register view:
class CreateUserView(CreateAPIView):
model = User
permission_classes = [
permissions.AllowAny # Or anon users can't register
]
serializer_class = UserSerializer
Right now, I am able to create User and but creating user profile according to the user_type parameter is a challenge for me.
So, if user_type is student, I need to create User instance for the StudentProfile as well(a new row in student profile table)
May be you can add simple if condition?:
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
if validated_data('user_type') == 'student':
Student.objects.create(user_id=user.id)
else:
Teacher.objects.create(user_id=user.id)
return user

Django Rest Framework create nested objects

I have seen similar questions but they are from 2012. Django Rest Framework has changed since then.
So, I have a Profile model that references the django User model. I have a ProfileSerializer that looks like this:
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerializer
class Meta:
model = Profile
fields = ['user', 'image', 'decription', 'city', 'phone_number']
The UserSerializer:
class UserSerializer(serializers.ModelSerializer):
def create(self, validated_data):
user_obj = User.objects.create(first_name=validated_data['first_name'], last_name=validated_data['last_name'],
email=validated_data['email'])
user_obj.set_password(validated_data['password'])
user_obj.save()
return user_obj
class Meta:
model = User
write_only_fields = ['password']
read_only_fields = ['id']
The Browsable Api shows the user field as a dropdown with the registered users, I need it to show the UserSerializer fields so that the view could create both, the Profile object and the User object at once, the view:
class RegisterUser(generics.CreateAPIView):
serializer_class = ProfileSerializer
Is it possible?
Edit, add the model:
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User)
image = models.UrlField()
....
So, is it possible to create both, the nested User object and the Profile object with one CreateAPIView? Or should I do it in two steps. All suggestions are welcome.

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