How to save a modelSerializer that has relations? - django - 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
...

Related

DRF SERILAZATION

I serialize the field named "product" with ProductSerializer() inside OrderItemSerializer().
That's what I want.
class OrderItemSerializer(serializers.ModelSerializer):
product = ProductSerializer()
class Meta:
model = models.OrderItem
fields = ('id','order', 'product', 'quantity')
The output is;
But when I try to request with POST Method needs to send Product as a dictionary, just giving the id value is not enough.
How can I POST by sending only the id value?
I haven't written anything about the operation yet. Default ModelViewSet
class OrderItemViewSet(ModelViewSet):
queryset = OrderItem.objects.all()
serializer_class = serializers.OrderItemSerializer
permission_classes = (IsOwnerOrNot, IsAuthenticated)
def get_queryset(self):
user = self.request.user
return self.filter_queryset(queryset=self.queryset.filter(order__user=self.request.user))
If you're supporting writable nested representations you'll need to write .create() or .update() methods that handle saving multiple objects.
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = ['username', 'email', 'profile']
def create(self, validated_data):
profile_data = validated_data.pop('profile')
user = User.objects.create(**validated_data)
Profile.objects.create(user=user, **profile_data)
return user

Serializer not passing complete information to API view

I created a serializer which the "user" below is from another Serializer which i imported, now the imported serializer(PubliceProfileSerializer) works fine on its own but it does not display the content of USER when i call it my browser from this serializer. Every other item shows except the user. Please help
from rest_framework import serializers
from users.api.serializers import PublicProfileSerializer
from blog.models import Post
class PostSerializer(serializers.ModelSerializer):
user = PublicProfileSerializer(source='users.profile', read_only=True)
category = serializers.SerializerMethodField()
label = serializers.SerializerMethodField()
class Meta:
model = Post
fields = '__all__'
def get_category(self, obj):
return obj.get_category_display()
def get_label(self, obj):
return obj.get_label_display()
Add you your serializer the list of fields. Replace
fields = '__all__'
with
fields = ('id', 'user', 'category', 'label')
because:
fields = '__all__'
will only populate id, category and label from the Post model, but will not provide the nested user serializer, so it becomes:
class Meta:
model = Post
fields = ('id', 'user', 'category', 'label')
or
class PostSerializer(serializers.ModelSerializer):
user = PublicProfileSerializer(many=False,
source='users.profile',
read_only=True)
category = serializers.SerializerMethodField()
label = serializers.SerializerMethodField()
class Meta:
model = Post
fields = ('id', 'user', 'category', 'label')
def get_category(self, obj):
return obj.get_category_display()
def get_label(self, obj):
return obj.get_label_display()

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

Django rest framework remove a field from API Create form

I have a model and viewset related to this model,
here is my code :
class EMAILTemplate(models.Model):
""" Message SMS Template """
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
body = models.TextField()
tokens = models.TextField()
created = models.DateTimeField(auto_now_add=True)
def save(self, *args, **kwargs):
self.tokens = ",".join(re.findall(r'{{\s*(.*?)\s*}}', self.body))
super().save(*args, **kwargs)
I don't want field tokens to be in my create form in Django rest framework create or edit form, because as you see It's going to be extracted from body field.
but I want to have this field in view single model or list of models.
and here is my ModelSerializer :
class EmailTemplateSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = EMAILTemplate
fields = ('name', 'body', 'user', 'tokens')
You can specify tokens as read only field:
class EmailTemplateSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = EMAILTemplate
fields = ('name', 'body', 'user', 'tokens')
read_only_fields = ('tokens',)
You can overwrite the current to_representation method
class EmailTemplateSerializer(serializers.HyperlinkedModelSerializer):
def to_representation(self, obj):
try:
if self.context['view'].action in ['list', 'detail']:
# get the original representation
ret = super(serializers.HyperlinkedModelSerializer, self).to_representation(obj)
# remove 'tokens' field
ret.pop('tokens')
return ret
except KeyError:
return super(serializers.HyperlinkedModelSerializer, self).to_representation(obj)
return super(serializers.HyperlinkedModelSerializer, self).to_representation(obj)

