Nested field serializer - Data missing - python

Related to this Topic
Hi,
I cannot follow the answer at the attached topic, because an ID is missing after serialization.
Model.py
class Owner(models.Model):
name = models.CharField(db_index=True, max_length=200)
class Car(models.Model):
name = models.CharField(db_index=True, max_length=200)
LCVS = models.ForeignKey(Owner)
View.py
class OwnerViewSet(viewsets.ModelViewSet):
queryset = Owner.objects.all()
serializer_class = OwnerSerializer
class CarViewSet(viewsets.ModelViewSet):
serializer_class = CarSerializer
queryset = Car.objects.all()
Serializer.py
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = Owner
fields = ('id', 'name')
class CarSerializer(serializers.ModelSerializer):
owner = OwnerSerializer()
class Meta:
model = Car
fields = ('id', 'name', 'owner')
def create(self, validated_data):
tmp_owner = Owner.objects.get(id=validated_data["car"]["id"])
car = Car.objects.create(name=self.data['name'],owner=tmp_owner)
return car
Now i send the following request :
Request URL:http://localhost:9000/api/v1/cars
Request Method:POST
Request Paylod :
{
"name": "Car_test",
"ower": {
"id":1,
"name": "Owner_test"
}
}
But, here the validated_data don't contain the owner ID !
Traceback | Local vars
validated_data {u'Owner': OrderedDict([(u'name', u'Owner_test')]), u'name': u'Car_test'}
#Kevin Brown :
Workful ! Thanks
I'll validate your answer but I get a new problem...
Now when I try to put a new Owner, an error raise :
{
"id": [
"This field is required."
]
}
I had to create a new serializer ?

Any AutoFields on your model (which is what the automatically generated id key is) are set to read-only by default when Django REST Framework is creating fields in the background. You can confirm this by doing
repr(CarSerializer())
and seeing the field generated with read_only=True set. You can override this with the extra_kwargs Meta option which will allow you to override it and set read_only=False.
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = Owner
fields = ('id', 'name')
extra_kwargs = {
"id": {
"read_only": False,
"required": False,
},
}
This will include the id field in the validated_data when you need it.

Related

Can I use a ModelViewSet for POST and GET of a parent of self in a model in Django REST?

