过滤已由函数填充的自定义 Django 字段



我不知道正确的术语,所以我希望标题不会导致混淆。

我有以下模型。

class Material(models.Model):
yes = models.IntegerField()
no =  models.IntegerField()
def _votes(self):
return int(self.no + self.yes)
def _ratio(self):
v = self.votes
y = self.yes
try:
return float(y)/v
except ZeroDivisionError:
return float(0)
ratio = property(_ratio)
votes = property(_votes)

这允许我查询Material项并使用每个字段。

Material.objects.all()[0].yes   # returns 5
Material.objects.all()[0].no    # returns 3
Material.objects.all()[0].votes # returns 8
Material.objects.all()[0].ratio # returns 0.625

目前为止,一切都好。我想过滤过滤器的值。例如,我只想在比率大于 0.8 时选择一个Material实例。

Material.objects.filter(ratio__gt=0.8)  # what I'd want to do

但是,这样做会返回错误,声称ratio不是字段。

FieldError: Cannot resolve keyword 'ratio' into field. Choices are: id, no, yes

如何执行此查询?我假设我需要对我的模型进行一些更改,因此ratiovotes被注册为实际字段。怎么做?

属性

和列

属性在模型级别定义。Python可以处理这些属性,你可以用它做非常复杂的事情(例如执行HTTP请求(。数据库不知道存在属性,大多数数据库也没有执行非常复杂功能的方法(通常数据库本身不执行非常复杂的任务(。

这意味着为了过滤,我们可以检索所有值后执行此操作。但这当然是低效的:这意味着我们首先将所有项目加载到内存中,如果过滤器相当严格,我们将做很多工作来只丢弃大部分结果。

将属性转换为F表达式

如果属性相当简单,我们可以编写一个等效于它的数据库。例如,您的.vote属性实际上是:

fvotes = F('yes') + F('no')  # total number of votes

其中F(..)是 Django 用来引用列的对象。

如果对于我们的ratio我们将始终排除没有votes的值(因为阈值高于零(,那么我们可以编写如下注释:

fratio = F('yes') / fvotes  # ratio of the votes

所以现在我们可以用这些额外的属性来注释我们的数据库:

from django.db.models import F
fvotes = F('yes') + F('no')
Material.objects.annotate(
votes=fvotes,
fratio=F('yes') / fvotes
).filter(fratio__gt=0.8)

所以在这里我们基本上写了一些查询,比如:

SELECT yes, no, yes + no AS votes, yes / (yes + no) AS ratio
FROM material
WHERE ratio > 0.8

因此,此查询是在数据库级别执行的,这通常比在 Django 级别手动执行过滤运行得更快。但如前所述,将 Python 函数转换为F表达式需要一些技能。此外,某些函数无法转换为表达式。例如,大多数数据库无法访问文件系统,无法联系Web服务等。在这种情况下,您必须手动进行过滤。

手动过滤(通常效率较低(

如果您必须手动进行过滤(出于上述原因(,我们可以使用 Python 的filter(..)函数。请注意,这个过滤器不会返回QuerySet,因此我们不能对它执行额外的.filter(..).first().annotate(..)等函数。在这种情况下,例如,我们可以使用 lambda 表达式:

filter(lambda x: x.ratio > 0.8, Material.objects.all())

所以这里我们把所有Material对象加载到内存中,让 Python 手动计算ratio并执行检查。

最新更新