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
Related
Im new to DRF and i have problem with a nested serializer. I cannot save/create the list of ingredients in recipe.
I'll start with model.Recipe that has a attribute ingredients
ingredients = models.ManyToManyField(Ingredient,
through='IngredientInRecipe',
blank=True)
model.IngredientInRecipe has the following attributes (i need through='IngredientInRecipe' because of the "amount" field)
class IngredientInRecipe(models.Model):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
ingredient = models.ForeignKey(Ingredient, on_delete=models.SET_NULL, null=True)
amount = models.DecimalField(max_digits=6,
decimal_places=1,
validators=[MinValueValidator(1)]
)
When sending a POST query to /recipes/ with data, i get the following errror
{
"ingredients": [
{
"id": 1,
"amount": 10
}
],
"tags": [
1,
2
],
"name": "Recipe 1",
"text": "Recipe 1",
"cooking_time": 1
}
TypeError at /api/recipes/
Field 'id' expected a number but got (<IngredientInRecipe: IngredientInRecipe object (14)>, True).
the RecipeSerializer looks like below and debug show that the problem is in
recipe_instance.ingredients.add(ingredient_in_recipe_instance)
class RecipeSerializer(serializers.ModelSerializer):
"""Serializer for recipe objects"""
author = UserSerializer(required=False)
name = serializers.CharField(max_length=200, required=True)
image = serializers.ImageField(required=False)
ingredients = IngredientInRecipeSerializer(many=True)
tags = serializers.PrimaryKeyRelatedField(
many=True,
queryset=Tag.objects.all()
)
def create(self, validated_data):
tags_data = validated_data.pop('tags')
ingredients_data = validated_data.pop('ingredients')
recipe_instance = Recipe.objects.create(author=self.context['request'].user, **validated_data)
for tag in tags_data:
recipe_instance.tags.add(tag.id)
for ingredient in ingredients_data:
ingredient_instance = get_object_or_404(Ingredient,id=ingredient['id'])
ingredient_in_recipe_instance = IngredientInRecipe.objects.get_or_create(ingredient=ingredient_instance,
amount=ingredient['amount'],
recipe = recipe_instance)
recipe_instance.ingredients.add(ingredient_in_recipe_instance)
return recipe_instance
IngredientInRecipeSerializer - any thoughts would be greate
class IngredientInRecipeSerializer(serializers.ModelSerializer):
"""Serializer for ingredient in recipe objects"""
id = serializers.IntegerField()
amount = serializers.DecimalField(max_digits=6,
decimal_places=1)
name = serializers.StringRelatedField()
measurement_unit = serializers.StringRelatedField()
class Meta:
model = IngredientInRecipe
fields = (
'id',
'amount',
'name',
'measurement_unit',
)
IngredientInRecipe.objects.get_or_create returns a tuple of (object, created), not the object itself
I have one-to-many relation Developer and Constructions
I want to serialize all Developer field when I serialize Construction object
For request
{
"developer": 1,
"name": "as2",
"address": "ZZZZZZZZZZZZZsdf",
"deadline": "2020-05-13 14:26:58",
"coordinates": { "latitude": 49.8782482189424, "longitude": 24.452545489 }
}
I have an error:
{
"developer_data": [
"This field is required."
]
}
It's strange for me because developer_data marked as read_only
How can I fix the error? I thin that problem deals with serializer
models.py
class Developer(models.Model):
name = models.CharField(max_length=100)
...
def name_image(instance, filename):
return '/'.join(['images', str(instance.name), filename])
class Construction(models.Model):
developer = models.ForeignKey(
Developer, related_name="constructions", on_delete=models.CASCADE
)
name = models.CharField(max_length=100)
image = models.ImageField(upload_to=name_image, blank=True, null=True)
...
serializers.py (UPDATED)
class DeveloperSerializer(serializers.ModelSerializer):
constructions_number = serializers.SerializerMethodField()
workers_number = serializers.SerializerMethodField()
machines_number = serializers.SerializerMethodField()
class Meta:
model = Developer
fields = ('id', 'name', 'constructions_number', 'workers_number', 'machines_number')
def create(self, validated_data):
instance = super().create(validated_data=validated_data)
return instance
def get_constructions_number(self, obj):
return obj.constructions.count()
def get_workers_number(self, obj):
res = obj.constructions.aggregate(Sum('workers_number'))['workers_number__sum']
if res:
return res
return 0
def get_machines_number(self, obj):
res = obj.constructions.aggregate(Sum('machines_number'))[ "machines_number__sum"]
if res:
return res
return 0
class ConstructionSerializer(serializers.ModelSerializer):
coordinates = PointField()
deadline = serializers.DateTimeField(format=TIME_FORMAT)
cameras_number = serializers.SerializerMethodField()
developer = DeveloperSerializer()
class Meta:
model = Construction
fields = (
'id', 'developer', 'name', 'image', 'address', 'coordinates', 'deadline',
'workers_number', 'machines_number', 'cameras_number',
)
read_only_fields = ('workers_number', 'machines_number', 'cameras_number')
def create(self, validated_data):
instance = super().create(validated_data=validated_data)
return instance
def get_cameras_number(self, obj):
return obj.cameras.count()
I use standard ModelViewSet for the models in views.py and i think that problem in serializers.py
You are using the developer serializer wrong, change the ConstructionSerializer to this
class ConstructionSerializer(serializers.ModelSerializer):
coordinates = PointField()
deadline = serializers.DateTimeField(format=TIME_FORMAT)
cameras_number = serializers.SerializerMethodField()
developer = DeveloperSerializer()
class Meta:
model = Construction
fields = (
'id', 'developer', 'name', 'image', 'address', 'coordinates', 'deadline',
'workers_number', 'machines_number', 'cameras_number',
)
read_only_fields = ('workers_number', 'machines_number', 'cameras_number', 'developer') # if you don't want developer to be read only then remove it from there
def create(self, validated_data):
instance = super().create(validated_data=validated_data)
return instance
def get_cameras_number(self, obj):
return obj.cameras.count()
I have a couple models, one of which is already populated with data (book name/ chapter number/ paragraph number data), and I am implementing the feature for each user to be able to add a note per each unique book name/ chapter number/ paragraph number, which I could, but I have been stack for a couple of days trying to retrieve books with the related_name note of the current user if they have any. Here are my models:
Book model that is already populated with data.
from django.db import models
class Book(models.Model):
day = models.CharField(max_length=128)
book = models.CharField(max_length=128)
chapter = models.CharField(max_length=256)
paragraph = models.CharField(max_length=256)
text = models.TextField()
link = models.CharField(max_length=256)
def __str__(self):
return f'{self.book}_{self.chapter}.{self.paragraph} '
class Meta:
ordering = ['-id']
verbose_name = "Paragraph"
verbose_name_plural = "Paragraph"
Here is the Note model that should store the current user's note regarding a specific unique book name / chapter number / paragraph number:
from django.db import models
from django.conf import settings
from rest_framework.reverse import reverse
from paragraphs.models import Book
class Note(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='author', on_delete=models.CASCADE)
paragraph = models.ForeignKey(Book, related_name='note', on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
text = models.TextField(default=None)
def __str__(self):
return f'Note on {self.paragraph}'
class Meta:
ordering = ['created']
def save(self, *args, **kwargs):
"""
"""
options = {'text': self.text} if self.text else {}
super(Note, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('note-detail', args=[self.id])
Here are my serializers:
Book serializer
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
Note serializer
from rest_framework import serializers
from .models import Note
from users.serializers import UserSerializer
from paragraphs.serializers import BookSerializer
class NoteSerializer(serializers.ModelSerializer):
owner = UserSerializer(many=False, read_only=True)
class Meta:
model = Note
fields = ['id', 'owner', 'paragraph', 'text', 'created']
def to_representation(self, instance):
self.fields['paragraph'] = BookSerializer(read_only=True)
return super(NoteSerializer, self).to_representation(instance)
def user(self):
request = self.context.get('request', None)
if request:
return request.user
return None
def create(self, validated_data):
note, _ = Note.objects.update_or_create(
owner=self.user(),
paragraph=validated_data.get('paragraph', None),
defaults={'text': validated_data.get('text', None)})
return note
The data I am getting:
{
"id": 25,
"day": "2",
"book": "Some book",
"chapter": "1",
"paragraph": "3",
"text": "This is an example text that the user would like to attach a note to",
"link": "https://somelink.com",
}
The data I am trying to get:
{
"id": 25,
"day": "2",
"book": "Some book",
"chapter": "1",
"paragraph": "3",
"text": "This is an example text that the user would like to attach a note to",
"link": "https://somelink.com",
"note": "note of current user or none"
}
Any help is appreciated
models.py:
class Book(models.Model):
day = models.CharField(max_length=128)
book = models.CharField(max_length=128)
chapter = models.CharField(max_length=256)
paragraph = models.CharField(max_length=256)
text = models.TextField()
link = models.CharField(max_length=256)
def __str__(self):
return f'{self.book}_{self.chapter}.{self.paragraph} '
class Meta:
ordering = ['-id']
class Note(models.Model):
owner = models.ForeignKey(to = User, related_name='author', on_delete=models.CASCADE)
book = models.ForeignKey(Book, related_name='note', on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
text = models.TextField(default=None)
def __str__(self):
return '%s(%s)' %(self.owner,self.book)
serializers.py:
class NoteSerializer(serializers.ModelSerializer):
class Meta:
model = Note
fields = ['id', 'owner', 'book', 'text', 'created']
class BookSerializer(serializers.ModelSerializer):
note = serializers.StringRelatedField(many=True, read_only=True)
# note = NoteSerializer(many=True, read_only=True)
class Meta:
model = Book
fields = ['day','book','chapter','paragraph','text','link','note']
Output:
{
"day": "2",
"book": "some book",
"chapter": "1",
"paragraph": "example",
"text": "some textttttttttttttttttttttttttttttttttttttttttttttttt",
"link": "http://127.0.0.1:8000/admin/api/book/add/",
"note": [
"admin(some book_1.example )"
]
}
It's return all field:
class NoteSerializer(serializers.ModelSerializer):
class Meta:
model = Note
fields = ['id', 'owner', 'book', 'text', 'created']
class BookSerializer(serializers.ModelSerializer):
# note = serializers.StringRelatedField(many=True, read_only=True)
note = NoteSerializer(many=True, read_only=True)
class Meta:
model = Book
fields = ['day','book','chapter','paragraph','text','link','note']
Output:
{
"day": "2",
"book": "some book",
"chapter": "1",
"paragraph": "example",
"text": "some textttttttttttttttttttttttttttttttttttttttttttttttt",
"link": "http://127.0.0.1:8000/admin/api/book/add/",
"note": [
{
"id": 2,
"owner": 1,
"book": 1,
"text": "saaaaaaaaaaaaaaaaaaa",
"created": "2021-02-24T14:34:13.279750Z"
}
]
}
What you're actually trying to achieve is for the NoteSerializer to include fields from the Foreign-key related book model. Overriding the to_representation method of the serializer is clunky and not the way to go. See here a better approach.
Context
I have 2 models: Customer & DeviceGroup.
Currently I have an API endpoint /api/v1/device-groups/?customer_uuid=<customer_uuid> which returns the DeviceGroups that are related to the given Customer like this:
[
{
"group_uuid": "c61361ac-0826-41bb-825a-8aa8e014ae0c",
"device_group_name": "Default",
"color": "0a2f45",
"is_default": true
},
{
"group_uuid": "1a86e8e4-b41b-4f33-aefb-ce984ef96144",
"device_group_name": "Testgroup",
"color": "123456",
"is_default": false
}
]
Goal
I want the array of DeviceGroups be part of an object like this:
"device_groups":
[
{
"group_uuid": "c61361ac-0826-41bb-825a-8aa8e014ae0c",
"device_group_name": "Default",
"color": "0a2f45",
"is_default": true
},
{
"group_uuid": "1a86e8e4-b41b-4f33-aefb-ce984ef96144",
"device_group_name": "Testgroup",
"color": "123456",
"is_default": false
}
]
Models
# models.py
class Customer(models.Model):
customer_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, db_index=True)
customer_name = models.CharField(max_length=128, unique=True)
class DeviceGroup(models.Model):
group_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, db_index=True)
customer_uuid = models.ForeignKey(Customer, on_delete=models.DO_NOTHING)
device_group_name = models.CharField(max_length=20)
color = models.CharField(max_length=10)
is_default = models.BooleanField(default=False)
Serializer
# serializers.py
class DeviceGroupSerializer(serializers.ModelSerializer):
class Meta:
model = DeviceGroup
fields = ('group_uuid', 'device_group_name', 'color', 'is_default')
View
# views.py
class DeviceGroupCustomerViewSet(viewsets.ModelViewSet):
serializer_class = DeviceGroupSerializer
def get_queryset(self):
return DeviceGroup.objects.filter(customer_uuid=self.request.GET['customer_uuid'])
I tried creating a new serializer but it did not solve my problem:
class TestSerializer(serializers.ModelSerializer):
device_groups = DeviceGroupSerializer(many=True, read_only=True)
class Meta:
model = DeviceGroup
fields = ('device_groups', 'group_uuid', 'device_group_name', 'color', 'is_default')
What do I need to change in order to get my desired output?
you can update your views like
def list(self, request):
queryset = DeviceGroup.objects.filter(customer_uuid=self.request.GET['customer_uuid'])
serializer = UserSerializer(queryset, many=True)
return Response({'device_groups': serializer.data})
this will get the desired output..
Just modify your new serializer named TestSerializer in the following way.
class TestSerializer(serializers.Serializer):
device_groups = serializers.SerializerMethodField(read_only=True)
def get_device_groups(self, model):
return DeviceGroupSerializer(model).data
The response will be a paginated response. If you want to disable it just mention pagination_class as None in your ModelViewset class.
To achieve this fairly easily without loosing the pagination, I would do this:
from rest_framework.pagination import PageNumberPagination
class DeviceGroupPagination(PageNumberPagination):
def get_paginated_response(self, data):
return Response(OrderedDict([
('count', self.page.paginator.count),
('next', self.get_next_link()),
('previous', self.get_previous_link()),
('device_groups', data)
]))
Then in the views
class DeviceGroupCustomerViewSet(viewsets.ModelViewSet):
serializer_class = DeviceGroupSerializer
pagination_class = DeviceGroupPagination
...
So now, in place of results, you will have device_groups
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: