M
M
MickiMouse2019-08-28 00:56:23
Django
MickiMouse, 2019-08-28 00:56:23

Django. How to catch a change in a model field?

Greetings. There is a `Card` model, it has a `DateTimeField` type field, it is called expired_date, it has its own price and status. There is a user who pays for services. The default card status is `Inacitve`. If the user buys a card, then its status will become `Active`, and expired_date will contain a date one day more than the current one. When the subscription expires, i.e. the current date becomes greater than expired_date, the card status will be set to `Inactive`. It is required to automatically immediately pay for the subscription in order for the card to become active. There is a signal that makes a payment, but I have no idea where to put it in order for it to work automatically, without user intervention. Googled in the direction of `django crontab`, `django thread`, `django schedule`, 'celery' but found nothing useful. That is, it is necessary that without user participation (no requests, etc.) ) triggered a signal. Tell me how to do it? Or in what direction to google? Foreign sources are accepted.
Card status - functional field

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'

Answer the question

In order to leave comments, you need to log in

1 answer(s)
W
werevolff, 2019-08-28
@MickiMouse

There are two popular solutions to the problem here. But first, let's look at the architecture.
1. We have a certain timer that runs a python function with some frequency.
2. This function filters Card by activity status. For example:

qs = Card.objects.filter(
    expired_data__gt = datetime.now()
)

3. The selected actions are applied to the instances in the Queryset.
for instance in qs:
    do_something(instance)

So, our task is to find this timer. The most popular solution is celery beat with periodic tasks
How it looks like:
1. Put on the Celery project, make sure that task.delay() works for the test task.
2. Create my_package/tasks.py.
3. Let's create a task inside that filters our inactive cards and performs an operation on them with invoicing, or whatever you have.
4. Enter this task in 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. Make sure celery is running in beat mode.
What if we don't want to install Celery? There is a solution in the form of django-crontab .
1. Install django-crontab.
2. Create a function that filters and processes inactive cards.
3. We add our function to settings.CRONJOBS
CRONJOBS = [
    ('*/5 * * * *', 'myapp.cron.my_scheduled_job')
]

4. Execute python manage.py crontab add
Additionally, we may need to save data about which cards to ignore. This can be solved by simply adding the ignore field to the model and changing the card filtering rule.
For testing: we take a task or a function for cron and execute it in tests. Make sure that the operation is performed only for inactive cards. Don't forget to use CELERY_ALWAYS_EAGER=True in override_settings for testing.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question