Как указано в заголовке вопроса, у меня есть задача отфильтровать результаты по полю, не представленному в модели, но рассчитанному сериализатором.
Модель:
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
Вот код просмотра:
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
Сериализатор:
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()
И код пользовательского фильтра:
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', ]
Целью является поле is_favorited, которое возвращает логическое значение. Я пытался написать func в пользовательском классе фильтра, который возвращает набор запросов, но не работал, ни одна документация не помогла мне с примерами. Надеюсь на вашу помощь.
Мы можем использовать аннотацию набора запросов:
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')
Мое плохое, любимое — это related_name в вашем рецепте избранного. Так что измените его на favorite_recipe
. Нравится: models.Count("favorite_recipe", filter=models.Q(favorite_recipe__user_id=user_id))
модель Favorite
должна иметь отношение к Receipe
. Затем используйте related_name
, который вы определили в Favorite
.
ошибка ушла, но фильтрация тоже не работает, запрос http://localhost:800/api/recipes/?page=1&limit=6&is_favorited=1
да, родственное имя 'favorite_recipe'
RecipeFilter изменить на: is_favorited = BooleanFilter(field_name='is_favorite')
Все еще не работает, по этому запросу нет избранных рецептов. Также заметил, что параметр is_favorite в рецепте имеет значение false, даже если запись в таблице избранного существует.
В RecipeViewSet
должноfilterset_class = RecipeFilter
не filter_class = ...
Обратите внимание, что совместное использование filterset_fields
и filterset_class
не поддерживается. Так что также переместите свой фильтр tags
в RecipeFilter.
Черт, до сих пор не фильтруется, либо поле неверно, у меня есть список рецептов. И все еще ложь о рецептах, которые нравятся
Спасибо за ответ! Попробовал ваше предложение, но получил ошибку
Cannot resolve keyword 'favorite' into field. Choices are: author, author_id, cooking_time, favorite_recipe, id, image, ingredients, name, recipe_amount, recipe_cart, tags, text
. В вопрос добавлен код модели