Reputation: 317
I have the following 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()
my nested serializer
looks like this:
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'
)
my views.py
looks like this:
class QuestionAnswerViewSet(BaseCertViewSet):
queryset = Question.objects.all()
serializer_class = serializers.NestedQuestionAnswerSerializer
filter_backends = [filters.DjangoFilterBackend]
filterset_fields = ('category',)
my urls.py
looks like this:
router.register('question-answer', views.QuestionAnswerViewSet, 'question-answer')
What I would like to be able to do is to filter by both Category AND Exam(which is a child attribute). So something like this: https://example.com/api/question-answer?category=4&exam=21
This potentially should return all the Questions that are part of category=4 AND appeared on exam=21.
I have no problem filtering by category
alone, but can't seem to filter on exam
which is a child foreign key.
I've tried many solutions on SO but none seem to do the above.
UPDATE:
Thanks everyone for your suggested solutions.
I ended up using this solution
Added a List Serializer class and modified the to_representation
function:
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)
and then in my Answer serializer I call it:
class AnswerSerializer(serializers.ModelSerializer):
related_name = 'answer'
class Meta:
model = Value
list_serializer_class = FilteredAnswerSerializer
fields = ('id', 'value', 'question', 'exam')
Upvotes: 1
Views: 2844
Reputation: 15128
One way to do it would be to create a custom FilterSet
for the ViewSet
using django-filter.
That is the more readable, and preferred way of doing it, since the code will be clearer and easier to alter in the future.
A very simple, less extendable way of achieving this would be to override the get_queryset
method of the ViewSet
class.
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
Edit: Added filtered_answers
to get_queryset
and serializer class based on
updated understanding of question from comments. Mostly adapted from this answer
here.
Upvotes: 2
Reputation: 317
Thanks everyone for your suggested solutions.
I ended up using this solution
Added a List Serializer class and modified the to_representation
function:
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)
and then in my Answer serializer I call it:
class AnswerSerializer(serializers.ModelSerializer):
related_name = 'answer'
class Meta:
model = Value
list_serializer_class = FilteredAnswerSerializer
fields = ('id', 'value', 'question', 'exam')
I will update my question with the solution.
Upvotes: 0
Reputation: 3156
you can create the filter class, in that filter class you can write down the custom fields and their query.
from django_filters import rest_framework as filters
class QuestionFilter(filters.FilterSet):
exam = filters.IntegerField(method="filter_exam")
class Meta:
fields = ('category', 'exam')
def filter_exam(self, queryset, name, value):
return queryset.filter(answer__exam_id=value)
in view
class QuestionAnswerViewSet(BaseCertViewSet):
queryset = Question.objects.all()
serializer_class = serializers.NestedQuestionAnswerSerializer
filter_backends = [filters.DjangoFilterBackend]
filter_class = QuestionFilter
Upvotes: 0