如何使用注释

时间:2017-01-29 15:40:18

标签: django python-2.7 django-queryset django-orm django-filter

我想在 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 个答案:

答案 0 :(得分:2)

也许考虑在模型上使用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或者simmilar,可以在查询中生成有效的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