在Django视图中使用F表达式进行QuerySet注解时触发TypeError
问题:Django中使用F表达式计算搜索相关性得分时触发TypeError错误
问题场景
我在Django中实现了处理搜索功能的视图,该视图根据用户查询过滤Question和Code数据,并尝试用Django的F表达式计算相关性得分,但在使用F表达式进行QuerySet注解时遇到了TypeError错误。
视图相关代码片段
class SearchResultView(TemplateView): template_name = 'codeisc/search_result_page.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['user'] = self.request.user if self.request.user.is_authenticated else None query = kwargs.get('query') keywords = query.split() questions = Question.objects.filter(Q(short_description__icontains=query) | Q(description__icontains=query)) for keyword in keywords: questions = questions.annotate( relevance_score=( F('short_description__icontains', keyword) + F('description__icontains', keyword) ) ) questions = questions.order_by('-relevance_score') context['questions'] = questions codes = Code.objects.filter(Q(short_description__icontains=query) | Q(code_text__icontains=query)) for keyword in keywords: codes = codes.annotate( relevance_score=( F('short_description__icontains', keyword) + F('code_text__icontains', keyword) ) ) codes = codes.order_by('-relevance_score') context['codes'] = codes return context
报错信息
Exception Type: TypeError Exception Value: F.__init__() takes 2 positional arguments but 3 were given
模型定义
class Code(models.Model): author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="Code_Author") short_description = models.CharField(max_length=100, null=False, default="No Description Provided") code_text = models.TextField(blank=True, null=False) created_at = models.DateTimeField(default=timezone.now) TYPE_CHOICES = [ ("TXT", "TextFile"), ("PY", "Python"), ("JS", "Javascript"), ("C", "C"), ("CPP", "C++"), ("CS", "Csharp"), ("JV", "Java"), ] type = models.CharField(max_length=3, choices=TYPE_CHOICES, default="TXT") def __str__(self): return f"{self.short_description}:{self.author.username}" class Question(models.Model): author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="Question_Author", null=True) short_description = models.CharField(max_length=100, null=False, default="No Description Provided") description = models.TextField(null=False, default="No Description Provided") code = models.ManyToManyField(Code) created_at = models.DateTimeField(default=timezone.now) def __str__(self): return f"{self.short_description}:{self.author.username}"
解决方案
错误原因
F()仅接收字段名作为参数,不能直接传入__icontains这类查询谓词和匹配关键词,这种误用导致传入了多余参数,触发TypeError。要实现关键词匹配的相关性得分,需用Case+When来判断字段是否包含关键词,匹配则累加得分。
修改后的视图代码
from django.db.models import Case, When, Value, IntegerField class SearchResultView(TemplateView): template_name = 'codeisc/search_result_page.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['user'] = self.request.user if self.request.user.is_authenticated else None query = kwargs.get('query') # 处理空查询情况 if not query: context['questions'] = [] context['codes'] = [] return context keywords = query.split() # 计算Question的相关性得分 questions = Question.objects.filter(Q(short_description__icontains=query) | Q(description__icontains=query)) score_expr = Value(0, output_field=IntegerField()) for keyword in keywords: # 匹配短描述加1分,匹配描述加1分 score_expr += Case( When(short_description__icontains=keyword, then=Value(1)), default=Value(0), output_field=IntegerField() ) + Case( When(description__icontains=keyword, then=Value(1)), default=Value(0), output_field=IntegerField() ) questions = questions.annotate(relevance_score=score_expr).order_by('-relevance_score') context['questions'] = questions # 计算Code的相关性得分 codes = Code.objects.filter(Q(short_description__icontains=query) | Q(code_text__icontains=query)) code_score_expr = Value(0, output_field=IntegerField()) for keyword in keywords: code_score_expr += Case( When(short_description__icontains=keyword, then=Value(1)), default=Value(0), output_field=IntegerField() ) + Case( When(code_text__icontains=keyword, then=Value(1)), default=Value(0), output_field=IntegerField() ) codes = codes.annotate(relevance_score=code_score_expr).order_by('-relevance_score') context['codes'] = codes return context
说明
- 用
Case+When判断每个字段是否包含关键词,匹配则加1分,否则加0分 - 对所有关键词的得分进行累加,得到最终的相关性得分
- 避免循环中重复
annotate的问题,一次性构建完整得分表达式 - 增加空查询判断,防止无查询词时引发异常
内容的提问来源于stack exchange,提问作者BGOPC
相关产品推荐
相关产品推荐

