Django rest framework serializing many to many field - python

How do I serialize a many-to-many field into list of something, and return them through rest framework? In my example below, I try to return the post together with a list of tags associated with it.
models.py
class post(models.Model):
tag = models.ManyToManyField(Tag)
text = models.CharField(max_length=100)
serializers.py
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ("text", "tag"??)
views.py
class PostViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer

You will need a TagSerializer, whose class Meta has model = Tag. After TagSerializer is created, modify the PostSerializer with many=True for a ManyToManyField relation:
class PostSerializer(serializers.ModelSerializer):
tag = TagSerializer(read_only=True, many=True)
class Meta:
model = Post
fields = ('tag', 'text',)
Answer is for DRF 3

This is what I did, let´s suppose a Book can have more than one author and an Author can have more than one book:
On Model:
class Author(models.Model):
name = models.CharField(max_length=100, default="")
last_name = models.IntegerField(default=0)
class Book(models.Model):
authors = models.ManyToManyField(Author, related_name="book_list", blank=True)
name = models.CharField(max_length=100, default="")
published = models.BooleanField(default=True)
On Serializers:
class BookSerializer(serializers.ModelSerializer):
authors = serializers.PrimaryKeyRelatedField(queryset=Author.objects.all(), many=True)
class Meta:
model = Book
fields = ('id', 'name', 'published', 'authors')
class AuthorSerializer(serializers.ModelSerializer):
book_list = BookSerializer(many=True, read_only=True)
class Meta:
model = Author
fields = ('id', 'name', 'last_name', 'book_list')

Adding to #Brian's answer
"tags": [{"name": "tag1"}] can be simplified to "tags": ["tag1", "tag2",...] in this way:
class TagListingField(serializers.RelatedField):
def to_representation(self, value):
return value.name
class PostSerializer(serializers.ModelSerializer):
tag = TagListingField(many=True, read_only=True)
class Meta:
...
More info here: https://www.django-rest-framework.org/api-guide/relations/#custom-relational-fields

The default ModelSerializer uses primary keys for relationships. However, you can easily generate nested representations using the Meta depth attribute:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ("text", "tag")
depth = 1
As mentioned in the documentation :
The depth option should be set to an integer value that indicates the depth of relationships that should be traversed before reverting to a flat representation.

This works for me.
tag = TagSerializer(source="tag", read_only=True, many=True)

Django 2.0
For many to many field, if you want specific one:
class QuestionSerializer(serializers.ModelSerializer):
topics_list = serializers.SerializerMethodField()
def get_topics_list(self, instance):
names = []
a = instance.topics.get_queryset()
for i in a:
names.append(i.desc)
return names
class Meta:
model = Question
fields = ('topics_list',)

