SoullessSoldier
SoullessSoldier

Reputation: 21

DRF how to get items grouping by categories

I understand that my question is repeated often, but I'm stuck with this. I want to make a simple API with DRF. I have two models: models.py

class Rubrics(models.Model):
    id = models.AutoField(primary_key=True)
    rubric = models.CharField(max_length=255, blank=True, null=True)

class Books(models.Model):
    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=255, blank=True, null=True)
    author = models.CharField(max_length=255, blank=True, null=True)
    date = models.CharField(max_length=255, blank=True, null=True)
    rubrics = models.ForeignKey('Rubrics', on_delete=models.DO_NOTHING, related_name='books', blank=True, null=True)

I'd like to view serialized results like this:

[
    rubric1: [
        {
         title: "title1",
         author:"author1"
        }, 
        book_obj2,
        so on
    ],
    rubric2: [
        book_obj4,
        book_obj5
    ]
]

views.py

class BooksByRubricView(APIView):
    """List of books by rubrics"""

    def get(self, request):
        last_date = Books.objects.latest("date").date
        books_last = Books.objects.filter(date=last_date)
        serializer = RubricsSerializer(books_last, many=True)
        return Response(serializer.data)

I try a lot of examples in this theme:

#sorry this garbage
class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Books
        #fields = ("title",)
        exclude = ()

class RubricsSerializer(serializers.ModelSerializer):
    rubrics = BookSerializer(read_only=True)
    class Meta:
        model = Rubrics
        fields = ("rubrics",)
        #exclude = ()
"""
class RubricSerializer(serializers.ModelSerializer):
    #rubrics = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    #books = RecursiveSerializer(many=True)
    #print(books)
    rubrics = RubricSerializer(read_only=True)
    #books = BooksListSerializer(many=True)
    class Meta:
        model = Books
        fields  = ("title", "rubrics",)
        #fields = ['rubric', 'rubrics']
        #fields = ['books', 'rubrics']
"""

But maybe I don't understand the principles of reverse relationships and serializing in DRF. Please, tell me WAIDW. Thanks.

UPD. I want to operate only on specified sets of data (for example, work with the last uploaded books), not on the whole table Books. So I want to view only rubrics, that are in this set, not all rubrics from table Rubrics. Yes, books have only one rubric id in a specified field, and the rubric has a one-to-many relationship to books. Well, that's JSON I want to see (of course, all characters appearing in this work, are fictitious. Any resemblance to actual persons, living or dead, is purely coincidental.):

{
    "rubric1": [{
        "author": "Marley W. Watkins",
        "title": "A Step-By-Step Guide to Exploratory Factor Analysis with Stata"
    }, {
        "author": "Robert E. Davis",
        "title": "Auditing Information and Cyber Security Governance; A Controls-Based Approach"
    }, {
        "author": "Gerhard X. Ritter, Gonzalo Urcid",
        "title": "Introduction to Lattice Algebra: With Applications in AI, Pattern Recognition, Image Analysis, and Biomimetic Neural Networks"
    }],
    "rubric2": [{
        "author": "Richard Cross and JT Paasch",
        "title": "The Routledge Companion to Medieval Philosophy"
    }, {
        "author": "Nicholas Allott (editor), Terje Lohndal (editor), Georges Rey (editor)",
        "title": "A Companion to Chomsky"
    }, {
        "author": "Olakunle George",
        "title": "A Companion to African Literatures"
    }, {
        "author": "Tim Byrnes, Ebubechukwu O. Ilo-Okeke",
        "title": "Quantum Atom Optics: Theory and Applications to Quantum Technology"
    }],
    "rubric3": [{
        "author": "Hiroyoshi Naito",
        "title": "Organic Semiconductors for Optoelectronics"
    }, {
        "author": "Bassem R. Mahafza, Scott C. Winton",
        "title": "Handbook of Radar Signal Analysis"
    }, {
        "author": "Sean McManus, Mike Cook",
        "title": "Raspberry Pi For Dummies, 4th Edition"
    }]
}

I realize it in plain Django:

class BooksByRubricView(APIView):
    """List of books by rubrics"""

    def get(self, request):
        last_date = Books.objects.using('books').latest("date").date
        books_last = Books.objects.using('books').filter(date=last_date)

        categories = []
        res_dict = {}

        for item in books_last:
            categories.append(item.rubrics_id)

        categories = set(categories)
        
        for item in categories:
            temp_list = []
            temp_qs = books_last.filter(rubrics_id=item)
            for i in temp_qs:
                temp_list.append({"author": i["author"], "title": i["title"]})
            
            res_dict["rubric"+str(item)]=list(temp_list)

        # res = json.dumps(res_dict)
        return JsonResponse(res_dict, safe=False, json_dumps_params={'ensure_ascii': False})

