Django:如何在同一对象中提到的时间自动更改字段的值?
Posted
技术标签:
【中文标题】Django:如何在同一对象中提到的时间自动更改字段的值?【英文标题】:Django: How to automatically change a field's value at the time mentioned in the same object? 【发布时间】:2015-07-06 08:13:11 【问题描述】:我正在开发一个用于赛车活动的 django 项目,其中数据库中的一个表具有三个字段。
1)布尔字段来判断比赛是否活跃
2)比赛开始时间
3)比赛结束时间
在创建它的对象时,指定了 start_time 和 end_time。如何在比赛开始时将布尔字段的值更改为 True 结束时为 False?如何安排这些活动?
【问题讨论】:
【参考方案1】:要在特定时间后自动更新模型字段,您可以使用Celery tasks。
第 1 步:创建 Celery 任务
我们将首先创建一个名为 set_race_as_inactive
的 celery 任务,它将在当前日期大于 race_object
的 end_time
之后将 race_object
的 is_active
标志设置为 False
。
只有当当前时间大于比赛对象的end_time
时,这个任务才会被Celery
执行。
@app.task
def set_race_as_inactive(race_object):
"""
This celery task sets the 'is_active' flag of the race object
to False in the database after the race end time has elapsed.
"""
race_object.is_active = False # set the race as not active
race_object.save() # save the race object
第 2 步:使用 eta
参数调用此 celery 任务
创建 celery 任务set_race_as_inactive
后,我们需要调用这个 celery 任务。
每当我们将新的race_object
保存到我们的数据库中时,我们都会调用此任务。因此,每当保存新的race_object
时,将触发一个 celery 任务,该任务仅在 race_object
的 end_time
之后执行。
我们将使用apply_async()
调用任务并将eta
参数作为race_object
的end_time
传递。
根据Celery docs,
ETA(预计到达时间)可让您设置特定日期和 执行任务的最早时间。
任务保证在指定时间后的某个时间执行 日期和时间,但不一定是那个确切的时间。
from my_app.tasks import set_race_as_inactive
class RaceModel(models.Model):
...
def save(self, *args, **kwargs):
..
create_task = False # variable to know if celery task is to be created
if self.pk is None: # Check if instance has 'pk' attribute set
# Celery Task is to created in case of 'INSERT'
create_task = True # set the variable
super(RaceModel, self).save(*args, **kwargs) # Call the Django's "real" save() method.
if create_task: # check if task is to be created
# pass the current instance as 'args' and call the task with 'eta' argument
# to execute after the race `end_time`
set_race_as_inactive.apply_async(args=[self], eta=self.end_time) # task will be executed after 'race_end_time'
对self.pk
和None
的检查是为了只有在创建新对象的情况下才会创建芹菜任务。如果我们不这样做,那么对于每个.save()
调用(INSERT
或UPDATE
)都会创建一个我们不想要的 celery 任务。这将导致许多不必要的 celery 任务等待执行,并使我们的 celery 队列超载。
使用 Celery 的好处是 is_active
标志的更新将在后台异步自动发生,您无需担心手动更新它们。每次创建新的竞赛对象时,都会触发一个任务,Celery 将推迟执行,直到竞赛的end_time
。在end_time
过去后,Celery 将执行该任务。
【讨论】:
评论不用于扩展讨论;这个对话是moved to chat。【参考方案2】:假设以下场景 -
-
您希望独立于数据库
一旦比赛结束,它就永远不会重新开始,所以一旦
active
变成false
,它就永远不会再变成true
。
有多种方法可以根据需要自动设置为 true -
如果只在使用对象的时候需要,可以使用属性——
@property
def active(self):
return self.end_date > datetime.datetime.utcnow() //I used local time if you want
你也可以把它放在init -
def __init__(self):
super().__init__()
self.active = self.end_date > datetime.datetime.utcnow()
但这并没有给你执行查询的选项,因为值是在对象加载到内存后计算的。
如果要进行查询,那么我们需要更新数据库中的值并保存。假设当比赛结束时,您在覆盖的保存方法中更新日期 -
def save(self, *args, **kwargs):
self.active = self.end_date > datetime.datetime.utcnow()
super().save()
所以当你在比赛结束后保存对象时,它会更新标志。
但是,如果您无法在比赛结束时更新比赛并且您需要自动计算它们,您可以使用调度程序。就像@rahul 建议的Celery
一样定期更新。但是对于这个选项,你必须接受这样一个事实,active
标志不会在游戏结束的确切时间更新。这取决于您运行调度程序的频率。
【讨论】:
这适用于大约 100k 的比赛吗?意味着效率。 是的,我有一个类似的场景,它每天输入 1k 笔交易。【参考方案3】:在我看来,您的“活动”字段应该是这样的方法:
from django.utils import timezone
class Race(models.Model):
start = models.DateTimeField()
end = models.DateTimeField()
def active(self):
now = timezone.now()
if self.start < now and now < self.end:
return True
return False
如果您在旧版本中使用 Django 1.7+ 或 South,这是一个微不足道的更改,并且也会规范您的数据库,除非有意创建“活动”字段。
【讨论】:
不会增加时间吗?意味着每当我查询该对象(调用活动函数)时都会进行时间检查? 是的,调用该方法的成本,但低于使用 Celery 的替代方案。此外,它既简单又干净,而且您最终将获得数据库的规范化模式。还要考虑到,除非你使用 .values 或 .values_list,否则无论如何你都会从数据库中获取日期时间字段,这比函数调用的成本要高得多。 那么对于大量使用,Celery 更好还是这个更好? 重要的是要确定导致性能下降的原因。如果您将经常由多个用户调用我描述的方法,那么打破规范化并将 active 作为模型的属性是有意义的,您将使用上述 Celery 代码对其进行更新。 @cold_coder【参考方案4】:您有什么理由不只计算业务逻辑中的布尔字段?即当您收到与该比赛相关的请求时,只需检查时间并评估比赛是否正在进行(用于显示、分析)等。
我假设您不会有这样的高负载(预处理的一个原因)。
【讨论】:
实际上会有大约 2-3 百万个对象,所以我需要一种有效的方法。 在这种情况下,我建议您构建一个服务,按照比赛即将开始的顺序对比赛进行排队,然后再构建一个服务,将比赛结束排队并在表格中进行更新.在您的数据库内部执行此操作可能更容易。 数据库内部?我没明白。您能否详细说明如何实现它? 您可以设置一个查询以每隔一段时间更新所有行(确保使用索引和相当具体的 WHERE 语句来最小化写入)。看到这个:pgadmin.org/docs/dev/pgagent.html 它仅适用于 PostgreSQL。其他人呢?【参考方案5】:您可以覆盖预定义的保存方法,例如this:
def save(self, *args, **kwargs):
if start:
boolean_field = True
super(YouModel, self).save(*args, **kwargs)
else:
boolean_filed = False
super(YouModel, self).save(*args, **kwargs)
【讨论】:
Real time 超过对象中提到的 end_time 时,boolean_field 如何自动更新为 False?以上是关于Django:如何在同一对象中提到的时间自动更改字段的值?的主要内容,如果未能解决你的问题,请参考以下文章
django 管理界面 - 如何在更改列表视图中折叠/展开对象详细信息?