如何基于ManyToMany字段匹配获取指定对象?Django DRF问题求助
问题描述
开发一款食谱APP,目标是返回用户可用现有食材制作的餐品列表,使用Django RestFramework(DRF)实现。当前CraftWithUsersIngridientsListAPIView无法正常工作,报错AttributeError: 'QuerySet' object has no attribute 'ingridients'。
相关代码
models.py
# models.py class Meal(models.Model): name = models.CharField(max_length=250) description = models.TextField(blank=True, null=True) recipe = models.TextField() is_published = models.BooleanField(default=False) category = ForeignKey('Category', on_delete=models.CASCADE, null=True) user = ForeignKey(User, verbose_name='User', on_delete= models.CASCADE) difficulty = ForeignKey('Difficulty', on_delete=models.PROTECT, null=True) ingridients = models.ManyToManyField('Ingridient') class Ingridient(models.Model): name = models.CharField(max_length=100, db_index=True) ico = models.ImageField(upload_to="photos/%Y/%m/%d/", blank=True, null=True) category = ForeignKey('CategoryIngridients', on_delete=models.CASCADE, null=True) def __str__(self): return self.name class CookBookUser(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) ingridients = models.ManyToManyField('Ingridient')
serializer.py
class MealSerializer(serializers.ModelSerializer): class Meta: model = Meal fields = "__all__"
views.py
class CraftWithUsersIngridientsListAPIView(generics.ListAPIView): serializer_class = MealSerializer def get_queryset(self): return Meal.objects.filter(ingridients=CookBookUser.objects.filter(user_id = self.request.user.id).ingridients)
解决方案
错误原因
CookBookUser.objects.filter(user_id=self.request.user.id)返回的是QuerySet对象(即使只有一条数据),而不是单个CookBookUser实例,因此无法直接通过.ingridients访问多对多字段。
修复步骤
- 获取当前用户对应的单个
CookBookUser实例(因为是OneToOne关联,每个用户最多对应一个) - 提取用户拥有的食材ID列表
- 筛选出所有食材都包含在用户现有食材中的餐品(确保用户能完整制作该餐品)
修改后的views.py代码:
from django.db.models import Count, F from rest_framework import generics class CraftWithUsersIngridientsListAPIView(generics.ListAPIView): serializer_class = MealSerializer def get_queryset(self): # 获取当前用户的CookBookUser实例,处理用户未绑定的情况 try: cook_user = CookBookUser.objects.get(user=self.request.user) except CookBookUser.DoesNotExist: return Meal.objects.none() # 获取用户拥有的食材ID集合 user_ingredient_ids = cook_user.ingridients.values_list('id', flat=True) # 筛选出所有食材都在用户现有食材中的餐品 return Meal.objects.annotate( # 统计餐品需要的食材总数 total_ingredients=Count('ingridients') ).filter( # 筛选出至少有一个食材匹配的餐品 ingridients__in=user_ingredient_ids ).annotate( # 统计匹配的食材数量 matched_ingredients=Count('ingridients') ).filter( # 确保餐品所有食材都被匹配 total_ingredients=F('matched_ingredients') ).filter(is_published=True) # 可选:只返回已发布的餐品
额外优化建议
- 添加
is_published=True过滤,只返回已发布的餐品,避免返回草稿状态的内容 - 若需求是“包含部分食材即可推荐”,可去掉最后一步的
total_ingredients=F('matched_ingredients')过滤,同时按匹配食材数量排序:.order_by('-matched_ingredients')
内容的提问来源于stack exchange,提问作者Vleo
相关产品推荐
相关产品推荐

