How can I serialize custom many-to-many model django? - python

I have 3 tables on data base: News, CustomUser(not important) and Comment:
class News(models.Model):
date = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=200)
text = models.TextField()
image = models.ImageField(upload_to='news_images/', blank=True)
author = models.ForeignKey(CustomUser, on_delete=models.SET_DEFAULT, blank=True, default=1)
tags = models.ManyToManyField(Tags, blank=True)
published = models.BooleanField(default=False)
comments = models.ManyToManyField(
CustomUser,
related_name='author',
through='Comments.Comment',
blank=True,
)
class Comment(models.Model):
date = models.DateTimeField(auto_now_add=True)
text = models.TextField()
author = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True)
event = models.ForeignKey(News, on_delete=models.CASCADE, parent_link=True)
As you can see, the comment is a custom many-to-many model, that links Users and News, and adds some extra data (text of comment itself).
I using DRF, so I need to serialize it. I need news serializer to include list of comments with all their fields(like author fields as you can see below on the screenshot).But when i getting one of the news I see that:
{"id":2,"author":{"username":"Admin","email":"alexsiid29#gmail.com","first_name":"","last_name":""},"tags":[],"comments":[1],"date":"15.08.2021 10:10","title":"Тест2","text":"тестовая 2","image":"http://127.0.0.1:8000/news_images/%D0%91%D0%B5%D0%B7%D1%8B%D0%BC%D1%8F%D0%BD%D0%BD%D1%8B%D0%B9.png","published":true}
Comments: "comments":[1]
And when I printing during the view-function execution:
self.object = News.objects.filter(pk=pk).first()
print(self.object.comments.all())
I getting this: <QuerySet [<CustomUser: Admin>]>
So, instead of a normal representation of comments, I get the authors ID.
There are my serializers:
class CommentSerializer(serializers.ModelSerializer):
author = CustomUserSerializer(source='author.id')
event = NewsSerializer(source='event.id')
class NewsSerializer(serializers.ModelSerializer):
author = CustomUserSerializer(read_only=True)
tags = TagSerializer(required=False, allow_null=True, many=True)
comments = serializers.PrimaryKeyRelatedField(allow_null=True, many=True, read_only=True)
So, how can I serialize these tables, or maybe organize them in a different way?
P.S.
I also tried StringRelatedField instead of PrimaryRelatedField, but it returns author's name.

You have typo in your code: comments in News model must be linked with Comment model, not with CustomUser. That's why you get <QuerySet [<CustomUser: Admin>]>.
To solve "I need news serializer to include list of comments with all their fields", I'd do the following:
class CommentSerializer(serializers.ModelSerializer):
author = CustomUserSerializer()
class Meta:
model = Comment
exclude = ('event',) # in your particular case
class NewsSerializer(serializers.ModelSerializer):
author = CustomUserSerializer(read_only=True)
tags = TagSerializer(required=False, allow_null=True, many=True)
comments = CommentSerializer(many=True)
class Meta:
model = News
fields = "__all__"
I advise you to exclude event in CommentSerializer because you'll have this info in your NewsSerializer so it won't be repeated for each comment.

Related

Unable to get related data from ManyToManyField

I'm trying to fetch related objects from below two models.
Following django models with ManyToManyField relationship.
Book
class Book(models.Model):
authors = models.ManyToManyField(
to=Author, verbose_name="Authors", related_name="books_author"
)
bookshelves = models.ManyToManyField(
to=Bookshelf, verbose_name="Bookshelf", related_name="books_shelves"
)
copyright = models.NullBooleanField()
download_count = models.PositiveIntegerField(blank=True, null=True)
book_id = models.PositiveIntegerField(unique=True, null=True)
languages = models.ManyToManyField(
to=Language, verbose_name=_("Languages"), related_name="books_languages"
)
Author
class Author(models.Model):
birth_year = models.SmallIntegerField(blank=True, null=True)
death_year = models.SmallIntegerField(blank=True, null=True)
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Meta:
verbose_name = _("Author")
verbose_name_plural = _("Author")
I have to fetch all the Auhtors with their related books. I have tried a lot of different ways none is working for me.
First way : using prefetch_related
class AuthorListAPIView(APIErrorsMixin, generics.ListAPIView):
serializer_class = AuthorSerializer
queryset = Author.objects.exclude(name__isnull=True)
def get_queryset(self):
auths = queryset.prefetch_related(Prefetch("books_author"))
Second way using related_name 'books_auhtor'
class AuthorListAPIView(APIErrorsMixin, generics.ListAPIView):
serializer_class = AuthorSerializer
queryset = Author.objects.exclude(name__isnull=True)
def get_queryset(self):
auths = queryset.books_author.all()
None of the above ways worked for me. I want to prepare a list of Authors and their associated books.
For ex:-
[{'Author1':['Book1','Book2'],... }]
Prefetching is not necessary, but can be used to boost efficiency, you can work with:
class AuthorListAPIView(APIErrorsMixin, generics.ListAPIView):
serializer_class = AuthorWithBooksSerializer
queryset = Author.objects.exclude(name=None).prefetch_related('books_author')
In the AuthorWithBooksSerializer, you can then add the data of the books, for example:
from rest_framework import serializers
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('book_id', 'copyright')
class AuthorWithBooksSerializer(serializers.ModelSerializer):
books = BookSerializer(source='books_author', many=True)
class Meta:
model = Author
fields = ('name', 'books')
Here the books will use the BookSerializer and thus encode a list of dictionaries.
While you can use the name of the author as object key, I strongly advise against this: it makes the object less accessible since the keys are no longer fixed and if these contain spaces, it can also result in more trouble obtaining the value(s) associated with a given attribute name.