Hello I have the following structure:
class Category(models.Model):
model.py
"""Class to represent the category of an Item. Like plants, bikes..."""
name = models.TextField()
description = models.TextField(null=True)
color = models.TextField(null=True)
# This will help to anidate categories
parent_category = models.ForeignKey(
'self',
on_delete=models.SET_NULL,
null=True,
)
Then I serialize it:
serializers.py:
class CategorySerializer(serializers.ModelSerializer):
"""Serializer for Category."""
class Meta: # pylint: disable=too-few-public-methods
"""Class to represent metadata of the object."""
model = Category
fields = ['id', 'name', 'description', 'color', 'parent_category']
And I Create my endpint
views.py:
class CategoryViewset(viewsets.ModelViewSet): # pylint: disable=too-many-ancestors
"""API Endpoint to return the list of categories"""
queryset = Category.objects.all()
serializer_class = CategorySerializer
pagination_class = None
Well this seems to work as expected to make a post request, for example sending this:
{
"name": "Plants",
"description": null,
"color": "#ef240d",
"parent_category": 1
}
But when I make a request of this I want to see the parent category and not have to do two requests. So I found from other questions that I could use an external library:
serializer.py
from rest_framework_recursive.fields import RecursiveField
class CategorySerializer(serializers.ModelSerializer):
"""Serializer for Category."""
parent_category = RecursiveField(many=False)
class Meta: # pylint: disable=too-few-public-methods
"""Class to represent metadata of the object."""
model = Category
fields = ['id', 'name', 'description', 'color', 'parent_category', 'category_name']
And then It seems to work:
{
"id": 2,
"name": "Flowers",
"description": null,
"color": "#ef240a",
"parent_category": {
"id": 1,
"name": "Plants",
"description": "something",
"color": "#26def2",
"parent_category": null,
},
},
But when I try to post now it will not work as It seems to expect an object instead of just the ID which is what I would have available in my frontend:
{
"parent_category": {
"non_field_errors": [
"Invalid data. Expected a dictionary, but got int."
]
}
}
Is it possible to mix somehow this two approaches in my ModelSerializer?
You can customise your ModelViewSet to use two serializers instead of one. For example
class CategoryViewset(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
pagination_class = None
def create(self, request):
new_category = CategoryCreateSerializer(data=request.data)
if new_category.is_valid:
return Response(CategoryRetrieveSerializer(new_category).data)

Python Django Rest Framework: The `.create()` method does not support writable nested fields by default

I am using django rest framework to create an api endpoint. I am using the default user model django offers. I need to create a post which uses the user as a foreign key. A user called "author" in the post can have multiple posts.
This is an example of a post json.
[
{
"author": {
"id": 1,
"username": "sorin"
},
"title": "First Post",
"description": "Hello World!",
"created_at": "2020-08-05T14:20:51.981163Z",
"updated_at": "2020-08-05T14:20:51.981163Z"
}
]
This is the model.
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
This is the serializer.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username')
class PostSerializer(serializers.HyperlinkedModelSerializer):
author = UserSerializer()
class Meta:
model = Post
fields = ('author', 'title', 'description', 'created_at', 'updated_at')
I am getting the error "The .create() method does not support writable nested fields by default." when trying to make a post request using a "username", "title" and "description".
Any help to how to solve this?
I like hooking in the create function of the serializer for these kind of use cases.
Make sure your UserSerializer is set to read_only=True.
class PostSerializer(serializers.HyperlinkedModelSerializer):
author = UserSerializer(read_only=True)
class Meta:
model = Post
fields = ('author', 'title', 'description', 'created_at', 'updated_at')
def create(self, validated_data):
request = self.context['request']
author_data = request.data.get('author')
if author is None or not isinstance(author.get('id'), int):
raise ValidationError({'author': ['This field is invalid.']})
author_instance = get_object_or_404(User, id=author.get('id'))
return Post.objects.create(author=author_instance, **validated_data)

Adding foreign key data to serializer?

I'm trying to use the Django Rest-Framework to produce some JSON that shows all the user's posts, but also shows the images for that post. Image is a foreign key to Post. Here are the models:
models.py
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
status = models.CharField(max_length=200)
class Image(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
img = models.CharField(max_length=120)
views_count = models.IntegerField(default=0)
views.py
class GetPosts(ListAPIView):
serializer_class = PostSerializer
def get_queryset(self):
requested_user = get_requested_user(self)
return Post.objects.filter(user=requested_user).order_by('-created_at')
def get_requested_user(self):
filter_kwargs = {'username': self.kwargs['username']}
return get_object_or_404(User.objects.all(), **filter_kwargs)
serializers.py
class PostSerializer(serializers.ModelSerializer):
image_img = serializers.RelatedField(source='Image', read_only=True)
class Meta:
model = Post
fields = ('status', 'image_img ')
In the serializers.py, I'd like to show all of the fields for Image (img, views_count) What I get with my current code is this:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"status": "I am number 1"
}
]
}
Which contains the user's posts, but not the user's posts and each post's images. Note: Query url looks like this: /api/posts/user/
You should use Nested serializer here,
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = ('img',)
class PostSerializer(serializers.ModelSerializer):
image_img = ImageSerializer(source='image_set', many=True)
class Meta:
model = Post
fields = '__all__'
Hence the response will be like,
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"status": "I am number 1",
"image_img": [
{"img": "image_url"},
{"img": "image_url"},
....
]
}
]
}
How to display all field of model class in serializer?
From the doc,
You can also set the fields attribute to the special value '__all__' to indicate that all fields in the model should be used.
Reference
1. DRF- Nested Realtions
2. source argument
3. Specifying which fields to include

Django Rest Create with a Writable Nested Serializer

