Related field id in POST request in Django Rest Framework - python

I am creating the object with just only related field id. I have searched a lot but couldn't get the answer. Here is my code
models.py:
class Resturant(models.Model):
name = models.CharField(_('name'), max_length=100)
class Menu(models.Model):
resturant_id = models.OneToOneField(Resturant, related_name='resturant', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now=True)
serializers.py:
class MenuSerializer(serializers.ModelSerializer):
resturant_id = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Menu
fields = ['id', 'created_at', 'resturant_id']
views.py:
class CreateMenuAPIView(APIView):
def post(self, request, *args, **kwargs):
serializer = MenuSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(status=status.HTTP_400_BAD_REQUEST)
I am getting this error while sending { "resturant_id": 2 } in POST request.
DETAIL: Failing row contains (14, 2018-04-02 09:36:43.261849+00, null).
The above exception (null value in column "resturant_id" violates not-null constraint
Any help would be appreciated !

you can override method create for find Restaurant object or create if not exist. and only edit serializer.
serializer.py
class MenuSerializer(serializers.ModelSerializer):
resturant_id = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Menu
fields = ['id', 'created_at', 'resturant_id']
def create(self, validated_data):
id_param = validated_data.pop('resturant_id')
resturant = Resturant.objects.get_or_create(id=id_param)[0]
menu = Menu.objtects.create(resturant_id=resturant.id)
return menu
if not work you can delete this line:
resturant_id = serializers.PrimaryKeyRelatedField(read_only=True)

returant_id = serializers.PrimaryKeyRelatedField(read_only=True)
Could you try giving read_only=False
Could you check the spelling,
returant_id is used in serializers field,'s' is missing.
'resturant_id' is used in fields list

You are using a model serializer and have overridden 'returant_id'.
class MenuSerializer(serializers.ModelSerializer):
returant_id = serializers.PrimaryKeyRelatedField(queryset=Resturant.objects.all())

try to change the serializer to
class MenuSerializer(serializers.ModelSerializer):
resturant_id = serializers.PrimaryKeyRelatedField()
class Meta:
model = Menu
fields = ['id', 'created_at', 'resturant_id']
if read_only=True then it will not write into database.

Related

Rest Framework cant save serializer with foreign key

I have next serializers:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
category = CategorySerializer()
class Meta:
model = Post
fields = ['id', 'title', 'text', 'date', 'category']
And here is my view:
#api_view(['POST'])
def create_post(request):
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
else:
return Response(serializer.errors)
return Response(serializer.data)
I want to create new Post object, but when I pass an category id at form it does not work, it is not saving my object. I tried to replace create method at my PostSerializer, to this:
def create(self, validated_data):
category_id = validated_data.pop('category')
post = Post.objects.create(**validated_data, category=category_id)
return post
but this dont work. Using postman formdata it is saying, that category field is required despite I filled it.
Here is my models:
class Category(models.Model):
name = models.CharField(max_length=512)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=512)
text = models.TextField()
date = models.DateTimeField(auto_now_add=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='category')
You need a category object not just an id, so try this
#api_view(['POST'])
def create_post(request):
category_id = request.data['category'] # or however you are sending the id
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
category = Category.objects.get(id=category_id)
serializer.save(category=category)
else:
return Response(serializer.errors)
return Response(serializer.data)
or you can do something similar in the create method of the serializer

Django Rest Framework I can't use Serializer save model of have foreign key

I'm a Django Rest Framework and Django newbie
i can use random data to make stages but i can't use serializer to add new stages.
My model and serializer
class Stage(models.Model):
class Meta:
db_table = 'stage'
stage_id = models.AutoField(primary_key=True)
stage_name = models.CharField(max_length=64, null=False)
company = models.ForeignKey(
Company,
db_column='id',
on_delete=models.CASCADE,
)
class StageSerializer(ModelSerializer):
stage_id = IntegerField(read_only=True)
class Meta:
model = Stage
fields = [
'stage_id',
'stage_name',
'company',
]
def update(self, instance, validated_data):
pass
def create(self, validated_data):
# create stages
stage = create_stage(**validated_data)
return stage
view.py
class StageListAPIView(APIView):
def post(self, request, company_id):
data = request.data.copy()
company = get_company_by_id(company_id)
data['company'] = company.pk
serializer = StageSerializer(data=data)
if not serializer.is_valid(raise_exception=True):
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
new_data = serializer.validated_data
serializer.save(company=company)
return Response(new_data, status=HTTP_200_OK)
request.data
<QueryDict: {'stage_name': ['kAkSdKq9Gt'], 'company': [6]}>
i will receive error:
TypeError: Object of type Company is not JSON serializable
i can't understand it and i don't know how to use serializer to save foreign key.
You need to serialize the Company instance before you can include it in your StageSerializer.
A simple example would be something like
class CompanySerializer(ModelSerializer):
class Meta:
model = Company
fields = '__all__'
And then to include that in your StageSerializer:
class StageSerializer(ModelSerializer):
stage_id = IntegerField(read_only=True)
company = CompanySerializer(source='company', read_only=True)
class Meta:
model = Stage
fields = [
'stage_id',
'stage_name',
'company',
]