Django Rest Framework Nested Comments

i am trying to create a backend for blog website. I created a comment model and i have associated with blog post it. When I list the comments, I can also list the answers that have been made to the comment. But there is a problem that even though it appears in the comment as an answer, it also looks like it was the main comment again. How can I solve this. I would appreciate it if someone could help. I may have mistakes in English, I hope I was able to tell you what I wanted to tell you. Thanks in advance.
https://i.stack.imgur.com/HcEoT.png
Comment Model:
class Comments(models.Model):
class Meta:
verbose_name = 'Yorum'
verbose_name_plural = 'Yorumlar'
user = models.ForeignKey(User, verbose_name='Yorum sahibi', on_delete=models.CASCADE)
post = models.ForeignKey(Post, verbose_name='Yorum yapılacak Post', on_delete=models.CASCADE,related_name='comments')
parent_id = models.ForeignKey('self', verbose_name='Parent Yorum', related_name='subcomments',on_delete=models.CASCADE, null=True, blank=True)
comment = models.TextField(verbose_name='Yorum')
[enter image description here][1]created_at = models.DateTimeField(auto_now_add=True, blank=True)
updated_at = models.DateTimeField(auto_now=True, blank=True)
def __str__(self):
return self.user.first_name + " " + self.user.last_name + " | " + self.post.title
Comment Serializer:
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comments
fields = '__all__'
def get_fields(self):
fields = super(CommentSerializer, self).get_fields()
fields['subcomments'] = CommentSerializer(many=True, read_only=True)
return fields
Post Serializer
class PostSerializer(serializers.ModelSerializer):
tag = serializers.StringRelatedField(many=True, read_only=True)
comments = CommentSerializer(many=True, read_only=True)
author = AuthorSerializer(read_only=True)
category = CategorySerializer2(read_only=True)
class Meta:
model = Post
fields = '__all__'
First Option
You can use a nested serializer to solve your problem
class CommentSerializer(serializers.ModelSerializer):
parent_id = serializers.PrimaryKeyRelatedField()
class Meta:
model = Comment
fields = '__all__'
def get_related_field(self, model_field):
return CommentSerializer()
Second Option
You can use a library called djangorestframework-recursive for your use case
First, install the library
pip install djangorestframework-recursive
In your serializer file
from rest_framework import serializers
from rest_framework_recursive.fields import RecursiveField
class CommentSerializer(serializers.ModelSerializer):
parent_id = serializers.ListField(child=RecursiveField())
class Meta:
model = Comments
fields = '__all__'

count likes in ManyToManyField - django rest framework