can I realize it with DRF serializers or simplest way is not f*cking any brains and return JSON as above?

Update2:

Well, after some magic with serializers and generic.APIView I've got a result not exactly expected, but very closest to that. Example (of course, all characters appearing in this work, are fictitious. Any resemblance to real persons, living or dead, is purely coincidental)

views.py

class BooksByRubricView2(generics.ListAPIView):
    """Books grouped by rubrics"""
    serializer_class = RubricsSerializer2
    queryset = Rubrics.objects.all()

#OR without generic
class BooksByRubricView3(APIView):

    def get(self, request):
        r = Rubrics.objects.all()
        serializer=RubricsSerializer2(r,many=True)
        return Response(serializer.data)

serializers.py

class FilteredListSerializer(serializers.ListSerializer):
    """Serializer to filter Book table, look for latest date for every rubric"""

    def to_representation(self, data):
        latest_data = data.latest("date").date
        data = data.filter(date=latest_data)
        return super(FilteredListSerializer, self).to_representation(data)


class BookSerializer2(serializers.ModelSerializer):
    class Meta:
        model = Books
        list_serializer_class = FilteredListSerializer
        fields = (
            "title",
            "author",
            "date")


class RubricsSerializer2(serializers.ModelSerializer):
    books = BookSerializer2(many=True, read_only=True)
    class Meta:
        model = Rubrics
        fields = ("rubric", "books",)

result:
[
    {
        "rubric": "Computers",
        "books": [
            {
                "title": "A Step-By-Step Guide to Exploratory Factor Analysis with Stata",
                "author": "Marley W. Watkins",
                "date": "2021-08-08"
            },
            {
                "title": "Auditing Information and Cyber Security Governance; A Controls-Based Approach",
                "author": "Robert E. Davis",
                "date": "2021-08-08"
            }
        ]
    },
    {
        "rubric": "Education",
        "books": [
            {
                "title": "The Routledge Companion to Medieval Philosophy",
                "author": "Richard Cross and JT Paasch",
                "date": "2021-08-08"
            }
        ]
    },
    so on
}

It's a dirty way because every Rubric from table Rubrics creates its own query to table Books and each Rubric has its latest date. But DRF filtering will be the next step.

Upvotes: 2

Views: 1233

Answers (2)

Francisco
Francisco

Reputation: 656

First i've tried to get the result that you wanted using your view and just dit not worked! So I've created another view:

from .serializers import *
from rest_framework import generics
from .models import *


class BooksByRubricView(generics.ListAPIView):
    serializer_class = RubricsSerializer
    queryset = Rubric.objects.all()
    # You should try!

And course I also had to create a path (Just for tests! Ignore it!):

from django.urls import path
from .views import BooksByRubricView

urlpatterns = [
    path('books/', BooksByRubricView.as_view(), name='books')
]

In your models:

from django.db import models


class Rubric(models.Model):
    id = models.AutoField(primary_key=True)  # Do not use plural in the models name!
    rubric_name = models.CharField(max_length=255, blank=True, null=True)

    class Meta:
        ordering = ['id']  # Create a ordering!

    def __str__(self):
        return self.rubric_name


class Book(models.Model):
    rubric = models.ForeignKey(Rubric, related_name='rubrics', on_delete=models.CASCADE)
    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=255, blank=True, null=True)
    author = models.CharField(max_length=255, blank=True, null=True)
    date = models.CharField(max_length=255, blank=True, null=True)

    class Meta:
        ordering = ['id']  # Create a ordering!

In your serializers.py:

from rest_framework import serializers
from .models import *


class BookSerializer(serializers.ModelSerializer):

    class Meta:
        model = Book
        fields = (
            "title",
            "author",
            "date"

        )


class RubricsSerializer(serializers.ModelSerializer):

    rubrics = BookSerializer(many=True, read_only=True)

    class Meta:
        model = Rubric
        fields = (
            "id",  # Put an Id if you want!
            "rubrics",
        )

Maybe the only problem was with your view! So I just did all this to get the result that you want but localy; images of the result: enter image description here

Upvotes: 1

nenadp
nenadp

Reputation: 816

There is no field rubrics in Rubrics model, available fields are:

  • id,rubric from model itself,
  • books_set which represent all books that have FK reference to that Rubrics instance. Take a look at this official docs

Book have one rubric (rubrics foreign key), and rubrics have multiple books (books_set). I would also suggest changing FK name from rubrics to rubric since there can be only one.

Try this and work from there:

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Books
        #fields = ("title",)
        exclude = ()

class RubricsSerializer(serializers.ModelSerializer):
    class Meta:
        model = Rubrics
        fields = ("books_set",)
        #exclude = ()

Upvotes: 1

Related Questions