Search code examples
djangodjango-modelsdjango-rest-frameworkdjango-viewsdjango-serializer

Django Modelviewset Filtering


I have two models Category & Post. In Post model there is foreign key of category. Based on category I want to filter the data to show the post category wise. Here's my code.

models.py

class Category(models.Model):
    name = models.CharField(max_length=200)
    slug = models.SlugField()
    parent = models.ForeignKey('self',blank=True, null=True ,related_name='news', on_delete=models.CASCADE)

    class Meta:
        unique_together = ('slug', 'parent',)    
        verbose_name_plural = "Category"     

    def __str__(self):                           
        full_path = [self.name]                  
        k = self.parent
        while k is not None:
            full_path.append(k.name)
            k = k.parent
        return ' -> '.join(full_path[::-1])

class Post(models.Model):
    NEWS_TYPE = (('Images','Images'),('Multi-Images','Multi-Images'),('Image-Text','Image-Text'),
                 ('Audio-Video','Audio-Video'),('Audio-Video-Text','Audio-Video-Text'),('Audio','Audio'),
                 ('Audio-Text','Audio-Text'))
    POST_STATUS = (('Pending','Pending'),('Verified','Verified'),('Un-Verified','Un-Verified'),
              ('Published','Published'),('Mint','Mint'))
    category = models.ForeignKey(Category, related_name='posts', on_delete=models.CASCADE)
    post_type = models.CharField(max_length=100, verbose_name='Post Type', choices=NEWS_TYPE)
    title = models.TextField(verbose_name='News Title')
    content = models.TextField(verbose_name='News Content')
    hash_tags = models.CharField(max_length=255, verbose_name='Hash Tags')
    source = models.CharField(max_length=255, verbose_name='News Source')
    author = models.ForeignKey(User, related_name='Post', on_delete=models.CASCADE)
    views = models.ManyToManyField(User,related_name='Views', blank=True)
    likes = models.ManyToManyField(User, related_name='Likes', blank=True)
    dislikes = models.ManyToManyField(User, related_name='Dislikes', blank=True)
    status = models.CharField(max_length=20, verbose_name='Status', choices=POST_STATUS, default='Pending')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return (self.post_type)+ '-' +self.title

serializers.py

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = '__all__'

class PostSerializer(serializers.ModelSerializer):
    category = CategorySerializer(many=True, read_only=True)
    class Meta:
        model = Post
        fields = ('category','post_type','title','content','hash_tags','source','author','views',
                  'likes','dislikes','status')

views.py

class CategoryAPI(viewsets.ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer

class PostAPI(viewsets.ModelViewSet):
    serializer_class = PostSerializer

    def get_queryset(self):
        news_post = Post.objects.all()
        return news_post

    def retrieve(self, request, *args, **kwargs):
        params = kwargs
        print(params['pk'])
        category = Category.objects.filter(name=params['pk'])
        serializer = CategorySerializer(category, many=True)
        return Response(serializer.data)

urls.py

from django.urls import path, include
from rest_framework import routers
from rest_framework.routers import DefaultRouter
from news.views import PostAPI, CategoryAPI
from . import views

router = DefaultRouter()
router.register('posts', views.PostAPI, basename='posts'),
router.register('category', views.CategoryAPI, basename='category'),


urlpatterns = router.urls

I tried solving in these way but it tells 'PostSerializer' object has no attribute 'get_category'. Is there anything i'm doing wrong. Please your support would be helpful. Thank you


Solution

  • I think then your approach should be the other way round, meaning you should add the list of Posts to your Category:

    serializers.py

    class PostSerializer(serializers.ModelSerializer):
        class Meta:
            model = Post
            fields = ('category','post_type','title','content','hash_tags','source','author','views',
                      'likes','dislikes','status')
    
    class CategorySerializer(serializers.ModelSerializer):
        posts = PostSerializer(many=True, read_only=True)
    
        class Meta:
            model = Category
            fields = ['name', 'slug', 'parent', 'posts']
    

    Attention: I changed the related name of your category field in the Post model to 'posts'

    This should show you all Posts when retrieving a category. No need to override any method in your views:

    class CategoryAPI(viewsets.ModelViewSet):
        queryset = Category.objects.all()
        serializer_class = CategorySerializer
    
    class PostAPI(viewsets.ModelViewSet):
        queryset = Post.obejcts.all()
        serializer_class = PostSerializer
    

    If do not want identify the category by id but by category name, e.g.:

    http://127.0.0.1:8000/news/category/sports/

    add a custom lookup field to your category view, e.g.

    class CategoryAPI(viewsets.ModelViewSet):
        queryset = Category.objects.all()
        serializer_class = CategorySerializer
        lookup_field = 'name'
    

    but make sure the lookup_field is unique