Nested Serializer for Many to Many - python

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

Related

How to override related field in django?

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

How can I display the values of a ManyToMany Field in Django Rest Framework instead of their Ids?

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

POST on ModelViewSet with nested object Django Rest Framework

I'm building an API with the django rest framework. I have these models:
class Organisme(models.Model):
nom = models.CharField(max_length=255)
adresse = models.ForeignKey(Adresse, on_delete=models.CASCADE)
class Adresse(models.Model):
rue = models.CharField(max_length=255, blank=True)
This is the view for my mode Organisme :
class OrganismeViewSet(viewsets.ModelViewSet):
queryset = Organisme.objects.all()
serializer_class = OrganismeSerializer
pagination_class = StandardResultsSetPagination
filter_backends = (filters.SearchFilter, DjangoFilterBackend)
filter_class = OrganismeFilter
search_fields = ('nom')
And my serializer:
class OrganismeSerializer(serializers.ModelSerializer):
class Meta:
model = Organisme
fields = '__all__'
depth = 1
So I'm trying to create a new Organisme by sending this:
{
"adresse": {
"rue": "test"
},
"nom":"TestTest",
}
or
{
"adresse": pk_id,
"nom":"TestTest",
}
But I always end up with this error:
IntegrityError at /organismes/
(1048, "Column 'adresse_id' cannot be null")
If you guys know how to proceed... Thank you in advance.
You need to override create method to make writable nested serializer:
class AddressSerializer(serializers.ModelSerializer):
class Meta:
model = Addresse
fields = ('rue',)
class OrganismeSerializer(serializers.ModelSerializer):
addresse = AddressSerializer()
class Meta:
model = Organisme
fields = '__all__'
def create(self, validated_data):
address_data = validated_data.pop('adresse')
address = Adresse.objects.create(**address_data)
organism = Organisme.objects.create(address=address, **validated_data)
return organism

Serialize related models and override the create method

I am new to DRF and I want to do something similar to the formsets in django forms
I have an Invoice And Products models related to each other throw a many to many InvoiceDetail model.. when I create an Invoice I choose some products and create a InvoiceDetail object for each .. I want to do this in DRF how can I serialize the Invoice model and it's create function then?
or should i do it form the view?
models.py:
class Invoices(models.Model):
#some fields
products = models.ManyToManyField('Products', through='InvoiceDetail')
class Products(models.Model):
#some fields
class InvoiceDetail(models.Model):
invoice = models.ForeignKey(Invoices, related_name='parent_invoice')
product = models.ForeignKey(Products, related_name='parent_product')
product_description = models.TextField()
product_price = models.DecimalField(max_digits=9, decimal_places=2)
quantity_sold = models.IntegerField()
serializers.py:
class ProductsSerializer(serializers.ModelSerializer):
class Meta:
model = Products
fields = ('barcode', 'product_code', 'name', 'description', 'category',
'quantity_in_stock', 'quantity_on_hold', 'expire_date',
'vendor', 'manufacturer', 'discount')
class InvoiceDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = InvoiceDetail
fields = '__all__'
view.py:
class ProductsView(viewsets.ReadOnlyModelViewSet):
queryset = Products.objects
serializer_class = ProductsSerializer
class InvoicesView(viewsets.ModelViewSet):
queryset = Invoices.objects
serializer_class = InvoicesSerializer
class InvoiceDetailView(viewsets.ModelViewSet):
queryset = InvoiceDetail.objects
serializer_class = InvoiceDetailsSerializer
You could do this in the serializer itself,
class InvoiceSerializer(serializers.ModelSerializer):
products = serializers.PrimaryKeyRelatedField(queryset=Product.objects.all(), many=True)
class Meta:
model = Invoice
fields = [ f.name for f in model._meta.fields ] + ['products']
def create(self, validated_data):
products = validated_data.pop('products')
invoice = super(InvoiceSerializer, self).create(validated_data)
for product in products:
InvoiceDetail.objects.create(invoice=invoice, product=product)
return invoice
This, is just a basic example for to know about how this works. You could customise it however you need.

Serializers in django rest framework

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:

Categories

Resources