Django Rest Framework create nested objects - django

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.

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 retrive data from OneToOne Relational Model in DRF using API view

I have imported User model and customized it a/c to my need and make OneToOne Relation with UserProfileModel Model. While retrieving data I got this error.
"The serializer field might be named incorrectly and not match any attribute or key on the AnonymousUser instance.
Original exception text was: 'AnonymousUser' object has no attribute 'gender'."
My Model is :
class UserProfileModel(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='userprofilemodel')
gender = models.CharField(max_length=20)
locality = models.CharField(max_length=70)
city = models.CharField(max_length=70)
address = models.TextField(max_length=300)
pin = models.IntegerField()
state = models.CharField(max_length=70)
profile_image = models.FileField(upload_to='user_profile_image', blank=True)
My Serializer looks like:
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model= User
fields = ['id', 'name' , 'email','mobile',]
class UserProfileModelSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField(many=True, read_only=True)
class Meta:
model= UserProfileModel
fields = ['user','gender' , 'locality','city','address','pin','state','profile_image', ]
My view looks like:
class UserProfileDataView(APIView):
def get(self, request, format=None):
# user = UserProfileModel.objects.all()
serializer = UserProfileModelSerializer(request.user)
return Response(serializer.data, status=status.HTTP_200_OK)
I want to retrieve profile data of the logged user using UserProfileModel Model
Your first issue in that you are passing a User instance to the UserProfileModelSerializer, which is expecting a UserProfileModel instance. To fix this you need to change:
serializer = UserProfileModelSerializer(request.user)
to
serializer = UserProfileModelSerializer(request.user.userprofilemodel)
where userprofilemodel is the related_name you have set on the user field in your UserProfileModel.
Second issue is, as Mohamed Beltagy said, you're allowing anyone to access the view, including unauthenticated users. Django rest framework has a built in mixin that you can use to restrict access to authenticated users only (https://www.django-rest-framework.org/api-guide/permissions/#isauthenticated).
from rest_framework.permissions import IsAuthenticated
class UserProfileDataView(APIView):
permission_classes = [IsAuthenticated]
the problem here is you are passing an anonymous user which has no profile ( you permit non-authenticated users access this view)
def get(self, request, format=None):
# user = UserProfileModel.objects.all()
if request.user.is_authenticated:
serializer = UserProfileModelSerializer(request.user)
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(status=status.HTTP_401_UNAUTHORIZED)

Django Rest Framework serializer check if exists in related Many to Many fiield

I have a custom User model with a blocked field as
class User(AbstractUser):
username = None
email = models.EmailField(_('email address'), unique=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
blocked = models.ManyToManyField("User", related_name="blocked_by", blank=True)
and I'm trying to have a serializer where I can look up a user by their ID and find out if they are blocked or not by the logged in user
my current is serializer
class UserSerializer(serializers.ModelSerializer):
is_blocked = serializers.BooleanField() # need to populate this
class Meta:
model = User
fields = ['id', 'email', 'user_type', 'is_blocked']
and my view
class UserDetail(generics.RetrieveAPIView):
authentication_classes = (authentication.JWTAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
serializer_class = UserSerializer
queryset = User.objects.all()
I'm just not sure how to populate that value such that it'd be true if the inquiried user is in the blocked manyToMany field of the logged in user or not
You can use SerializerMethodField and calculate it. Get the authenticated user and check if the serialized user is in the blocked list.
class UserSerializer(serializers.ModelSerializer):
is_blocked = serializers.SerializerMethodField()
class Meta:
model = User
fields = ['id', 'email', 'user_type', 'is_blocked']
def get_is_blocked(self, obj):
# you might need to do some fixes in this method, I'm not 100% sure that it works like you want
logged_in_user = self.context['request'].user # the authenticated user
is_blocked = logged_in_user.blocked.filter(id=obj.id).exists() # check if the serialized user (obj) is in the auth user blocked list
return is_blocked

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