As declared in question title, i got task to filter results by field not presented in model but calculated by serializer.
The model:
class Recipe(models.Model):
tags = models.ManyToManyField(
Tag,
related_name='recipe_tags'
)
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='author_recipes'
)
ingredients = models.ManyToManyField(
Ingredient,
related_name='recipe_ingredients'
)
name = models.CharField(max_length=200)
image = models.ImageField()
text = models.TextField()
cooking_time = models.PositiveSmallIntegerField(
validators=[MinValueValidator(1)]
)
class Meta:
ordering = ("-id",)
verbose_name = "Recipe"
verbose_name_plural = "Recipes"
def __str__(self):
return self.name
Here is the view code:
class RecipeViewSet(ModelViewSet):
queryset = Recipe.objects.all()
permission_classes = [IsAdminOrAuthorOrReadOnly, ]
serializer_class = RecipeInSerializer
pagination_class = LimitPageNumberPagination
filter_backends = [DjangoFilterBackend]
filterset_fields = ['tags', ]
filter_class = RecipeFilter
Serializer:
class RecipeOutSerializer(serializers.ModelSerializer):
tags = ManyRelatedField(child_relation=TagSerializer())
author = CustomUserSerializer()
ingredients = serializers.SerializerMethodField()
is_favorite = serializers.SerializerMethodField()
is_in_shopping_cart = serializers.SerializerMethodField()
class Meta:
fields = '__all__'
model = Recipe
def get_ingredients(self, obj):
ingredients = IngredientAmount.objects.filter(recipe=obj)
return GetIngredientSerializer(ingredients, many=True).data
def get_is_favorite(self, obj):
request = self.context.get("request")
if request.user.is_anonymous:
return False
return Favorite.objects.filter(recipe=obj, user=request.user).exists()
def get_is_in_shopping_cart(self, obj):
request = self.context.get("request")
if not request or request.user.is_anonymous:
return False
return ShoppingCart.objects.filter(recipe=obj, user=request.user).exists()
And custom filter code:
class RecipeFilter(rest_framework.FilterSet):
tags = ModelMultipleChoiceFilter(
field_name='tags__slug',
to_field_name="slug",
queryset=Tag.objects.all()
)
favorite = BooleanFilter(field_name='is_favorite', method='filter_favorite')
def filter_favorite(self, queryset, name, value):
return queryset.filter(is_favorite__exact=True)
class Meta:
model = Recipe
fields = ['tags', ]
Target is is_favorited field that return boolean value. I tried writing func in custom filter class that return queryset but didnt work, neither documentation helped me with examples. Hope for your help.
We can use queryset annotate:
from django.db import models
from rest_framework import serializers
class RecipeViewSet(ModelViewSet):
def get_queryset(self):
user = self.request.user
user_id = user.id if not user.is_anonymous else None
return Recipe.objects.all().annotate(
total_favorite=models.Count(
"favorite",
filter=models.Q(favorite__user_id=user_id)
),
is_favorite=models.Case(
models.When(total_favorite__gte=1, then=True),
default=False,
output_field=BooleanField()
)
)
class RecipeOutSerializer(serializers.ModelSerializer)
is_favorite = serializers.BooleanField(read_only=True)
class Meta:
model = Recipe
fields = (
# ...
is_favorite,
)
class RecipeFilter(rest_framework.FilterSet):
favorite = BooleanFilter(field_name='is_favorite')