如何使用注释在 Django ORM 中创建通知日期字段
How to create notification date field in DjangoORM using annotate
我想在 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)
Now the formula of date notification of breeding is
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()。任何人都知道如何制定这个所需的查询?这对我有很大的帮助。
也许可以考虑在您的模型上使用 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,例如 SUM
、COALESCE
或类似的,可以在您的查询中生成有效的 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')))
)
我从 中提取了一些,但这是一个仅适用于 postgres 的函数。我对此进行了测试,它适用于 postgres 数据库。我没有针对 mysql 测试我的代码,因此您可能需要稍微调整一下。但这基本上是如何做到的。
如果您想了解更多关于如何编写自己的表达式,look here or dig into the django source and have a look at already implemented expressions like CAST。
我想在 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)
Now the formula of date notification of breeding is
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()。任何人都知道如何制定这个所需的查询?这对我有很大的帮助。
也许可以考虑在您的模型上使用 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,例如 SUM
、COALESCE
或类似的,可以在您的查询中生成有效的 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')))
)
我从
如果您想了解更多关于如何编写自己的表达式,look here or dig into the django source and have a look at already implemented expressions like CAST。