@MickiMouse

Django. Как отловить изменение в поле модели?

Приветствую. Имеется модель `Card`, у неё есть поле типа `DateTimeField`, называется оно expired_date, есть своя цена и статус. Есть юзер, который платит за услуги. По умолчанию статус карточки `Inacitve`. Если юзер купит карточку, то её статус станет `Active`, а в expired_date поместится дата на день больше текущей. Когда время подписки истечёт, то есть текущая дата станет больше чем expired_date, статус карточки примет значение `Inactive`. Требуется автоматически сразу же оплатить подписку, чтобы карточка стала активной. Есть сигнал, который производит оплату, но я понятия не имею куда его засунуть, чтоб это работало автоматически, без участия пользователя. Гуглил в направлении `django crontab`, `django thread`, `django schedule`, 'celery' но ничего полезного не нашёл. То есть нужно, чтоб без участия пользователя (без запросов и т.д.) вызвался сигнал. Подскажите как это сделать? Или в каком русле гуглить? Иностранные источники принимаются.

Статус карты - функциональное поле
class Card(models.Model):
    created_at = models.DateTimeField(verbose_name='Created', auto_now_add=True)
    last_change = models.DateTimeField(verbose_name='Last change', auto_now=True, blank=True)
    expired_date = models.DateTimeField(verbose_name='Expired date', blank=True, null=True)
    subscriber = models.ForeignKey('Subscriber', on_delete=models.PROTECT, related_name='cards',
                                   null=True, blank=True)

    def status(self):
            if self.expired_date > datetime.now():
                return 'Active'
            else:
                return 'Inactive'
  • Вопрос задан
  • 262 просмотра
Решения вопроса 1
werevolff
@werevolff
Здесь есть два популярных решения проблемы. Но, сперва, разберёмся в архитектуре.

1. У нас работает некий таймер, который с некоторой периодичностью запускает python функцию.
2. Эта функция фильтрует Card по статусу активности. Например:
qs = Card.objects.filter(
    expired_data__gt = datetime.now()
)


3. Для инстансов в Queryset применяются выбранные действия.

for instance in qs:
    do_something(instance)


Итак, наша задача - найти этот таймер. Наиболее популярное решение - celery beat с periodic tasks

Как это выглядит:

1. Ставим на проект Celery, убедимся, что task.delay() работает для тестового таска.
2. Создаём my_package/tasks.py.
3. Создадим внутри таск, который фильтрует наши неактивные карточки и выполняет на них операцию с выставлением счёта, или что там у тебя.
4. Заносим этот таск в settings.CELERY_BEAT_SCHEDULE

CELERY_BEAT_SCHEDULE = {
    'my_task_name': {
        'task': 'my_package.tasks.my_task_name',
        'schedule': 24 * 60 * 60,  # 24 hours
        'args': ()
    }
}


5. Убедимся, что celery запущена в режиме beat.

Что если мы не хотим ставить Celery? Есть решение в виде django-crontab.

1. Ставим django-crontab.
2. Создаём функцию, которая фильтрует и обрабатывает не активные карточки.
3. Заносим нашу функцию в settings.CRONJOBS

CRONJOBS = [
    ('*/5 * * * *', 'myapp.cron.my_scheduled_job')
]


4. Выполняем python manage.py crontab add

Дополнительно нам может потребоваться сохранять данные о том, какие карты игнорировать. Это можно решить элементарным добавлением поля ignore в модель и изменением правила фильтрации карточки.

Для тестирования: берём таск или функцию для крона и выполняем её в тестах. Убедимся, что операция выполнена только для не активных карточек. Не забываем использовать CELERY_ALWAYS_EAGER=True в override_settings для теста.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы