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_objectend_time 之后将 race_objectis_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_objectend_time 之后执行。

我们将使用apply_async() 调用任务并将eta 参数作为race_objectend_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.pkNone 的检查是为了只有在创建新对象的情况下才会创建芹菜任务。如果我们不这样做,那么对于每个.save() 调用(INSERTUPDATE)都会创建一个我们不想要的 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 admin:单独的只读视图和更改视图

django 管理界面 - 如何在更改列表视图中折叠/展开对象详细信息?

如何通过多对一关系中同一相关对象的两个属性在 django 中进行过滤?

Django 条件模板继承

Django ElasticBeanstalk 更改存储桶

Django-admin:如何在记录更改列表中显示指向对象信息页面的链接而不是编辑表单?