如何使用注释在 Django ORM 中创建通知日期字段

Posted

技术标签:

【中文标题】如何使用注释在 Django ORM 中创建通知日期字段【英文标题】:How to create notification date field in DjangoORM using annotate 【发布时间】:2017-06-14 19:53:09 【问题描述】:

我想在 DjangoORM 中使用注释创建一个名为 notification_date 的新字段。

这是我的模型:

SpeciesType(models.Model):
   # ... some species type setting fields.
   heat_lapse = IntegerField()
   estrous = IntegerField()


Breeding(models.Model):
   # .. some breeding fields

   species_type = ForeignKey(SpeciesType, related_name="breedings", on_delete=CASCADE)


   created_at = DateTimeField(auto_add_now=True)

现在育种日期通知的公式是

Breeding.created_at + (SpeciesType.heat_lapse * SpeciesType.estrous) in days 

e.g. 1/29/2017 11:21PM + (3 * 21) in days = 4/2/2017 as notification date

所以为了实现这一点,我使用 timedelta、F() 对象和 ExpressionWrapper 创建了这个查询过滤器:

from django.db.models import F, ExpressionWrapper, DateField
from datetime import date, timedelta

Breeding.objects.annotate(
     notification_date = ExpressionWrapper(
        F('created_at') + 
        timedelta(days=(
            F('species_type__heat_lapse') * F('species_type__estrous')
        ))
     , output_field=DateField())
).filter(
    notification_date__lte == date.today()
)

但这不起作用,因为您不能在 timedelta 内执行 F()。任何人都知道如何制定这个所需的查询?这对我有很大的帮助。

【问题讨论】:

【参考方案1】:

也许考虑在您的模型上使用cached_property。如果您小心,这将更容易并且不涉及任何额外的查询,所有使用的值都已正确预取。您也可以像使用普通属性一样使用它,这意味着使用 my_breading_instance.notification_date 访问它

from datetime import date, timedelta

from django.db import models
from django.utils.functional import cached_property


Breeding(models.Model):
    # .. your breeding fields

    @cached_propery
    def notification_date(self):
        delta = timedelta(days=(self.species_type.heat_leapse * self.species_type.estrous))
        return self.created_at + delta

第一次访问后,该值也会被缓存。

更新:

如果您确实需要对此进行注释,因为您想进一步过滤 notification_date 上的查询集,您必须编写自己的聚合函数。

正如您已经注意到的,您不能在注释中使用 timedelta,因为要注释的值必须完全在数据库内部计算。因此,您只能使用数据库函数来计算它。

Django 提供了一些common functions,如SUMCOALESCE 或类似的,它们在您的查询中生成有效的sql。

然而,你需要的并没有在 django 中实现。但是你可以自己写。 mysql 需要的那个叫做DATEADD。该函数必须创建看起来例如的 sql。像这样:

SELECT OrderId,DATE_ADD(OrderDate,INTERVAL 30 DAY) AS OrderPayDate FROM Orders

应该是这样的:

class DateAdd(models.Func):
    """
    Custom Func expression to add date and int fields as day addition
    """
    function = 'DATE_ADD'
    arg_joiner = ", INTERVAL "
    template = "%(function)s(%(expressions)s DAY)"
    output_field = models.DateTimeField()

这会创建如下所示的 sql:

DATE_ADD("created_at", INTERVAL ("heat_lapse" * "estrous") DAY) AS "notifiaction_date"

使用 arg_joiner 连接 DateAdd 函数的两个参数以创建必要的 sql 是一个肮脏的技巧。

你可以这样使用它:

qs = Breeding.objects.annotate(
    notifiaction_date=DateAdd('created_at', (models.F('species_type__heat_lapse') * models.F('species_type__estrous')))
)

我从this answer 中获取了一些,但这是一个仅适用于 postgres 的函数。我对此进行了测试,它适用于 postgres 数据库。我没有为 mysql 测试我的代码,所以也许你需要稍微调整一下。但这基本上是如何做到的。

如果您想了解更多关于如何编写自己的表达式,look here 或深入了解 django 源代码并查看已经实现的表达式,例如 CAST。

【讨论】:

单个实例访问会影响性能,考虑my_breading_instance.notification_date.date() <= date.today()要带多少条记录 如果我使用 get() 获取实例会找到,但过滤器可能意味着更大的记录 那我猜你必须自己写Func。这取决于您使用的数据库。对于 postgres,您必须使用 INTERVALfunction 和对于 mysql DATEADD 我用的是mysql,Func是什么意思?类似于存储过程的东西?

以上是关于如何使用注释在 Django ORM 中创建通知日期字段的主要内容,如果未能解决你的问题,请参考以下文章

Django ORM 在 SQL Join 中创建幻影别名

在 Django 中创建用户通知系统

如何创建临时表而不丢失 django 中的 ORM?

如何使用 MongoDB 在 Prisma ORM 中创建类别及其子类别

如何使用 Django ORM 将注释字符串转换为布尔值

使用Django ORM查询如何注释是不是存在多级外键层次结构