首页 > 解决方案 > 过滤嵌套序列化程序 - Django REST

问题描述

我有以下内容models.py

class Question(models.Model):

    code = models.CharField(max_length=12)
    text = models.CharField(max_length=1000, null=True)
    catgeroy = models.ForeignKey(
        Category, on_delete=models.PROTECT, null=True, blank=True, db_index=True, related_name='category')


class Answer(models.Model):

    question = models.ForeignKey(
        Question, on_delete=models.PROTECT, null=True, blank=True, db_index=True, related_name='question')
    exam = models.ForeignKey(Exam, on_delete=models.CASCADE)
    value = models.FloatField(null=True, blank=True)


class Exam(models.Model):

    year = models.IntegerField()

我的嵌套serializer看起来像这样:


class AnswerSerializer(serializers.ModelSerializer):

    related_name = 'answer'

    class Meta:
        model = Value
        fields = ('id', 'value', 'question', 'exam')


class NestedQuestionAnswerSerializer(serializers.ModelSerializer):

    answer = AnswerSerializer(many=True, read_only=True)

    class Meta:
        model = Question
        fields = (
            'id','code', 'text', 'answer'
        )

我的views.py样子是这样的:

class QuestionAnswerViewSet(BaseCertViewSet):
    queryset = Question.objects.all()
    serializer_class = serializers.NestedQuestionAnswerSerializer
    filter_backends = [filters.DjangoFilterBackend]
    filterset_fields = ('category',)

我的urls.py样子是这样的:

router.register('question-answer', views.QuestionAnswerViewSet, 'question-answer')

我想做的是同时按类别和考试(这是一个子属性)进行过滤。所以是这样的:https://example.com/api/question-answer?category=4&exam=21

这可能会返回属于 category=4 并且出现在考试=21 中的所有问题。

我单独过滤没有问题category,但似乎无法过滤exam哪个是子外键。

我在 SO 上尝试了很多解决方案,但似乎没有一个能做到以上。

更新:

感谢大家提出的解决方案。

我最终使用了这个解决方案

添加了 List Serializer 类并修改了to_representation函数:

class FilteredAnswerSerializer(serializers.ListSerializer):
    def to_representation(self, data):
        qry_exam = self.context['request'].GET.get('exam')
        data = data.filter(exam=qry_exam)
        return super(FilteredAnswerSerializer,  self).to_representation(data)

然后在我的 Answer 序列化程序中我称之为:


class AnswerSerializer(serializers.ModelSerializer):

    related_name = 'answer'

    class Meta:
        model = Value

        list_serializer_class = FilteredAnswerSerializer

        fields = ('id', 'value', 'question', 'exam')

标签: pythondjangoserializationdjango-rest-frameworknested

解决方案


一种方法是using django-filter创建一个自定义FilterSetViewSet

这是更易读、更受欢迎的方式,因为将来代码会更清晰、更容易修改。

实现此目的的一种非常简单、可扩展性较差的方法是覆盖类的get_queryset方法ViewSet

from django.db.models import Prefetch

class NestedQuestionAnswerSerializer(serializers.ModelSerializer):
    answer = AnswerSerializer(source="filtered_answers", many=True, read_only=True)

    class Meta:
        model = Question
        fields = ('id', 'code', 'text', 'answer')

class QuestionAnswerViewSet(BaseCertViewSet):
    queryset = Question.objects.all()
    serializer_class = serializers.NestedQuestionAnswerSerializer
    filter_backends = [filters.DjangoFilterBackend]
    filterset_fields = ('category',)

    def get_exam_param(self):
        """ A helper to extract the exam id from the query_params. """
        try:
            return int(self.request.query_params["exam"])
        except (KeyError, ValueError, TypeError):
            return None

    def get_queryset(self):
        queryset = super().get_queryset()
        exam = self.get_exam_param()
        if exam is not None:
            queryset = queryset.filter(answer__exam_id=exam).prefetch_related(
                Prefetch(
                    "answers",
                    queryset=Answer.objects.filter(exam_id=exam),
                    to_attr="filtered_answers",
                ),
            )
        else:
            queryset = queryset.prefetch_related(
                Prefetch(
                    "answers",
                    queryset=Answer.objects.all(),
                    to_attr="filtered_answers",
                ),
            )
        return queryset

编辑:根据对评论问题的更新理解添加filtered_answersget_queryset序列化程序类。主要改编自这里的答案


推荐阅读