Django饮品配方应用:如何基于多对多标签过滤相似饮品?
问题描述
我正在开发一款饮品配方应用,每个DrinkRecipe对象通过多对多字段drinkTag和drinkTagTaste关联标签(口味描述)。现需在详情页显示3款与当前饮品标签相似的饮品。
我的视图代码如下:
def drinkdetail_view(request, slug=None): #Grab the correct drink try: obj = DrinkRecipe.objects.get(slug=slug) except: obj = None if obj is None: return HttpResponse("Not found.") context = { "drink": obj, } return render(request,'drinks/drinkdetail.html', context)
模型代码如下:
class Tag(models.Model): drinkTag = models.CharField(max_length=255, blank=False) def __str__(self): return f"{self.drinkTag}" class TagTaste(models.Model): drinkTagTaste = models.CharField(max_length=255, blank=False) def __str__(self): return f"{self.drinkTagTaste}" class Ingredient(models.Model): ingredientRum = models.ForeignKey('Bottle', on_delete=models.SET_NULL, null=True, blank=True) ingredientName = models.CharField(max_length=255, blank=True) ingredientAmount = models.DecimalField(decimal_places=1,max_digits=5) drink = models.ForeignKey('DrinkRecipe', on_delete=models.CASCADE) def __str__(self): if self.ingredientRum: return f"{self.ingredientRum}" else: return f"{self.ingredientName}" class DrinkRecipe(models.Model): drinkTag = models.ManyToManyField(Tag) drinkTagRum = models.ForeignKey(Bottle, on_delete=models.SET_NULL, null=True) drinkTagTaste = models.ManyToManyField(TagTaste) drinkName = models.CharField(max_length=255, blank=False) drinkDescription = HTMLField() drinkImage = models.ImageField(upload_to=image_upload_handler) drinkActive = models.BooleanField(default=True) slug = models.SlugField(unique=True, blank=True, null=True) imgLocation = 'drinks' def __str__(self): return f"{self.drinkName}" def get_ingredients(self): return self.ingredient_set.all() def get_absolute_url(self): return reverse ('rhumSite:drinkDetail', kwargs={'slug':self.slug})
我尝试使用similar_drinks = DrinkRecipe.objects.filter(drinkTag__drinkTag=obj.drinkTag)来过滤,但无法生效,硬编码标签值则可以正常运行。请问如何在视图中基于当前饮品的drinkTag或drinkTagTaste过滤出相似饮品?
解决方案
你当前的过滤写法错误在于:obj.drinkTag是多对多关系的管理器对象,不是单个标签值,无法直接用于匹配。以下是正确的实现方式:
1. 基于drinkTag过滤相似饮品
通过drinkTag__in传入当前饮品的所有标签集合,匹配包含任意相同标签的饮品:
# 获取当前饮品的所有drinkTag对象 current_tags = obj.drinkTag.all() # 过滤相似饮品:排除当前饮品,去重后取前3个 similar_drinks = DrinkRecipe.objects.filter( drinkTag__in=current_tags, drinkActive=True # 可选:仅显示激活状态的饮品 ).exclude(id=obj.id).distinct()[:3]
distinct()避免同一饮品因多个匹配标签重复出现exclude(id=obj.id)确保不会推荐当前饮品本身
2. 基于drinkTagTaste过滤相似饮品
逻辑与上述一致,替换为口味标签字段即可:
current_taste_tags = obj.drinkTagTaste.all() similar_drinks_by_taste = DrinkRecipe.objects.filter( drinkTagTaste__in=current_taste_tags, drinkActive=True ).exclude(id=obj.id).distinct()[:3]
3. 混合两种标签推荐(可选)
如果想同时匹配drinkTag和drinkTagTaste,使用Q对象实现或条件:
from django.db.models import Q current_tags = obj.drinkTag.all() current_taste_tags = obj.drinkTagTaste.all() similar_drinks = DrinkRecipe.objects.filter( Q(drinkTag__in=current_tags) | Q(drinkTagTaste__in=current_taste_tags), drinkActive=True ).exclude(id=obj.id).distinct()[:3]
4. 优化:按相似度排序(可选)
通过计算匹配标签的数量,让重叠标签最多的饮品排在前面:
from django.db.models import Count similar_drinks = DrinkRecipe.objects.filter( drinkTag__in=current_tags, drinkActive=True ).exclude(id=obj.id).annotate( matching_tags=Count('drinkTag') # 统计匹配的标签数量 ).order_by('-matching_tags').distinct()[:3]
整合到视图的完整代码
def drinkdetail_view(request, slug=None): try: obj = DrinkRecipe.objects.get(slug=slug) except DrinkRecipe.DoesNotExist: return HttpResponse("Not found.") # 获取基于drinkTag的相似饮品(可根据需求替换为其他过滤逻辑) current_tags = obj.drinkTag.all() similar_drinks = DrinkRecipe.objects.filter( drinkTag__in=current_tags, drinkActive=True ).exclude(id=obj.id).annotate( matching_tags=Count('drinkTag') ).order_by('-matching_tags').distinct()[:3] context = { "drink": obj, "similar_drinks": similar_drinks } return render(request,'drinks/drinkdetail.html', context)
之后在模板中遍历similar_drinks即可展示推荐的饮品。
内容的提问来源于stack exchange,提问作者Thomas Doll-Datema
相关产品推荐
相关产品推荐

