Need help with serializer of django rest framework for ManyToMany Field .
Model
class Genre(models.Model):
genre = models.CharField(max_length=255)
def __unicode__(self):
return u'%s' % (self.genre)
class Movie(models.Model):
popularity = models.FloatField()
director = models.CharField(max_length=255)
imdb_score = models.FloatField()
name = models.CharField(max_length=255)
genre = models.ManyToManyField(Genre)
def __unicode__(self):
return u'%s' % (self.name)
Serializer
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = ('popularity', 'director', 'genre', 'imdb_score', 'name')
depth = 1
View
class MovieList(APIView):
def get(self, request, format=None):
movies = Movie.objects.all()
serializer = MovieSerializer(movies, many=True)
return Response(serializer.data)
The result of the API using the above serializer is below,
{
"popularity":83.0,
"director":"Victor Fleming",
"genre":[
{"id":1,"genre":"Adventure"},
{"id":2,"genre":"Family"},
{"id":3,"genre":"Fantasy"},
{"id":4,"genre":"Musical"}
],
"imdb_score":8.3,
"name":"The Wizard of Oz"
}
How can I exclude id and genre so that the output is like shown below.
{
"99popularity": 83.0,
"director": "Victor Fleming",
"genre": [
"Adventure",
" Family",
" Fantasy",
" Musical"
],
"imdb_score": 8.3,
"name": "The Wizard of Oz"
}
What you will most likely want to do is use a StringRelatedField for the genre field on your MovieSerializer. For instance:
class MovieSerializer(serializers.ModelSerializer):
genre = serializers.StringRelatedField(many=True)
class Meta:
model = Movie
fields = ('popularity', 'director', 'genre', 'imdb_score', 'name')
depth = 1
Nested serializing is the most effective way to handle this problem. The issue with "StringRelatedField" is it's read-only and it will raise "StringRelatedField.to_internal_value() must be implemented" error during post/update. Take a look to the update that I made on your code:
Genre Serializer
class GenreSerializer(serializers.ModelSerializer):
class Meta:
model = Genre
fields = ('genre')
Movie serializer
class MovieSerializer(serializers.ModelSerializer):
genre = GenreSerializer(many=True)
class Meta:
model = Movie
fields = ('popularity', 'director', 'genre', 'imdb_score',
'name')
depth = 1
#Method to manage create/post requests
def create(self, validated_data):
genre_data = validated_data.pop('genre')
movie = MoviewModel.objects.create(**validated_data)
for genre_data in genre_data:
GenreModel.objects.create(movie=movie, **genre_data)
return movie
Navigate to DRF documentation for more:
Related
Let's say we have such models.
class Product(models.Model):
name = models.CharField(max_length=100)
# ...
main_photo = models.ImageField(upload_to='photos/')
class ProductPhoto(models.Model):
product = models.ForeignKey(Product, related_name='photos', on_delete=models.CASCADE)
photo = models.ImageField(upload_to='photos/')
def __str__(self):
return self.photo.url
I have two views:
ProductsView. It provides list of products with general information about each one, including name, ..., main_photo only.
ProductDetailsView. It provides more detailed info, including all photos.
class ProductsView(ListAPIView):
serializer_class = ProductSerializer
class ProductDetailsView(RetrieveAPIView):
serializer_class = ProductDetailsSerializer
serializers:
class ProductSerializer(ModelSerializer):
class Meta:
model = Product
fields = ('id', 'name', 'main_photo')
class ProductDetailsSerializer(ModelSerializer):
photos = StringRelatedField(many=True)
class Meta:
model = Product
fields = ('id', 'name', 'main_photo', 'photos')
I want detailed view to provide all photos in flat array photos, like this [main_photo, ...rest_photos].
In other words,
In response to detailed view instead of this:
{
"id": 1,
"name": "name",
"main_photo": "/media/photos/main_photo.jpg",
"photos": [
"/media/photos/photo1.jpg",
"/media/photos/photo2.jpg",
"/media/photos/photo3.jpg"
],
}
I want to get this:
{
"id": 1,
"name": "name",
"photos": [
"/media/photos/main_photo.jpg",
"/media/photos/photo1.jpg",
"/media/photos/photo2.jpg",
"/media/photos/photo3.jpg"
],
}
How can I do this with django rest framework? On which level should this logic be implemented? Model, View, Serializer?
I think it should be somewhere here, but not quite sure how should it look.
class ProductDetailsView(RetrieveAPIView):
serializer_class = ProductDetailsSerializer
def get_queryset(self):
query_set = Product.objects.all()
# ...
return query_set
For url of the photos add a __str__ method in ProductPhoto which will return only url of the photo
class ProductPhoto(models.Model):
...
def __str__(self):
return self.photo.url
and change ProductDetailsSerializer like this
class ProductDetailsSerializer(ModelSerializer):
photo_list = serializers.SerializerMethodField()
def get_photo_list(self, obj):
db_photos = obj.photos.all()
result = []
if obj.main_photo:
result.append(obj.main_photo.url)
for p in db_photos:
result.append(p.photo.url)
return result
class Meta:
model = Product
fields = ('id', 'name', 'photo_list')
For more relation related documentation for DRF check this
Model:
class Genre(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Song(models.Model):
name = models.CharField(max_length=200)
genre = models.ManyToManyField(Genre)
Serializers:
class GenreSerializer(serializers.ModelSerializer):
class Meta:
model = Genre
fields = '__all__'
class SongSerializer(serializers.ModelSerializer):
class Meta:
model = Song
fields = '__all__'
def to_representation(self, instance):
rep = super().to_representation(instance)
print(GenreSerializer(instance.name).data)
return rep
The above print in serializer gives: {'name': None}
and the response is with the Genre ids instead of values:
[
{
"id": 1,
"name": "Abcd",
"genre": [
1,
3
]
}
]
where genre 1. Pop and 3. Rock
What changes can I make to to_representation to print the values instead of the ids of Genre which is a ManyToManyField with Song Model.
You were almost there, try this
class SongSerializer(serializers.ModelSerializer):
class Meta:
model = Song
fields = '__all__'
def to_representation(self, instance):
rep = super().to_representation(instance)
rep["genre"] = GenreSerializer(instance.genre.all(), many=True).data
return rep
Although the above answer is correct, you can also do it this way.
class SongSerializer(serializers.ModelSerializer):
genre= serializers.StringRelatedField(many=True)
class Meta:
model = Song
fields = '__all__'
https://www.django-rest-framework.org/api-guide/relations/#stringrelatedfield
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',
]
I am new to Python and Django. I am creating api using Django-Rest-Framework I want to serializer data that can accept json in below format:
{
"ingredients": ["Sugar","Egg"],
"name": "Cake",
"description": "Dinner Food",
"directions": "direction1"
}
However I am able to persist data in db with below format:
{
"ingredients": [{"name":"Cake"},{"name":"Egg"}],
"name": "Rice",
"description": "Dinner Food",
"directions": "direction1"
}
I am not sure how can I convert dictionary in to the set field. I am aware of List field and list serialiser but not sure how to use them.
Is it possible to do this using model serialiser?
Serializer.py
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingredient
fields = '__all__'
class RecipeSerializer(serializers.ModelSerializer):
ingredients = IngredientSerializer(many=True)
class Meta:
model = Recipe
fields = '__all__'
def create(self, validated_data):
ingredients_data = validated_data.pop('ingredients')
print(ingredients_data)
recipe = Recipe.objects.create(**validated_data)
for ingredient in ingredients_data:
ingredient, created = Ingredient.objects.get_or_create(name=ingredient['name'])
recipe.ingredients.add(ingredient)
return recipe
Model.py
class Ingredient(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Recipe(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(blank=True, null=True)
directions = models.TextField()
ingredients = models.ManyToManyField(Ingredient)
def __str__(self):
return self.name
view.py
class RecipieView(viewsets.ModelViewSet):
queryset = Recipe.objects.all()
serializer_class = RecipeSerializer
class IngredientView(viewsets.ModelViewSet):
queryset = Ingredient.objects.all()
serializer_class = IngredientSerializer
I would recommend you to use two different serializers for creation purpose and others. See the below snippet,
views.py
class RecipieView(viewsets.ModelViewSet):
queryset = Recipe.objects.all()
serializer_class = RecipeMainSerializer
def get_serializer_class(self):
if self.action == 'create':
return RecipeCreateSerializer
return RecipeMainSerializer
serializer.py
class RecipeCreateSerializer(serializers.ModelSerializer):
ingredients = serializers.ListField(write_only=True)
class Meta:
model = Recipe
fields = '__all__'
def create(self, validated_data):
ingredients_data = validated_data.pop('ingredients')
recipe = Recipe.objects.create(**validated_data)
for ingredient in ingredients_data:
ingredient, created = Ingredient.objects.get_or_create(name=ingredient)
recipe.ingredients.add(ingredient)
return recipe
class RecipeMainSerializer(serializers.ModelSerializer):
ingredients = IngredientSerializer(many=True)
class Meta:
model = Recipe
fields = '__all__'
We can refactor #JPG serializers in better way like this:
class RecipeMainSerializer(serializers.ModelSerializer):
ingredients = IngredientSerializer(many=True)
class Meta:
model = Recipe
fields = '__all__'
class RecipeCreateSerializer(RecipeMainSerializer):
ingredients = serializers.ListField(write_only=True)
def create(self, validated_data):
ingredients_data = validated_data.pop('ingredients')
recipe = Recipe.objects.create(**validated_data)
for ingredient in ingredients_data:
ingredient, created = Ingredient.objects.get_or_create(name=ingredient)
recipe.ingredients.add(ingredient)
return recipe
This improve dry principle of our codes in more complicated serializers
I have setup my serializer to return nested content successfully.
However, I have not been able to post data within the nested fields.
I don't get an error when posting the data- but it only posts to the non-nested fields.
I would like for it to take the "name" field OR primary key (of model "TAG") for posting item.
Models.py
class Tag(models.Model):
name = models.CharField("Name", max_length=5000, blank=True)
def __str__(self):
return self.name
class Movie(models.Model):
title = models.CharField("Whats happening?", max_length=100, blank=True)
tag = models.ManyToManyField('Tag', blank=True)
def __str__(self):
return self.title
Serializers.py:
class TagSerializer(serializers.ModelSerializer):
taglevel = filters.CharFilter(taglevel="taglevel")
class Meta:
model = Tag
fields = ('name', 'taglevel', 'id')
class MovieSerializer(serializers.ModelSerializer):
tag = TagSerializer(many=True, read_only=False)
info = InfoSerializer(many=True, read_only=True)
class Meta:
model = Movie
fields = ('title', 'tag', 'info')
def validate(self, data):
print(self.initial_data.__dict__)
data['tag_name'] = []
if 'tag' in self.initial_data.keys():
for entry in self.initial_data['tag']:
data['tag_name'].append(entry['name'])
return data
def create(self, validated_data):
print(validated_data)
tags_data = validated_data.pop('tag')
movie = Task.objects.create(**validated_data)
for tag_data in tags_data:
Movie.objects.create(name=name, **tag_data)
return movie
Sample of posting data:
r = requests.post('http://localhost:8000/api/Data/',{ "title": "TEST_title", "tag": [ { "name": "test1", "name": "test2" } ], "info": [] })
Your json should be.
{
"title": "TEST_title",
"tag": [ {"name": "test1" },
{"name": "test2"}
],
"info": []
}
class TagSerializer(serializers.ModelSerializer):
taglevel = filters.CharFilter(taglevel="taglevel")
class Meta:
model = Tag
fields = ('name', 'taglevel', 'id')
class MovieSerializer(serializers.ModelSerializer):
tag = TagSerializer(many=True, read_only=False)
info = InfoSerializer(many=True, read_only=True)
class Meta:
model = Movie
fields = ('title', 'tag')
def create(self, validated_data):
tags_data = validated_data.pop('tag')
movie = Movie.objects.create(**validated_data)
for tag_data in tags_data:
movie.tag.create(**tag_data)
return movie