django-simple-history
):
class MyModel (models.Model):
status = models.IntegerField()
history = HistoricalRecords()
我想获取在给定日期没有特定
status
的所有实例(即在限制日期具有不同状态的所有实例,加上当时不存在的所有实例)。
以下查询将返回在限制日期之前的任何时间点从未有过
status = 4
的所有实例:
MyModel.filter (~Exists (
MyModel.history.filter (
id = OuterRef ("id"),
history_date__lte = limit_date,
status = 4))
但不幸的是,它还删除了过去某个日期具有
status = 4
的实例,然后在限制日期之前更改为不同的 status
,我想保留这些实例。
以下应该给出正确的结果:
MyModel.filter (~Exists (
MyModel.history.filter (
id = OuterRef ("id"),
history_date__lte = limit_date)
.order_by ("-history_date")
[:1]
.filter (status = 4)))
不幸的是它不起作用:
Cannot filter a query once a slice has been taken.
这个问题链接到这个文档页面,它解释了查询集被切片后不允许过滤。
请注意,错误来自 Django 中的
assert
。如果我注释掉 assert
中的 django/db/models/query.py:953
,那么代码似乎可以工作并给出预期的结果。然而,在上游依赖项中注释掉 assert
在生产中并不是一个可行的解决方案。
那么有没有一种干净的方法来根据对象过去的状态来过滤我的查询集?
历史模型仅在项目更改时保存记录,而不是每天保存。因此,我们可以通过以下方式获取给定日期的状态:
from django.db.models import OuterRef, Q, Subquery
MyModel.annotate(
historic_status=Subquery(
MyModel.history.filter(id=OuterRef('id'), history_date__lte=limit_date)
.order_by('-history_date')
.values('status')[:1]
)
).filter(~Q(history_status=4) | Q(history_status=None))
因此,我们首先查找历史模型的
status
,其日期为 before 或等于 limit_date
。通过首先订购最新的 history_date
,我们可以获得最新的状态。
这会将
historic_status
设置为status
时的
limit_date
、或,如果记录当时不存在,NULL
(None
)。
因此,我们可以过滤
MyModel
,因此 history_status
不是四个(并且我们明确添加了 NULL
检查),尽管通常以下内容应该足够了:
from django.db.models import OuterRef, Q, Subquery
MyModel.annotate(
historic_status=Subquery(
MyModel.history.filter(id=OuterRef('id'), history_date__lte=limit_date)
.order_by('-history_date')
.values('status')[:1]
)
).filter(~Q(history_status=4))