如何将关联模型生成的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
相关产品推荐
相关产品推荐

