How can I trigger automatic actions when a model field (e.g. date) reaches a specific time like 5 days, weekly, or yearly?
I’m working on a Django-based app where developers can connect users to their apps using an account number. Once connected, a Subscription
model is created with fields like user
, plan
, is_active
, and next_billing_date
. The next_billing_date
depends on the plan type (weekly, monthly, yearly, etc.).
I want to automatically take some action (e.g., disable is_active
or charge again) when the next_billing_date
is reached. This includes:
- Deactivating expired subscriptions
- Or triggering recurring billing based on subscription cycle (e.g. Jan 1, 2025 → Jan 1, 2026 for yearly, or weekly for others)
I already store the next billing date like this:
next_billing_date = timezone.now().date() + timedelta(days=30) # For monthly plans
I was trying to use signals to handle things like automatically charging users after 7 days or ending a subscription after 1 month. But I realized that Django signals only work when a model is saved, updated, or deleted, not based on time passing.
This means if I want something to happen exactly 7 days after a user subscribes, a signal won’t help unless something else triggers it (like the model being saved again). So even if the subscription is about to expire, Django won’t do anything automatically unless some code updates that object.
What is the best practice for implementing this kind of recurring billing logic in Django? Any suggestions, especially for time-based triggers in Django apps, would be appreciated.
But I realized that Django signals only work when a model is saved, updated, or deleted, not based on time passing.
Strictly speaking you can probably get this working with a call to an asynchronous function, and then let the processor sleep for a week before running the action.
But, that is not a good idea: if in the meantime you restart the server, the process is gone, and so are the other items involved.
You could use Celery [Celery-docs] which can run scheduled tasks, and work with a persistent database to store the tasks, and this could work, but this takes some effort to set up, and probably more importantly, while they definitely did a good job making it robust, it still is not completely reliable.
Usually you try to make the application as "passive" as possible. Meaning, you dont end a subscription, you just determine if a subscription is active when you need it.
For example:
from django.utils import timezone
class Subscription(models.Model):
end_date = models.DateTimeField()
@property
def is_active(self):
return self.end_date < timezone.now()
You can also filter Subscription
s to check if a user has an active subscription with:
from django.db.models.functions import Now
user_with_active_subscription = User.objects.filter(subscription__lte=Now())
the same when you want to generate bills: you let a management command [Django-doc] run periodically, for example with cron [wiki] that generates the bills, and maybe advance the timestamps of the active subscriptions for the next billing task. Typically you try to make such tasks robust, so you do not only generate bills for items where the billing date is the same as the current date, but also look for active subscriptions with a billing date in the past, such that if the script somehow did not run a day or two, it still can recover from that problem.
I wrote a small article [django-antipatterns] about this a few weeks ago.