Django Rest Framework SerializerMethodField only on GET Request

Running into a little snag here with my DRF backend.
I am populating fields with choices on certain models.
I have a foreign key requirement on one model. When I create the model I want to save it under the foreign id.
When I request the models, I want the model with whatever the choice field maps to.
I was able to do this with SerializerMethodField, however when I try to create a model, I get a 400 error because the block is not valid. If I remove the SerializerMethodField, I can save, but get the number stored in the db from the request.
Any help would be appreciated.
class BlockViewSet(ModelViewSet):
model = apps.get_model('backend', 'Block')
queryset = model.objects.all()
serializer_class = serializers.BlockSerializer
permissions = ('All',)
def create(self, request, format=None):
data = request.data
data['user'] = request.user.id
data['goal'] = WorkoutGoal.objects.get(goal=data['goal']).id
block = serializers.BlockSerializer(data=data, context={'request': request})
if block.is_valid():
new_block = block.save()
return Response({'block': {'name': new_block.name, 'id': new_block.id}}, status=status.HTTP_201_CREATED)
else:
return Response(block.errors, status=status.HTTP_400_BAD_REQUEST)
class WorkoutGoalSerializer(serializers.ModelSerializer):
class Meta:
model = apps.get_model('backend', 'WorkoutGoal')
fields = ('goal',)
goal = serializers.SerializerMethodField(read_only=True, source='get_goal')
def get_goal(self, obj):
return dict(WorkoutGoal.GOALS).get(obj.goal)
class BlockSerializer(serializers.ModelSerializer):
workout_count = serializers.IntegerField(required=False)
completed_workouts = serializers.IntegerField(required=False)
goal = WorkoutGoalSerializer()
class Meta:
model = apps.get_model('backend', 'Block')
read_only_fields = ('workout_count', 'completed_workouts')
fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')
The above code returns the correct choice, but I can't save under it. Remove the goal = WorkoutGoalSerializer() and it saves but doesn't return the mapped choice.
I think this will work like a charm,
class WorkoutGoalSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'request' in self.context and self.context['request'].method == 'GET':
self.fields['goal'] = serializers.SerializerMethodField(read_only=True, source='get_goal')
class Meta:
model = apps.get_model('backend', 'WorkoutGoal')
fields = ('goal',)
goal = serializers.SerializerMethodField(read_only=True, source='get_goal') # remove this line
def get_goal(self, obj):
return dict(WorkoutGoal.GOALS).get(obj.goal)
How this Work?
It will re-initiate the goal field with SerializerMethodField, if the reuested method is GET.
Remember one thing, you should remove the line,
goal = serializers.SerializerMethodField(read_only=True, source='get_goal')
serializers.py
class BlockCreateSerializer(serializers.ModelSerializer):
workout_count = serializers.IntegerField(required=False)
completed_workouts = serializers.IntegerField(required=False)
class Meta:
model = apps.get_model('backend', 'Block')
read_only_fields = ('workout_count', 'completed_workouts')
fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')
class BlockSerializer(serializers.ModelSerializer):
workout_count = serializers.IntegerField(required=False)
completed_workouts = serializers.IntegerField(required=False)
goal = WorkoutGoalSerializer()
class Meta:
model = apps.get_model('backend', 'Block')
read_only_fields = ('workout_count', 'completed_workouts')
fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')
views.py
class BlockViewSet(ModelViewSet):
model = apps.get_model('backend', 'Block')
queryset = model.objects.all()
serializer_class = serializers.BlockSerializer
permissions = ('All',)
def get_serializer_class(self):
if self.action == 'create':
return serializers.BlockCreateSerializer
else:
return self.serializer_class
def create(self, request, format=None):
data = request.data
data['user'] = request.user.id
data['goal'] = WorkoutGoal.objects.get(goal=data['goal']).id
block = self.get_serializer(data=data)
if block.is_valid():
new_block = block.save()
return Response({'block': {'name': new_block.name, 'id': new_block.id}}, status=status.HTTP_201_CREATED)
else:
return Response(block.errors, status=status.HTTP_400_BAD_REQUEST)
override get_serializer_class to return different serializer_class for create and other action(list\retrieve\update\partial_update)