Подтвердить что ты не робот

Django: Как автоматически изменить значение поля в момент, указанный в том же объекте?

Я работаю над проектом django для гоночного события, в котором таблица в базе данных имеет три поля.

1) Булевое поле, чтобы узнать, активна ли раса или нет

2) Время начала гонки

3) Время окончания гонки

При создании объекта из него указываются start_time и end_time. Как изменить значение логического поля на True, когда начинается гонка и
to False, когда он заканчивается? Как планировать эти действия?

4b9b3361

Ответ 1

Чтобы автоматически обновить поле модели через определенное время, вы можете использовать Задачи сельдерея.

Шаг 1. Создание задачи Сельдерея

Сначала мы создадим задачу celery c set_race_as_inactive, которая установит флаг is_active race_object на False после того, как текущая дата будет больше, чем end_time race_object.

Эта задача будет выполняться Celery, только если текущее время больше, чем объект гонки end_time.

@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: вызовите эту задачу celery, используя eta аргумент

После создания задачи сельдерея set_race_as_inactive, нам нужно вызвать эту задачу сельдерея.

Мы будем называть эту задачу всякий раз, когда мы сохраняем новую race_object в нашей базе данных. Таким образом, всякий раз, когда новый race_object будет сохранен, будет запущена задача celery, которая будет выполняться только после end_time race_object.

Мы будем называть задачу с помощью apply_async() и передавать аргумент eta как end_time race_object.

Как Документы Сельдерей,

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 "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 выполняется так, что только при создании новых объектов создается задача celery. Если мы этого не сделаем, то для каждого вызова .save() (либо INSERT или UPDATE) будет создана задача сельдерея, которую мы не хотим. Это приведет к множеству ненужных задач сельдерея, ожидающих исполнения, и перегрузит наши очереди сельдерея.

Преимущества использования <пользы для использования сельдерея - это то, что обновление флага is_active будет происходить автоматически в фоновом режиме асинхронно, если вам не нужно беспокоиться об их ручном обновлении. Каждый раз, когда создается новый объект гонки, задача будет запущена, и Сельдерей отложит свое исполнение до end_time гонки. По истечении end_time, Celery выполнит эту задачу.

Ответ 2

Предполагая следующие сценарии -

  • Вы хотите быть независимым от базы данных
  • Как только гонка закончится, она никогда не начнется снова, поэтому как только active будет false, он больше никогда не будет 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()

Итак, когда вы сохраните объект после окончания гонки, он обновит флаг.

Но если вам не удастся обновить гонку, когда она закончится, и вам нужно, чтобы они были рассчитаны автоматически, вы могли бы использовать планировщик. Как Celery, поскольку @rahul предложил периодически обновлять. Но для этого варианта вы должны принять тот факт, что флаг active не будет обновляться в точное время окончания игры. Это будет зависеть от того, как часто вы запускаете планировщик.

Ответ 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 со старыми версиями, это тривиальное изменение и нормализует вашу базу данных, если не было намеренно создано "активное" поле.

Ответ 4

Есть ли какая-то причина, по которой вы не просто вычислили логическое поле в своей бизнес-логике? то есть, когда вы получаете запрос, относящийся к этой гонке, просто проверяйте время и оцените, активна ли гонка (для отображения, анализа) и т.д.

Я предполагаю, что у вас не будет высокой нагрузки (одна из причин предварительной обработки), как это.

Ответ 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)