in models.py:
class Post(models.Model):
body = models.TextField(max_length=10000)
date = models.DateTimeField(auto_now_add=True, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
liked_by = models.ManyToManyField(User, blank=True, related_name='liked_by')
class Meta:
ordering = ['-date']
in serializers.py:
class PostSerializer(serializers.ModelSerializer):
user = UserSerializers()
class Meta:
model = Post
fields = ('body','date','user')
how to count likes of a single post? and also show which user liked the post.
class PostSerializer(serializers.ModelSerializer):
user = UserSerializers()
total_likes = serilaizers.SerializerMethodField()
liked_by = UserSerializers(many=True)
class Meta:
model = Post
fields = ('body','date','user', 'total_likes', 'liked_by')
def get_total_likes(self, instance):
return instance.liked_by.count()
You need to change class Post manyToMany relation adding "through=".
liked_by = models.ManyToManyField(User, through='liked_users', blank=True, related_name='liked_by')
Create manually model LikedUsers:
class LikedUsers(models.Model):
liked_count = models.IntegerField(default=0)
post = models.ForeignKey(Post, related_name='likedusers', on_delete=models.CASCADE)
user = models.ForeignKey(User, related_name='likedusers', on_delete=models.CASCADE)
Get access to likes (post.likedusers[0].liked_count, ... next users)
You can get the total likes of a post using SerializerMethodField.
from rest_framework.fields import SerializerMethodField
class PostSerializer(serializers.ModelSerializer):
user = UserSerializers()
total_likes = SerializerMethodField()
class Meta:
model = Post
fields = ('body','date','user')
def get_total_likes(self, instance):
return instance.liked_by.all().count()
Your liked_by field is just referencing to User model, but not to User model and you have only who liked your post but likes quantity. So you can query certain post and count User quantity, but I suggest to have separate filed of likes count

What is best way to fill out a related table after processing the parent field , DRF

I have a class like bellow:
class News(models.Model):
description = models.TextField(verbose_name="Description", max_length=50, blank=True, null=True, default="")
timestamp = models.DateTimeField(auto_now_add=True)
it has a text field.Now I am supposed to list the entities that are in this text, so I defined a related table as bellow:
class Entity(models.Model):
news = models.ForeignKey(
News,
related_name='entities4thisnews',
on_delete=models.CASCADE)
entity = models.TextField(verbose_name="Entity", max_length=100, blank=True, null=True, default="")
timestamp = models.DateTimeField(auto_now_add=True)
As an example I have this text in first table
news1ID, " France is in Europe".
when user posts it second table should be automatically filled with
entityID1,news1ID, France
entityID2,news1ID, Europe
when the user posts a news, my question is that where is the best place to process the text(finding entity in this case), and post the processed results accordingly to the next table?
An issue right of the bat is that, newsID1 is not determined before any post for the news.
here are also my views.
class NewsList(generics.ListCreateAPIView):
queryset = News.objects.all()
serializer_class = NewsSerializer
name = 'news-list'
def perform_create(self, serializer):
pass
class EntityList(generics.ListCreateAPIView):
queryset = Entity.objects.all()
serializer_class = EntitySerializer
name = 'entity-list'
Thanks for your help!

Django Rest Framework - Create foreign key object on POST

I have a simple DRF REST API that I want to use to create blog articles. I want to be able to add tags to those blog articles so users can search tags and see related articles. However, the tags may not exist yet. I have created an Article Model with a ForeignKey field to a Tag Model like this:
class Tag(models.Model):
name = models.CharField(max_length=32)
def _str__(self):
return self.name
class Meta:
ordering = ('name',)
class Article(models.Model):
title = models.CharField(max_length=256)
author = models.ForeignKey(User, on_delete=models.CASCADE)
content = models.TextField()
date = models.DateTimeField(auto_now_add=True)
tags = models.ForeignKey(Tag, on_delete=models.CASCADE, blank=True, default=None)
def __str__(self):
return self.title
class Meta:
ordering = ('date', 'id')
Ideally what I want is to be able to POST a new Article with a set of tags, and if any of the tags don't exist, create them in the DB. However, as it is currently, the tags need to already exist to be added to the Article. Visually, DRF shows this as a dropdown that is populated with pre-existing tags:
How can I add or create multiple Tags from my Article API endpoint?
EDIT: As requested, I've added my views.py
views.py:
from api.blog.serializers import ArticleSerializer, TagSerializer
from rest_framework import viewsets
# /api/blog/articles
class ArticleView(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
# /api/blog/tags
class TagView(viewsets.ModelViewSet):
queryset = Tag.objects.all()
serializer_class = TagSerializer
For completeness, here are my serializers from my REST API's serializers.py.
serializers.py:
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
urls.py:
from rest_framework import routers
router = routers.DefaultRouter()
router.register('articles', views.ArticleView)
router.register('tags', views.TagView)
urlpatterns = [
path('', include(router.urls)),
]
Overriding the create() method of the serializer as
class ArticleSerializer(serializers.ModelSerializer):
tags = serializers.CharField()
class Meta:
model = Article
fields = '__all__'
def create(self, validated_data):
tag = validated_data.pop('tags')
tag_instance, created = Tag.objects.get_or_create(name=tag)
article_instance = Article.objects.create(**validated_data, tags=tag_instance)
return article_instance
Okay, thanks to #JPG for their help. This is what I've ended up with. It allows users to add space delimited tags into a CharField on the /api/blog/article endpoint. When a POST request is performed, the tags are split on spaces, get_or_create()d (for this to work I needed to make Tag.name the primary key), and then added to the Article with article.tags.set(tag_list). As #JPG and #Martins suggested, a ManyToManyField() was the best way to do this.
Here is my full code:
serializers.py:
class ArticleSerializer(serializers.ModelSerializer):
class TagsField(serializers.CharField):
def to_representation(self, tags):
tags = tags.all()
return "".join([(tag.name + " ") for tag in tags]).rstrip(' ')
tags = TagsField()
class Meta:
model = Article
fields = '__all__'
def create(self, validated_data):
tags = validated_data.pop('tags') # Removes the 'tags' entry
tag_list = []
for tag in tags.split(' '):
tag_instance, created = Tag.objects.get_or_create(name=tag)
tag_list += [tag_instance]
article = Article.objects.create(**validated_data)
print(tag_list)
article.tags.set(tag_list)
article.save()
return article
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
Note that I had to create a custom TagField() and override to_representation(). This is because if I used a regular serializer.CharField() tags were displayed as: "Blog.tag.None" instead of the tag values, like this:
models.py:
class Tag(models.Model):
name = models.CharField(max_length=32, primary_key=True)
def __str__(self):
return self.name
class Meta:
ordering = ('name',)
class Article(models.Model):
title = models.CharField(max_length=256)
author = models.ForeignKey(User, on_delete=models.CASCADE)
content = models.TextField()
date = models.DateTimeField(auto_now_add=True)
tags = models.ManyToManyField(Tag)
def __str__(self):
return self.title
class Meta:
ordering = ('date', 'id')

Categories

Resources