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
Related
I have made Sample model as a ForeignKey to other two child model, and sample_ID as a primary key using lookup_field.
models.py
class Sample(models.Model):
sample_ID = models.CharField(max_length=10)
class Label(models.Model):
AI_sample = models.ForeignKey(Sample, null=True, on_delete=models.CASCADE, related_name='AI_sample')
AI_position = models.CharField(max_length=50)
AI_defect = models.CharField(max_length=50)
AI_tool = models.CharField(max_length=50)
class Manual(models.Model):
manual_sample = models.ForeignKey(Sample, null=True, on_delete=models.CASCADE, related_name='manual_sample')
manual_position = models.CharField(max_length=50)
manual_defect = models.CharField(max_length=50)
manual_tool = models.CharField(max_length=50)
Now my API endpoint looks like this:
{
"id": 3,
"sample_ID": "a000001",
"AI_sample": [
{
"id": 3,
"AI_position": "Position 1",
"AI_defect": "Defect 1",
"AI_tool": "Tool 1"
}
],
"manual_sample": [
{
"id": 8,
"manual_position": "Position 3",
"manual_defect": "Scratch",
"manual_tool": "Tool 2"
}
]
}
Currently I am using reverse lookup of foreignKey to get the child data from sample_ID. Now I can easily make GET request by destructing the data like this:
axios.get(url)
.then(resp => {
let data = [];
const manual_sample = resp.data.manual_sample;
manual_sample.forEach(el => {
data.push({
id: el.id,
manual_position: el.manual_position,
manual_defect: el.manual_defect,
manual_tool: el.manual_tool
});
});
})
However, I am not able to make POST request out of a child data, because the I cannot set the primary key of sample_ID in a nested object. I am sending the manual_sample data request like this:
{
"id": 9,
"manual_position": "Position 5",
"manual_defect": "Crack",
"manual_tool": "Tool 4"
}
Now, is there a possible soluation to make a POST request out of manual_sample data under sample_ID: a000001.
Here is my serializers.py & views.py if you are interested:
serializers.py
class LabelSerializer(serializers.ModelSerializer):
class Meta:
model = Label
fields = ['id', 'AI_position', 'AI_defect', 'AI_tool']
class ManualSerializer(serializers.ModelSerializer):
class Meta:
model = Manual
fields = ['id', 'manual_position', 'manual_defect', 'manual_tool']
class SampleSerialier(serializers.ModelSerializer):
AI_sample = LabelSerializer(many=True, read_only=True)
manual_sample = ManualSerializer(many=True, read_only=True)
class Meta:
model = Sample
fields = ['id', 'sample_ID', 'AI_sample', 'manual_sample']
views.py
class SampleViewSet(viewsets.ModelViewSet):
queryset = Sample.objects.all()
serializer_class = SampleSerialier
lookup_field = "sample_ID"
class LabelViewSet(viewsets.ModelViewSet):
queryset = Label.objects.all()
serializer_class = LabelSerializer
class ManualViewSet(viewsets.ModelViewSet):
queryset = Manual.objects.all()
serializer_class = ManualSerializer
I don't know if I described it correctly and precisely, but please free to leave a comment, and let me know if I missed something. Thanks
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)
I have a ManyToMany relation with tag and items:
class Tag(BaseModel):
name = models.CharField(max_length=255) # ToDo Change max length
description = models.TextField(blank=True, null=True)
class Item(BaseModel):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
image = models.ImageField(upload_to='items', blank=True)
title = models.TextField(blank=False, null=True)
message = models.TextField(blank=True, null=True)
fav_count = models.IntegerField(default=0)
tags = models.ManyToManyField(Tag, related_name='tags')
I need all fields to be serialized, but i wish to only limit the response values.
Example:
What I'm receiving now:
{
"user": 2,
"image": null,
"title": "test3",
"message": "testmessage",
"fav_count": 0,
"tags": [
{
"id": 7,
"name": "tag1",
"description": null
},
{
"id": 8,
"name": "tag2",
"description": null
}
]
}
But i only wish to receive the tag ids not the name and description...
My simple view:
if request.method == 'GET':
items = Item.objects.all()
serializer = ItemSerializer(items, many=True)
return Response(serializer.data)
Would i need to rebuild my response data to include/exclude or is there a better way to do this? (or if iv missed the terminology)
use PrimaryKeyRelatedField DRF field in your serializer
Example
class ItemSerializer(serializers.ModelSerializer):
tags = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Item
fields = ('tags', 'image',.....other fields)
Response
{
'image': 'image1',
...........
'tags': [
89,
90,
91,
...
]
..........
}
In you want to do it dynamically based on a request parameter.
class ItemSerializer(serializers.ModelSerializer):
tags = serializers.SerializerMethodField()
def get_tags(self, obj):
if self.request.get('some_condition'):
data_tags = TagSerializer(obj.tags, many=True).data
data = map(data.pop('field_to_remove') for data in data_tags)
return list(data)
else:
return TagSerializer(obj.tags, many=True).data
Then, pass request to your serializer when you init it in your view.
serializer = ItemSerializer(data, context={'request':self.request})
You probaply using serializer for Tag model and declare it in ItemSerializer so view is showing full TagSerializer info.
If you want to show only pk field, just use default representation, don't declare special serializer for Tag in ItemSerializer
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
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.