how to update OneToOneField / ForeignKey using ViewSet? Django

I am currently using restful and serializers to create and update my user.
Somehow I am not able to update some of the fields if the field has to do with OneToOneField / ForeignKey.
in my models.py, my Student is actually connected to the django build in user model which includes the user's email and connected to the school model which has the name of the school
class Student(Model):
user = OneToOneField(settings.AUTH_USER_MODEL, on_delete=CASCADE)
date_of_birth = DateField(blank=True, null=True)
student_name = CharField(max_length=256)
school = ForeignKey(School,
on_delete=CASCADE,
related_name="%(class)ss",
related_query_name="%(class)s",
blank=True,
null=True)
in serializer.py I have
class StudentSerializer(ModelSerializer):
user_email = SerializerMethodField()
school_name = SerializerMethodField()
class Meta:
model = Student
fields = (
'user_email', 'student_name', 'phone', 'school_name')
def get_user_email(self, obj):
return obj.user.email
def get_school_name(self, obj):
return obj.school.school_name
def create(self, validated_data):
return Student.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.user.email = validated_data.get('user_email', instance.user.email)
instance.student_name = validated_data.get('student_name', instance.student_name)
instance.phone = validated_data.get('phone', instance.phone)
instance.school.school_name = validated_data.get('school_name', instance.school.school_name)
instance.save()
return instance
in my view.py update function
class UserViewSet(ViewSet):
queryset = Student.objects.all()
def update(self, request, pk=None):
student = get_object_or_404(self.queryset, pk=pk)
serializer = StudentSerializer(student, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response({'status': True})
return Response({'status': False, 'message': serializer.errors})
I am able to use the API view to pass in json and update the student_name and phone but as for the other two, user_email and school_name I am not able to update it. I don't get any error output when I submit the json though.
I realized the two fields that I am not able to update are because they OneToOneField / ForeignKey.
Can someone please give me a hand what I am missing here or what I can do to check?
Thanks in advance
I think your serializer isn't completed... the field of user and school is instance model, you need specific field in your serializer to implement the instance model, eg: with source='...' argument.
and example:
class VoteSerializer(serializers.ModelSerializer):
# by `username`
user = serializers.CharField(
source='user.username',
read_only=True
)
# by `pk/id`
candidate = serializers.IntegerField(
source='candidate.pk',
read_only=True
)
class Meta:
model = Vote
fields = ('user', 'candidate', 'score')
def create(self, validated_data):
return Vote.objects.create(**validated_data)
and in your case, perhaps is like this;
class StudentSerializer(ModelSerializer):
# by `pk/id` from the user
user = serializers.IntegerField(
source='user.pk',
read_only=True
)
school = serializers.IntegerField(
source='school.pk',
read_only=True
)
Since you are using SerializerMethodField which is readonly field (docs) for user_email and school_name so they won't be available in the validated_data.
Have you check the data you are receiving in validated_data
def update(self, instance, validated_data):
print('++'*22, validated_data)
return instance
The nested seriailzer / model / presentation actually helped me get the work done and pretty helpful.
An example is also provided here.
http://www.django-rest-framework.org/api-guide/serializers/#writing-update-methods-for-nested-representations
the above is continued from
http://www.django-rest-framework.org/api-guide/serializers/#writing-create-methods-for-nested-representations which contained how the nested serializer is being setup in the class and meta's fields

unique_together of two field messes with read_only_fields

I have this code for rating lessons, user and lesson should be added automatically from request authorization and URL:
#views.py
class RatingViewSet(
mixins.ListModelMixin,
mixins.CreateModelMixin,
viewsets.GenericViewSet
):
permission_classes = [permissions.IsAuthenticated]
serializer_class = RatingSerializer
def perform_create(self, serializer):
lessonInstance = Lesson.objects.get(id = self.kwargs['lessonID'])
serializer.save(user=self.request.user, lesson = lessonInstance)
def get_queryset(self):
lessonID = self.kwargs['lessonID']
return Rating.objects.filter(user=self.request.user, lesson=lessonID)
#serializers.py
class RatingSerializer(serializers.ModelSerializer):
class Meta:
model = Rating
fields = ('id', 'lesson','user', 'difficulty')
read_only_fields = ('id', 'user','lesson')
#models.py
class Rating(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
lesson = models.ForeignKey('lessons.Lesson')
difficulty = models.IntegerField()
class meta:
unique_together('user','lesson')
I want to have max 1 rating per user/lesson, hence unique_together('user','lesson'). But there is a problem: as long as that constraint is in the code, requests without user or lesson fields get denied with field required error, even though they are read_only.
(If I migrate with unique_together('user','lesson'), then delete that line it works, but as soon as it's there I get errors.)
I want to keep that bit of code there so I don't accidentally remove the unique_together constraint on later migrations.
This is a special-case that requires a different approach. Here's what django-rest-framework documentation (see the Note) says about this case:
The right way to deal with this is to specify the field explicitly on
the serializer, providing both the read_only=True and default=…
keyword arguments.
In your case, you need to explicitly define the user and lesson fields on your RatingSerializer, like this:
class RatingSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault()) # gets the user from request
lesson = serializers.PrimaryKeyRelatedField(read_only=True, default=None) # or any other appropriate value
class Meta:
model = Rating
fields = ('id', 'lesson','user', 'difficulty')
Good luck!
If a field is read_only=True then the validated_data will ignore data of it => Cause error required field, read more at doc
I also met this issue in a similar context, then tried #iulian's answer above but with no luck!
This combo read_only + default behavior is not supported anymore, check this
I resolved this issue by 2 solutions:
My model:
class Friendship(TimeStampedModel):
"""Model present Friendship request"""
from_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='friendship_from_user')
to_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='friendship_to_user')
class Meta:
unique_together = ('from_user', 'to_user')
Solution 1. Write your own CurrentUserDefault class to get the user id then set to default attribute data of serializer(Ref from #51940976)
class CurrentUserDefault(object):
def set_context(self, serializer_field):
self.user_id = serializer_field.context['request'].user.id
def __call__(self):
return self.user_id
class FriendshipSerializer(serializers.ModelSerializer):
from_user_id = serializers.HiddenField(default=CurrentUserDefault())
class Meta:
model = Friendship
fields = ('id', 'from_user', 'from_user_id', 'to_user', 'status')
extra_kwargs = {
'from_user': {'read_only': True},
}
Solution 2. Override the create method of serializer to set data for user id(Ref from this)
class FriendshipSerializer(serializers.ModelSerializer):
class Meta:
model = Friendship
fields = ('id', 'from_user', 'to_user', 'status')
extra_kwargs = {
'from_user': {'read_only': True},
}
def create(self, validated_data):
"""Override create to provide a user via request.user by default.
This is require since the read_only `user` filed is not included by default anymore since
https://github.com/encode/django-rest-framework/pull/5886.
"""
if 'user' not in validated_data:
validated_data['from_user'] = self.context['request'].user
return super(FriendshipSerializer, self).create(validated_data)
I hope this helps!

django-rest-framework PUT manytomany through model

I have a Django Model w/ a m2m relationship that uses a through model:
models.py
class ModelA(models.Model):
name = models.CharField(max_length=64)
class ModelB(models.Model):
name = models.CharField(max_length=64)
other_models = models.ManyToManyField("ModelA", through="ModelC")
class ModelC(models.Model):
model_a = models.ForeignKey("ModelA", related_name="link_to_model_a")
model_b = models.ForeignKey("ModelB", related_name="link_to_model_b")
some_other_info = models.TextField()
class Meta:
unique_together = ("model_a", "model_b", )
I want to serialize this using django-rest-framework:
serializers.py
class ModelCSerializer(ModelSerializer):
class Meta:
model = ModelC
fields = ('id', 'model_a', 'model_b', 'some_other_info', )
class QModelBSerializer(ModelSerializer):
class Meta:
model = ModelB
fields = ('id', 'other_models', )
other_models = ModelCSerializer(many=True, required=False, source="link_to_model_b")
Now, for existing models the GET displays properly:
{
"id": 2,
"name": "i am an instance of model_b",
"other_models": [
{"id": 1, "model_a": 1,"model_b": 2, "some_other_info":"here is some other info"}
],
}
But, if I try to PUT some data it fails w/ a unique_together error. I thought that sending this as a PUT would cause an update (which shouldn't raise a unique_together error) not a create? Here is the code for PUT:
views.py
class ModelBDetail(APIView):
def put(self, request, pk, format=None):
model = ModelB.objects.get(id=pk)
serializer = ModelBSerializer(model, data=request.data, context={"request": request})
if serializer.is_valid(): # THIS IS RETURNING FALSE
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Any thoughts?
Django rest framework documentation states that it is up to developer to implement creates and updates for nested representations.
Although #Ivan was correct about writing my own create & update fn, the specific issue I was seeing was that the nested serialization did not have an instance attribute associated with it.
The new code looks like this:
serializers.py
class ModelBSerializer(ModelSerializer):
....
def update(self, model_instance, validated_data):
model_c_serializer = self.fields["other_models"]
model_c_data = validated_data.pop(model_c_serializer.source, [])
for key, value in validated_data.iteritems():
setattr(model_instance, key, value)
model_instance.save()
model_c_serializer.update(model_instance.link_to_model_b.all(),
model_c_data)
return model_instance
class ModelCSerializer(ModelSerializer):
...
def to_internal_value(self, data):
# this is as good a place as any to set the instance
try:
model_class = self.Meta.model
self.instance = model_class.objects.get(pk=data.get("id"))
except ObjectDoesNotExist:
pass
return super(ModelCSerializer, self).to_internal_value(data)
Basically, I call update for the nested serializers explicitly and I also force each nested serializer to check the data that is passed to them for an instance.

Categories

Resources