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
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 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
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.
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: