Django:如何在同一对象中提到的时间自动更改字段的值?

Django: How to automatically change a field's value at the time mentioned in the same object?

我正在为赛车活动开发一个 django 项目,其中数据库中的 table 具有三个字段。

1)布尔字段来知道比赛是否活跃

2)比赛开始时间

3)比赛结束时间

创建它的对象时,指定了start_time和end_time。如何在比赛开始时将布尔字段的值更改为 True
到 False 结束时?如何安排这些活动?

您有什么理由不只计算业务逻辑中的布尔字段?即当您收到与该比赛相关的请求时,只需检查时间并评估比赛是否活跃(用于显示、分析)等。

我假设您不会像这样有高负载(预处理的原因之一)。

您可以覆盖预定义的保存方法,如 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)

要在特定时间后自动更新模型字段,您可以使用Celery tasks

第 1 步:创建 Celery 任务

我们将首先创建一个名为 set_race_as_inactive 的 celery 任务,它将在当前日期大于 end_timerace_object

仅当当前时间大于比赛对象的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_objectend_time 传递。

根据Celery docs,

The ETA (estimated time of arrival) lets you set a specific date and time that is the earliest time at which your task will be executed.

The task is guaranteed to be executed at some time after the specified date and time, but not necessarily at that exact time.

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 的检查是为了仅在创建新对象时创建 celery 任务。如果我们不这样做,那么每次 .save() 调用(INSERTUPDATE)都会创建一个我们不想要的 celery 任务。这将导致许多不必要的 celery 任务等待执行,并使我们的 celery 队列超载。

使用 Celery 的 好处is_active 标志的更新将在后台异步自动发生,您无需担心手动更新它们。每次创建一个新的比赛对象时,都会触发一个任务,Celery 会将其执行推迟到比赛的 end_timeend_time 结束后,Celery 将执行该任务。

假设以下场景 -

  1. 您想独立于数据库
  2. 比赛一旦结束就再也不会开始,所以一旦 activefalse 就永远不会再是 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 标志不会在游戏结束的确切时间更新。这将取决于您 运行 调度程序的频率。

我觉得你的 "active" 字段应该是这样的方法:

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 与旧版本一起使用,这是一个微不足道的更改,也会规范化您的数据库,除非 "active" 字段是有意创建的。