In the serializer on init method you can pass the queryset to the field and rest_framework valide the ids on that queryset
1) first extend your serializer from serializers.ModelSerializer
class YourSerializer(serializers.ModelSerializer):
2) include the field on the meta class
class YourSerializer(serializers.ModelSerializer):
class Meta:
fields = (..., 'your_field',)
3) in the init method:
def __init__(self, *args, **kwargs):
super(YourSerializer, self).__init__(*args, **kwargs)
self.fields['your_field].queryset = <the queryset of your field>
You can limit the queryset for that field under any argument using filter or exclude like normally you do. In case that you want include all just use .objects.all()

models.py
class Tag(models.Model):
name = models.CharField(max_length=100)
# ===============
# ... rest of the fields ...
class Post(models.Model):
tag = models.ManyToManyField(Tag)
text = models.CharField(max_length=100)
serialiazers.py
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ("text", "tag")
views.py
## FUNCTION BASED VIEW
def fbvPost_ListView(request):
# list
if request.method == "GET":
posts = Post.objects.all()
serializer = PostSerializer(instance=posts, many=True)
return JsonResponse(serializer.data, safe=False)
return JsonResponse({"success": False})
# ===========================================================
## CLASS BASED VIEW
class cbvPost_ListView(viewsets.ReadOnlyModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
NB: Tag, Post are two models & we need to serialize them. Here, Post model have a dependency of Tag models, so here we explicitly mention it, [tags = TagSerializer(many=True, read_only=True)] or its return it's primary field value.
DETAILS HERE

Hi I will be showing many to many for update and create. The context is the event can have many dances and dances can have many event.
The request will be as followed.
{
"competition": 2,
"title": "the title",
"dances":[ {"id":1},{"id":2}],
"description": "the desc"
}
The Create Function will be as followed.
def create(self, validated_data):
try:
dance_ids = []
for dance in self.initial_data['dances']:
if 'id' not in dance:
raise serializers.ValidationError({'detail': 'key error'})
dance_ids.append(dance['id'])
new_event = models.Event.objects.create(**validated_data)
if dance_ids:
for dance_id in dance_ids:
new_event.dances.add(dance_id)
new_event.save()
return new_event
except Exception as e:
raise serializers.ValidationError({'detail': e})
The Update Function will be as followed.
def update(self, instance, validated_data):
# Delete all records of genres.
try:
for current_genre in instance.dances.all():
instance.dances.remove(current_genre)
# Repopulate genres into instance.
for dance in self.initial_data['dances']:
if 'id' not in dance:
raise serializers.ValidationError({'detail': 'key error'})
dance_obj = models.Dance.objects.get(pk=dance['id'])
instance.dances.add(dance_obj)
event_updated = super().update(instance, validated_data)
return event_updated
except Exception as e:
raise serializers.ValidationError({'detail': e})
If you want to just do "dances":[1,2] instead, just make some amendments to the
for dance in self.initial_data['dances']:
if 'id' not in dance:
raise serializers.ValidationError({'detail': 'key error'})
dance_ids.append(dance['id'])
part. I hope this will be able to help yall out! :)

First, Tag needs its own serializer too
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
Then in your PostSerializer, add one line
class PostSerializer(serializers.ModelSerializer):
tag = TagSerializer(read_only=True, many=True).data
class Meta:
model = Post
fields = ("text", "tag")
This will make it so your Tag field in Post is an array of tag ids. if you don't put the ".data" part, it will put all of the attributes of tag, which is probably too much in most cases

You can use serializers.SlugRelatedField() or serializers.StringRelatedField(many=True) Serializer relations
In your case :
class PostSerializer(serializers.ModelSerializer):
tag = serializers.StringRelatedField(many=True) # this will return a list
class Meta:
model = Post
fields = ('tag', 'text',)

Related

getting null in object of many to many relation object django

I'm saving an object with many to many relations in the database but when I'm fetching it so so it returns me IDs of that relational object but I want to the whole object instead, so I have added a property in the serializer and after that, it returns me the object.name only and the value of it is null, I don't know why it is behaving like this? anyone can help?
views.py
queryset = Tag.objects.filter(project_id=project_id).all()
serializer = TagSerializer(queryset, many=True)
return Response(serializer.data)
serializers.py
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
# fields = '__all__'
fields = ['id', 'name', 'description', 'singleline']
singleline = SinglelineSerializer(read_only=True)
models.py
class Tag(models.Model):
name = models.CharField(max_length=255, default='')
description = models.CharField(max_length=255, default='')
singleline = models.ManyToManyField(Singleline)
class Meta:
db_table = 'tags'
A Tag can have many Singlelines, you need to work with many=True since it is a collection:
class TagSerializer(serializers.ModelSerializer):
singleline = SinglelineSerializer(read_only=True, many=True)
class Meta:
model = Tag
fields = ['id', 'name', 'description', 'singleline']
You can improve efficiency of the view by prefetcing the singleline relation and thus load all the related Singlelines in bulk:
queryset = Tag.objects.filter(project_id=project_id).prefetch_related('singleline')
serializer = TagSerializer(queryset, many=True)
return Response(serializer.data)

How to make a ManyToMany serialization to be able to create a object in the POST request?

I'm trying to create a serialize to handle a ManyToMany relation, but it's not working. I have read the documentation and I probably doing something wrong. Also I have read the answers here.
Here are my models.
class Author(models.Model):
name = models.CharField(verbose_name="Name", max_length=255)
class Book(models.Model):
author = models.ForeignKey(
Author,
related_name="id_author",
blank=True,
null=True,
on_delete=models.PROTECT)
price = models.FloatField(verbose_name="Price")
class FiscalDocument(models.Model):
seller = models.ForeignKey(
User,
related_name="user_id",
on_delete=models.PROTECT)
book = models.ManyToManyField(Book)
My serializer:
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ('id', 'name')
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('id', 'author', 'price')
def to_representation(self, instance):
response = super().to_representation(instance)
response['author'] = AuthorSerializer(instance.author).data
return response
class FiscalDocumentSerializer(serializers.ModelSerializer):
book = BookSerializer()
class Meta:
model = FiscalDocument
fields = ('id', 'seller', 'book')
def create(self, validated_data):
book_data = validated_data.pop('book')
fiscal_document = FiscalDocument.objects.create(**validated_data)
Book.objects.create(FiscalDocument=FiscalDocument,**medicine_data)
return fiscal_document
When I try to access the endpoint of the FiscalDocument, django-rest-framework is throwing an error:
Got AttributeError when attempting to get a value for field price on serializer BookSerializer. The serializer field might be named incorrectly and not match any attribute or key on the ManyRelatedManager instance. Original exception text was: ManyRelatedManager object has no attribute price.
If anyone can help XD.

Django Rest Framework Serializer Doesn't Display ALL Fields

I have a problem where DRF isn't displaying all of my fields correctly for a model class / reference table (specifically the primary key).
My Model Class looks like this (very simple):
class UnitOfIssue(models.Model):
code = models.CharField(max_length=2, primary_key=True)
description = models.CharField(max_length=16)
class Meta:
ordering = ('code',)
def __str__(self):
return "{0} - {1}".format(self.code, self.description)
My Serializer Looks like this:
class UnitOfIssueSerializer(serializers.HyperlinkedModelSerializer):
"""
"""
url = serializers.HyperlinkedIdentityField(
read_only=True,
view_name='unitofissue-detail',
format='html',
lookup_field='code')
class Meta:
model = UnitOfIssue
fields = ('code', 'description', 'url')
# fields = '__all__'
And I'm using a generic view:
class UnitOfIssueDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = UnitOfIssue.objects.all()
serializer_class = UnitOfIssueSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
lookup_field = 'code'
In order for the UnitOfIssue primary key code to be displayed in the auto-generated UI, I have to define fields = ('code', 'description', 'url') in the serializer instead of fields = '__all__'.
I want to just be able to use the '__all__' syntax but I can't figure out what's going wrong.
Also, I'm using Django==1.11.13 and djangorestframework==3.8.2
This issue plagued me for weeks, and yet it was such a simple error. I fixed it by changing the serializer base class from:
class UnitOfIssueSerializer(serializers.HyperlinkedModelSerializer):
to:
class UnitOfIssueSerializer(serializers.ModelSerializer):

Django rest framework serializer return a list instead of json

I have the following tags and posts objects in many to many relationship. What I try to return in the post serializer is to return the tags in a list (using Tag.name only) instead of json, what's the clean way of doing this?
serializers.py
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ('name', 'description', 'date_created', 'created_by')
class PostSerializer(serializers.ModelSerializer):
tags = TagSerializer(read_only=True, many=True)
class Meta:
model = Post
fields = ('post_id',
'post_link',
'tags')
Currently, the PostSerializer returns tags in json format with all fields, I just want it to return tags: ['tag1', 'tag2', 'tag3'] in a string list.
One way to do this is:
class PostSerializer(serializers.ModelSerializer):
tags = serializers.SerializerMethodField()
class Meta:
model = Post
fields = ('post_id', 'post_link', 'tags')
def get_tags(self, post):
return post.tags.values_list('name', flat=True)
Second way is with a property on the Post model:
class Post(models.Model):
....
#property
def tag_names(self):
return self.tags.values_list('name', flat=True)
class PostSerializer(serializers.ModelSerializer):
tag_names = serializers.ReadOnlyField()
class Meta:
model = Post
fields = ('post_id', 'post_link', 'tag_names')
One very simple solution for you might be to change this
tags = TagSerializer(read_only=True, many=True)
into this
tags = TagSerializer(read_only=True, many=True).data
this will list your tags as ids instead of listing all of the attributes of every tag

Cannot post entry specifying specific id if the id is specified as ForeignKey in Django

I have two models, Book and ReadBy as specified in models.py:
class Book(models.Model):
created = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(MyUser)
class ReadBy(models.Model):
created = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(MyUser)
book = models.ForeignKey(Book)
views.py:
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def pre_save(self, obj):
obj.owner = self.request.user
class ReadByViewSet(viewsets.ModelViewSet):
queryset = ReadBy.objects.all()
serializer_class = ReadBySerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def pre_save(self, obj):
obj.owner = self.request.user
serializers.py:
class BookSerializer(serializers.ModelSerializer):
readby = serializers.SerializerMethodField('get_ready_by')
def get_read_by(self, obj):
user = self.context['request'].user
book = obj
return ReadBy.objects.filter(book=book, owner=user).exists()
class Meta:
model = Book
fields = ('id', 'created', 'owner', 'readby')
class ReadBySerializer(serializers.ModelSerializer):
owner = serializers.Field(source='owner.id')
book = serializers.Field(source='book.id')
class Meta:
model = ReadBy
fields = ('id', 'created', 'owner', 'book')
Book is a ForeignKey in ReadBy. The reason for this is that I want to see if the book has been read by defining the readby field in BookSerializer to true or false.
Now when I try to POST a ReadBy item I cannot explicitly set the book_id, even if I try do I still get the same error:
"Column 'book_id' cannot be null"
What I need to know is how I can explicitly specify book_id to be able to "read" a book.
Thanks.
class ReadBy(models.Model):
... snip ...
book = models.ForeignKey(Book, null=True, blank=True)
OK I solved this.
I forgot to define the book field in the ReadBySerializer as PrimaryKeyRelatedField:
book = serializers.PrimaryKeyRelatedField(source='book.id')
In fact it seems like I can remove this line altogether since PrimaryKeyRelatedField is default if nothing is specified.

Categories