Django Rest How to show all related foreignkey object? - python

I have an blog website and my visitors can also comment on my blog posts. Each blog post have multiple comment and I want to show those comment under my each single blog post. Assume Blog1 have 10 comment so all 10 comment will be show under Blog1
here is my code:
models.py
class Blog(models.Model):
blog_title = models.CharField(max_length=200, unique=True)
class Comment(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(max_length=100)
comment = models.TextField()
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
Serializer.py
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = Blog
exclude = ("author", "blog_is_published")
lookup_field = 'blog_slug'
extra_kwargs = {
'url': {'lookup_field': 'blog_slug'}
}
views.py:
class BlogViewSet(viewsets.ModelViewSet):
queryset = Blog.objects.all().order_by('-id')
serializer_class = BlogSerializer
pagination_class = BlogPagination
lookup_field = 'blog_slug'

You can access comments list from blog object using comment_set attribute, so add comment_set field to your serializer:
class BlogSerializer(serializers.ModelSerializer):
comment_set = CommentSerializer(many=True)
class Meta:
model = Blog
exclude = ("author", "blog_is_published")
lookup_field = 'blog_slug'
extra_kwargs = {
'url': {'lookup_field': 'blog_slug'}
}

Related

Django / DRF - Get field of a related model

everyone.
What I want is get field of a related model by serializer.
I have 2 models:
class Question(models.Model):
question_text = models.CharField(max_length=200)
def __str__(self):
return self.question_text
class Test(models.Model):
test_name = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
question = models.ManyToManyField(Question, related_name='tests')
def __str__(self):
return self.test_name
Why I've used ManyToManyField?
Because I've followed here:
https://medium.com/django-rest/lets-build-a-basic-product-review-backend-with-drf-part-1-652dd9b95485
Now I can do smth like:
But I want get question_text in response.
What I tried:
class TestSerializer(serializers.HyperlinkedModelSerializer):
question_text = serializers.CharField(read_only=True, source="question.question_text")
class Meta:
model = Test
fields = ['pk', 'test_name', 'pub_date', 'question_text']
expandable_fields = {
'question': (QuestionSerializer, {'many': True})
}
But it returned:
I understand, that problem might be in DB relations, but I can't get it.
Thanks in advance!
Use nested serializer:
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = ['question_text']
class TestSerializer(serializers.ModelSerializer):
question = QuestionSerializer(many=True)
class Meta:
model = Test
fields = ['pk', 'test_name', 'pub_date', 'question']
There are two ways to do this I don't know which one is better but this is them
1- create a separate Serializer for question and assgin related_name in models.py to him
class QuestionSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Test
fields = "__all__"
class TestSerializer(serializers.HyperlinkedModelSerializer):
tests = QuestionSerializer(many=True) ## related_name
class Meta:
model = Test
fields = ['pk', 'test_name', 'pub_date', 'tests']
2 - using serializers.SerializerMethodField()
class TestSerializer(serializers.HyperlinkedModelSerializer):
question = serializers.SerializerMethodField()
class Meta:
model = Test
fields = ['pk', 'test_name', 'pub_date', 'question']
def get_question(self, obj):
question = obj.tests.all()
serilaizer = QuestionSerializer(question, many=True)
return serilaizer.data

how to make a comment post api in django related to the single blogpost detail?

I have a page where a blog post detail is displayed. Under the post, there is a section where user can comment after inputing thier name subject and text in a comment box. Now i have to make an api for this. I want to make a post api such that that comment is stored/associated to that particular blogpost detail. That means i need blogpost id to pass while posting comment. How to do that??
class BlogPost(models.Model):
CATEGORY_CHOICES = (
('travel_news', 'Travel News',),
('travel_tips', 'Travel Tips',),
('things_to_do', 'Things to Do',),
('places_to_go', 'Places to Go'),
)
image = models.ImageField(blank=True, null=True)
categories = models.CharField(max_length=64, choices=CATEGORY_CHOICES, default='travel_news')
description = models.CharField(max_length=255)
content = RichTextUploadingField()
# todo support for tags
tags = models.CharField(max_length=255, default='#travel') #todo
date_created = models.DateField()
#property
def html_stripped(self):
from django.utils.html import strip_tags
return strip_tags(self.content)
#property
def comments(self):
return self.comments_set.all()
Here are my serializers:
class CommentPostSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
# fields = '__all__'
fields = ['name', 'email', 'subject', 'comment',]
class BlogPostSerializer(serializers.ModelSerializer):
comments = CommentListSerializer(many=True)
class Meta:
model = BlogPost
fields = ['image', 'categories', 'description', 'content', 'tags', 'date_created', 'comments']
# fields = '__all__'
Here is my view:
class CommentCreateAPIView(CreateAPIView):
queryset = Comment.objects.all()
serializer_class = CommentPostSerializer

Django get a list of parents each populated with their own children

I have a pair of parent/children relation models like:
class Post(models.Model):
title = models.TextField(null=True)
content = models.TextField(null=True)
author = models.TextField(null=True)
created_time = models.DateTimeField(null=True)
class Comment(models.Model):
content = models.TextField(null=True)
created_time = models.DateTimeField(null=True)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
and the serializers are like:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
and finally views:
class PostView(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
class CommentView(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
Now I want to created an API that returns a list of Posts, in which each Post will contain two additional fields, one be all_comments, and the other will be latest_comment. I understand this could be easily done in SQL using JOINs. I am new to Django. I wonder if there's any easy way to do it in Django. Thanks.
Hope this config works for you :)
class CommentPostSerializer(serializers.ModelSerializer): # New Serializer class
class Meta:
model = Comment
exclude = ('post',)
class PostSerializer(serializers.ModelSerializer):
all_comments = CommentPostSerializer(read_only=True, many=True, source='comment_set')
latest_comment = serializers.SerializerMethodField()
def get_latest_comment(self, post):
latest_comment = post.comment_set.last()
return CommentPostSerializer(latest_comment).data
class Meta:
model = Post
fields = '__all__'

ModelViewSet: "this field is required" when POST, field is FK

This is my current code
Models:
class Author(models.Model):
a_author= models.CharField( primary_key=True, unique=True, db_column='author_id')
a_name = models.CharField(db_column='author_name')
class Book(models.Model):
b_book = models.CharField( primary_key=True, unique=True, db_column='book_id')
b_name = models.CharField(db_column='book_name')
b_author = models.ForeignKey(
Author,
on_delete=models.CASCADE,
db_column='book_author_name')
Serializers
class AuthorSerializer(serializers.ModelSerializer):
author = serializers.CharField(source='a_author')
name = serializers.CharField(source='a_name')
class Meta:
fields = ('author', 'name')
class BookSerializer(serializers.ModelSerializer):
book = serializers.CharField(source='b_book')
name = serializers.CharField(source='b_name')
author = serializers.CharField(source='b_author')
class Meta:
fields = ('book', 'name', 'author')
def create(self, validated_data):
author_id = validated_data.pop('author')
author = models.Author.objects.filter(a_author=author_id).first()
validated_data['b_author'] = author
return models.Book.objects.create(**validated_data)
Views
class BookViewSet(viewsets.ModelViewSet):
serializer_class = BookSerializer
def get_queryset(self):
queryset = models.Book.objects
author = self.kwargs.get('author')
queryset = queryset.filter(b_author=author)
return queryset
Url
urlpatterns = [
path('library/<str:author_id>/', BookViewSet.as_view({'get': 'list', 'post':'create'}))
]
Currently if I post to /library/123abc/ with params: { 'name': 'test', 'author': '123abc' }, it will work - a Book record with name=test, author=123abc will be created.
But now I want to take author out of the params (since url already has athor id so I don't want to duplicate it again in params) and send just {'name': 'test'}, it will return 400 error with message {'author': 'this field is required'}. I have tried author = serializers.CharField(source='b_author', required=False) but it didn't work.
Is there any way to get around this? I wonder if there is any way to include additional value in django before params value are validated...
Since you want the author to be shown but not written, you need to set that field as read_only:
class BookSerializer(serializers.ModelSerializer):
book = serializers.CharField(source='b_book')
name = serializers.CharField(source='b_name')
author = serializers.CharField(source='b_author', read_only=True)
Next, you'll want the author to be saved when creating a Book.
For that, you'll have to explicitly provide it to the serializer:
class BookViewSet(viewsets.ModelViewSet):
serializer_class = BookSerializer
def get_queryset(self):
queryset = models.Book.objects
author = self.kwargs.get('author')
queryset = queryset.filter(b_author=author)
return queryset
def perform_create(self, serializer):
author = get_object_or_404(Author, pk=self.kwargs.get('author'))
serializer.save(author= author)

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