Имеется django модель: class FavoriteAndShoppingCa...
बनाया गया: 12 फ़रवरी 2025
बनाया गया: 12 फ़रवरी 2025
Имеется django модель:
class FavoriteAndShoppingCart(models.Model):
"""Модель связывает рецепт и пользователя."""
recipe = models.ForeignKey(
Recipe, on_delete=models.CASCADE,
related_name='favorite_recipes', # для доступа к объектам исходной модели
verbose_name='Рецепт')
user = models.ForeignKey(
User, on_delete=models.CASCADE,
related_name='favorite_recipes',
verbose_name='Пользователь'
)
is_in_shopping_cart = models.BooleanField(
default=False,
blank=True,
verbose_name='Рецепт в корзине')
is_favorited = models.BooleanField(
default=False,
blank=True,
verbose_name='Избранные рецепты')
textclass Meta: verbose_name = 'Выбранный рецепт' verbose_name_plural = 'Выбранные рецепты' ordering = ['recipe', 'user'] constraints = [ models.UniqueConstraint( fields=['recipe', 'user'], name='%(app_label)s_%(class)s_unique_relationships' ), models.CheckConstraint( name='%(app_label)s_%(class)s_prevent_self_favorite', check=~models.Q(recipe=models.F('user')) ) ] def __str__(self): return (f'{self.user} выбрал рецепт {self.recipe} как: \ любимый рецепт {self.is_favorited}, \ рецепт для покупок ингридиентов {self.is_in_shopping_cart}')
serializers
class ShoppingCartSerializer(serializers.ModelSerializer):
'''Сериалайзер отображает страницу "Список покупок"
рецептов в списке покупок может: много или один.
исопользуется для добавления/удаления рецептов из списка покупок
после скачивания списка покупок, обнулить ведь его надо? - добавить на вью
'''
user = serializers.SlugRelatedField(
queryset=User.objects.all(),
source='favorite_recipes', many=True,
slug_field='usersfavorites',
default=serializers.CurrentUserDefault() # Текущий пользователь
)
recipe = serializers.SlugRelatedField(
slug_field='favoriterecipes', many=True,
source='favorite_recipes',
queryset=Recipe.objects.all(), # список рецептов текущего пользователя
)
is_in_shopping_cart = serializers.SerializerMethodField()
textclass Meta: model = FavoriteAndShoppingCart fields = ['image', 'name', 'cooking_time', 'is_in_shopping_cart'] def get_is_in_shopping_cart(self, obj): # ---- возможно можно сделать полезнее """Функция отображает, находится ли рецепт в корзине покупок текущего пользователя. Возвращает False если текущий пользователь не авторизован. """ current_user = self.context.get('request').user if current_user.is_anonymous: return False return current_user.favorite_recipes.filter( # проверить выход через рeлатед нейм recipe=obj ).exists()
Нужно написать @action для ViewSet добавления и удаления рецепта в корзину
Ниже приведён один из вариантов реализации добавления и удаления рецептов из корзины покупок через кастомные экшены @action
. Предположим, что вы хотите добавить эти действия в ваш RecipeViewSet
(или любой другой ViewSet, где логически правильно выполнять эти операции).
Основная идея:
При добавлении в корзину:
get_object_or_404
).FavoriteAndShoppingCart
.is_in_shopping_cart = True
.При удалении из корзины:
FavoriteAndShoppingCart
для текущего пользователя и рецепта.is_in_shopping_cart = False
или при желании удаляем запись из таблицы.JSON
.Пример кода (сделаем две раздельные action — на добавление и удаление; если нужно объединить POST/DELETE в один URL, придётся в одном методе обрабатывать два http-метода, но обычно удобнее раздельно):
pythonfrom rest_framework.decorators import action from rest_framework.response import Response from rest_framework import status from django.shortcuts import get_object_or_404 from rest_framework.permissions import IsAuthenticated class RecipeViewSet(viewsets.ModelViewSet): # ваш обычный набор настроек queryset = Recipe.objects.all() serializer_class = RecipeSerializer permission_classes = [IsAuthenticatedOrReadOnly] @action( detail=True, methods=['post'], permission_classes=[IsAuthenticated], url_path='shopping_cart', # /recipes/<pk>/shopping_cart/ ) def add_to_shopping_cart(self, request, pk=None): """Добавляем рецепт в корзину покупок.""" user = request.user recipe = get_object_or_404(Recipe, pk=pk) # получаем или создаём запись в FavoriteAndShoppingCart relation_obj, created = FavoriteAndShoppingCart.objects.get_or_create( user=user, recipe=recipe ) # если рецепт уже "в корзине", вернём 400 if relation_obj.is_in_shopping_cart: return Response( {'errors': 'Рецепт уже находится в корзине.'}, status=status.HTTP_400_BAD_REQUEST ) relation_obj.is_in_shopping_cart = True relation_obj.save() # используем любой удобный сериализатор, можно и RecipeSerializer serializer = RecipeSerializer(recipe) return Response(serializer.data, status=status.HTTP_201_CREATED) @action( detail=True, methods=['delete'], permission_classes=[IsAuthenticated], url_path='shopping_cart', # /recipes/<pk>/shopping_cart/ ) def remove_from_shopping_cart(self, request, pk=None): """Удаляем рецепт из корзины покупок.""" user = request.user recipe = get_object_or_404(Recipe, pk=pk) relation_qs = FavoriteAndShoppingCart.objects.filter( user=user, recipe=recipe, is_in_shopping_cart=True ) if not relation_qs.exists(): return Response( {'errors': 'Рецепта нет в вашей корзине, удалять нечего.'}, status=status.HTTP_400_BAD_REQUEST ) relation_obj = relation_qs.first() # Либо удаляем запись совсем, либо просто ставим флаг в False # relation_obj.delete() relation_obj.is_in_shopping_cart = False relation_obj.save() return Response(status=status.HTTP_204_NO_CONTENT)
Разделение на два экшена (add_to_shopping_cart
и remove_from_shopping_cart
) позволяет явно разграничить операции и разные http-методы.
POST
(добавить) → add_to_shopping_cart
.DELETE
(удалить) → remove_from_shopping_cart
.Проверка существования объекта FavoriteAndShoppingCart
:
add_to_shopping_cart
при создании через get_or_create
вы избавляетесь от лишнего «ручного» поиска.remove_from_shopping_cart
сначала фильтруем по is_in_shopping_cart=True
, чтобы точно убедиться, что рецепт именно «в корзине».Ответы:
201 (Created)
, возвращаем сериализованные данные рецепта (или можно вернуть «минималистичные» данные).204 (No Content)
, обычно тело ответа не требуется. Если нужно — можно вернуть JSON с {"detail": "Рецепт удалён из корзины"}
.Проверка на уже существующий статус:
400
, иначе выставляем флаг is_in_shopping_cart = True
.400
.Таким образом вы сможете вызывать:
textPOST /api/recipes/<recipe_id>/shopping_cart/ DELETE /api/recipes/<recipe_id>/shopping_cart/
для добавления/удаления соответствующего рецепта из корзины покупок.