You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将关联模型生成的is_in_shopping_cart字段改为BooleanField?

问题:如何实现Recipe模型的「是否在购物车」布尔属性?

现有模型代码

PurchaseList 模型

class PurchaseList(models.Model):

    author = models.ForeignKey('User',
                               on_delete=models.CASCADE,
                               verbose_name='Автор',
                               related_name='added_to_cart')
    recipe = models.ForeignKey('Recipe',
                               on_delete=models.CASCADE,
                               verbose_name='Рецепт',
                               related_name='is_in_shopping_cart')

    class Meta:
        verbose_name = 'Список покупок'
        verbose_name_plural = 'Списки покупок'
        ordering = ['recipes']  # 注意:模型中无`recipes`字段,应改为`recipe`

    def __str__(self):
        return self.recipe  # 建议改为`self.recipe.name`,否则会返回Recipe对象而非名称

Recipe 模型

class Recipe(models.Model):

    tags = models.ManyToManyField(Tag, verbose_name='Теги')
    author = models.ForeignKey('User', related_name='recipes',
                               on_delete=models.CASCADE, verbose_name='Автор')
    ingridients = models.ManyToManyField(Product, through='IngredientToRecipe',
                                         verbose_name='Ингредиент')
    name = models.CharField(max_length=16, verbose_name='Наименование')
    image = models.ImageField(
        upload_to='recipes/images/',
        default=None,
        verbose_name='Изображение'
    )
    text = models.TextField(verbose_name='Описание')
    cooking_time = models.IntegerField(
        verbose_name='Время приготовления в минутах')
    pub_date = models.DateTimeField(auto_now_add=True,
                                    verbose_name='Дата публикации')

    class Meta:
        verbose_name = 'Рецепт'
        verbose_name_plural = 'Рецепты'
        ordering = ['-pub_date']

    def __str__(self):
        return self.title  # 注意:模型中定义的是`name`字段,应改为`self.name`,否则会报错

需求

给Recipe模型添加布尔属性is_in_shopping_cart,用于判断当前登录用户是否将该食谱加入购物车。

现有方案的问题

你找到的@property写法存在逻辑错误:

@property
def is_in_shopping_cart(self):  
    return PurchaseList.objects.filter(author=self.author).exists()

这里的self.author是Recipe的创建者,而非当前访问的用户,所以判断的是「食谱作者是否将自己的食谱加入购物车」,完全不符合需求。

正确实现方式

方式1:DRF序列化器中自定义字段(适合API场景)

如果使用Django REST Framework,可在序列化器中传入当前用户,动态计算该属性:

class RecipeSerializer(serializers.ModelSerializer):
    is_in_shopping_cart = serializers.SerializerMethodField()

    def get_is_in_shopping_cart(self, obj):
        user = self.context['request'].user
        if user.is_anonymous:
            return False
        return PurchaseList.objects.filter(author=user, recipe=obj).exists()

    class Meta:
        model = Recipe
        fields = ['id', 'name', 'is_in_shopping_cart', ...]

方式2:用annotate批量查询(高效无N+1问题)

在视图查询Recipe列表时,通过annotate批量为每个Recipe对象添加该布尔字段,避免循环查询数据库:

from django.db.models import Exists, OuterRef

def recipe_list(request):
    user = request.user
    recipes = Recipe.objects.annotate(
        is_in_shopping_cart=Exists(
            PurchaseList.objects.filter(
                author=user,
                recipe=OuterRef('pk')
            )
        )
    )
    # 此时recipes中的每个对象都拥有is_in_shopping_cart布尔属性

方式3:修正后的实例方法(不推荐列表场景使用)

若一定要用实例方法,需传入当前用户作为参数(@property无法传参),但这种方式在列表查询时会引发N+1查询问题,效率较低:

class Recipe(models.Model):
    # ... 其他字段 ...
    def is_in_shopping_cart(self, user):
        if user.is_anonymous:
            return False
        return PurchaseList.objects.filter(author=user, recipe=self).exists()

关于朋友的建议

你朋友提到的「给recipe字段添加名为field的属性并设为BooleanField」完全不可行:

  • recipe是PurchaseList模型中的ForeignKey字段,用于关联Recipe,不能直接改为BooleanField;
  • 购物车本质是「用户-食谱」的多对多关联,PurchaseList就是该关联的中间表,逻辑上无法用单个布尔字段替代关联关系。

内容的提问来源于stack exchange,提问作者gazone-gazelle17

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 18:19:54