I'm trying to perform a create in Django Rest Framework using a writable nested serializer.
With the code bellow I can create a ScriptQuestion but I can't add a RecordedInterview into it. Django says OrderedDict is None.
What am I doing wrong?
Thanks in advance
#models.py
class ScriptQuestion(models.Model):
interview = models.ManyToManyField(RecordedInterview)
...
class RecordedInterview(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
...
The serializers
#serializers.py
class InterviewTitleSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = RecordedInterview
fields = ('id', 'title')
extra_kwargs = {
'title': { 'read_only': True }
}
class QuestionDetailSerializer(serializers.HyperlinkedModelSerializer):
interview = InterviewTitleSerializer(many=True)
class Meta:
model = ScriptQuestion
fields = ('id', 'title', 'prep_time', 'answer_time', 'interview')
depth = 1
def create(self, validated_data):
interview_data = validated_data.pop('interview')
question = ScriptQuestion.objects.create(**validated_data)
for item in interview_data:
item = interview_data['id']
question.interview.add(item)
return question
Here is my view
#views.py
class CreateQuestion(generics.CreateAPIView):
queryset = ScriptQuestion.objects.all()
serializer_class = QuestionDetailSerializer
And the json
{
"title": "Question Test Json",
"prep_time": "1",
"answer_time":"1",
"interview": [
{
"id": "a450aeb0-8446-47b0-95bd-5accbb8b4afa"
}
]
}
If I do manually, I can add the RecordedInterview into the ScriptQuestion:
#serializers.py
def create(self, validated_data):
interview_data = validated_data.pop('interview')
question = ScriptQuestion.objects.create(**validated_data)
item = 'a450aeb0-8446-47b0-95bd-5accbb8b4afa'
question.interview.add(item)
return question
I cannot add a comment because of low reputation. SO adding as an answer. I think you should use 'serializers.ModelSerializer' instead of 'serializers.HyperLinkedModelSerializer'
Oh, I could make it.
For someone in the future, just add "id = serializers.CharField()" in the Serializer
class InterviewTitleSerializer(serializers.ModelSerializer):
id = serializers.CharField()
class Meta:
model = RecordedInterview
fields = ('id', 'title')
extra_kwargs = {'title': { 'read_only': True }}

Django REST Framework nested serializer FK creation

I'm in trouble creating a bunch of related models using DRF nested serializers.
They are failing validation on the foreign key.
Models
class Employee(models.Model):
user = models.OneToOneField(User) # Django user
...
class Task(models.Model):
author = models.ForeignKey(Employee, related_name='tasks')
title = models.CharField(max_length=64)
...
class EmployeeTarget(models.Model):
employee = models.ForeignKey(Employee, null=False)
task = models.ForeignKey(Task, null=False, related_name='employee_targets')
...
Objective
Basically I have the Employees already created, and I want to create a Task and related EmployeeTarget in a single request, getting the request user as the author. JSON request example:
{
"title": "Lorem Ipsum",
"employee_targets": [
{ "employee": 10 },
{ "employee": 11 }]
}
/* or */
{
"title": "Lorem Ipsum",
"employee_targets": [10,11]
}
Serializers
class EmployeeSerializer(serializers.ModelSerializer):
name = serializers.CharField(source="user.get_full_name", read_only=True)
email = serializers.CharField(source="user.email", read_only=True)
class Meta:
model = Employee
class EmployeeTargetSerializer(serializers.ModelSerializer):
employee = EmployeeSerializer()
class Meta:
model = EmployeeTarget
class TaskSerializer(base.ModelSerializer):
employee_targets = EmployeeTargetSerializer(many=True, required=False)
class Meta:
model = Task
def create(self, validated_data):
employee_target_data = validated_data.pop('employee_targets')
task = Task.objects.create(**validated_data)
EmployeeTarget.objects.create(task=task, **employee_target_data)
return task
ViewSet
class TaskViewSet(ModelViewSet):
serializer_class = TaskSerializer
def get_queryset(self):
request_employee = self.request.user.employee
return Task.objects.filter(Q(author=request_employee) |
Q(employee_targets__employee=request_employee))
def perform_create(self, serializer):
serializer.save(author=self.request.user.employee)
Result
I'm getting 400 BAD REQUEST with the following error:
{
"employee_targets": [
{
"employee": {
"non_field_errors": ["Invalid data. Expected a dictionary, but got int."]
},
"task": ["This field is required."]
}
],
"author": ["This field is required."]
}
The employee error was expected, but I haven't figured out how to create them using only the ID.
The bigger problem here is the employee_targets failing validation at the task FK, before the enclosing TaskSerializer specify them at create method.
Can you try with this:
class EmployeeSerializer(serializers.ModelSerializer):
name = serializers.CharField()
email = serializers.CharField()
class Meta:
depth = 2
model = Employee

Categories

Resources