Want to delete registered users who have not activated their accounts after a minute. Here is the code in my signals.py:
from django.dispatch import receiver
from django.utils import timezone
from datetime import timedelta
from django.core.mail import send_mail
from .models import CustomUser
@receiver(post_save, sender=CustomUser)
def schedule_deletion(sender, instance, created, **kwargs):
if created:
print('We are going somewhere')
instance.scheduled_deletion_time = timezone.now() + timezone.timedelta(minutes=1)
print(instance.scheduled_deletion_time)
print(timezone.now())
instance.save(update_fields=['scheduled_deletion_time'])
@receiver(post_save, sender = CustomUser)
def perform_scheduled_deletion(sender,instance,*args,**kwargs):
if not instance.is_active and timezone.now() >= instance.scheduled_deletion_time:
print(timezone.now())
print(instance.is_active)
instance.delete() ```
In my apps.py, I registered the signal like this:
``` from django.apps import AppConfig
class CoreConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'core'
def ready(self):
import core.signals ```
Signals arenāt what youāre thinking they are. Theyāre not a means for you to schedule events for later. Thatās the purpose of modules like Celery Beats. Youāre not going to be able to do what you want to do here from within Django itself.
Side note: I deleted your duplicate post. Do not post the same question or issue multiple times. (See the forum FAQ.)
@giftedmatrix, while Celery is technically the correct design choice here; it might be a little bit heavy for your requirements.
Maybe a better implementation would be to have the registration process still use a signal to remove all previous attempts older than some time_frame.
This would mean that the most recent registration attempt wonāt be removed until there is yet another attempt(and this one is older than the time_frame), but it also means you wonāt need the additional infrastructure and business logic to run Celery and will only ever have a few stale registration attempts on the DB.
To answer you original question, Celery will run anywhere your app will run. Here is a basic example of someone using it to help you wrap your head around the concept.
Thank you for this. Same thing with the django-crontab. Unless you dockerize your project and unfortunately Iām not too familiar with the technology. Guess I have no choice but to dive into it.
It payoffs on the long journey.
Itās always a good idea to take a look into some boilerplate projects.
Like cookiecutter-django that setups a lot for you. If you found thatās has too much stuff, maybe look to some simple ones. I like some articles that breakdown the process so you understand it better.
These are some good articles that may help you on this journey: Dockerizing Django and Celery;
Without celery: Dockerizing Django with Postgres, Gunicorn, and Nginx | TestDriven.io
But remember: Each decision you make its a tradeoff.
Sometimes itās better to keep it simple until you need more complexity, control. @gcain answer is also really handy, and way easier to implement.
Opinion:
I donāt like signals, they are not always remembered when you check for logic. I prefer creating a function that does all the logic of saving, creating more stuff as needed, on a services.py file. This was taken from HackSoftware style guide, itās really worth the read.
I dockerized my django project so I could use the django-crontab module. Iām trying to delete users that are inactive after a minute from registration and also send emails to users reminding them to subscribe every month. Both functionalities are not working. I initiated the crontab using āādocker exec -ti django-cron python manage.py crontab addāā. The ādjango-crontabā is listed in my Installed apps in the settings.py file and also in the requirements.txt. Please Iād really appreciate being shown what Iām not getting right. These are the relevant files and settings:
SIGNALS.PY
from django.dispatch import receiver
from django.utils import timezone
from datetime import timedelta
from django.core.mail import send_mail
from .models import CustomUser
@receiver(post_save, sender=CustomUser)
def schedule_deletion(sender, instance, created, **kwargs):
if created and not instance.is_active:
print('We are going somewhere')
instance.scheduled_deletion_time = timezone.now() + timezone.timedelta(minutes=1)
print(instance.scheduled_deletion_time)
print(instance.is_active)
CustomUser.objects.filter(email=instance.email).update(scheduled_deletion_time=instance.scheduled_deletion_time)
if instance.is_active:
instance.scheduled_deletion_time = None
CustomUser.objects.filter(email=instance.email).update(scheduled_deletion_time=instance.scheduled_deletion_time)
@receiver(post_save, sender=CustomUser)
def send_subscription_reminder(sender, instance, created, **kwargs):
if created:
#Set the last reminder sent time to the current time
instance.last_reminder_sent = timezone.now()
instance.save(update_fields=['last_reminder_sent'])
#Check if it's been 30 days since the last reminder was sent
if instance.last_reminder_sent is None or (timezone.now() - instance.last_reminder_sent) >= timedelta(days=30):
#Check if the user has not paid this month
if not instance.paid_for_the_month:
# Send a reminder email
send_mail(
'Subscription Reminder',
'Hello {},\n\nThis is a reminder that your subscription is due. Please make your payment as soon as possible.\n\nThank you for using our service!'.format(instance.first_name),
'techforjonah@gmail.com',
[instance.email],
fail_silently=False,
)
#Update the last reminder sent time
instance.last_reminder_sent = timezone.now()
instance.save()```
CRON.PY
from datetime import timedelta
from django.utils import timezone
from core.models import CustomUser
from core.signals import send_subscription_reminder
def send_sub_reminder():
# Calculate the date 28 days ago from today
twenty_eight_days_ago = timezone.now() - timedelta(days=28)
# Get all users who haven't paid and haven't received a reminder in the last 28 days
users_to_remind = CustomUser.objects.filter(has_paid_this_month=False, last_reminder_sent__lt=twenty_eight_days_ago)
# Loop through the users and send them a reminder
for user in users_to_remind:
send_subscription_reminder(sender=CustomUser, instance=user)
return None
def delete_inactive_user():
users = CustomUser.objects.filter(is_active=False)
for user in users:
if timezone.now() >= user.scheduled_deletion_time:
user.delete()
return None
SETTINGS.PY
CRONJOBS = [
('0 0 1 * *', 'core.cron.send_sub_reminder'),
('* * * * *', 'core.cron.delete_inactive_user')
]
DOCKERFILE
FROM python:3.10.5-alpine
# install django-crontab
RUN apk add --update apk-cron && rm -rf /var/cache/apk/*
RUN alias py=python
#set your working directory
WORKDIR /usr/src/dormafrica
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# install psycopg2 dependencies
RUN apk update \
&& apk add postgresql-dev gcc python3-dev musl-dev
COPY ../dormafrica .
COPY ./requirements.txt .
RUN pip install -r requirements.txt
# django-crontab logfile
RUN mkdir /cron
RUN touch /cron/django_cron.log
EXPOSE 8000
CMD service cron start && python manage.py runserver 0.0.0.0:8000
DOCKER-COMPOSE.YML
version: "3.9"
services:
webapp:
build: ../dormafrica
command: python manage.py runserver 0.0.0.0:8000
container_name: django-cron
volumes:
- ../dormafrica:/usr/src/dormafrica
env_file: ./.env
ports:
- 8000:8000
depends_on:
- db
db:
image: postgres:13.0-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
environment:
- POSTGRES_DB=****
- POSTGRES_USER=****
- POSTGRES_PASSWORD=****
volumes:
postgres_data:
The django-crontab project is seriously out-of-date. I wouldnāt suggest using it without some careful investigation.
How critical is it that the deletion occurs roughly 60 seconds after creation? What if itās 65 seconds? Is that ok? (In other words, what is the actual limit required here?)
I agree with one of the earlier suggestions - Iād check to see if the activation occurs within 60 seconds. If it does, great. If it doesnāt, then delete the account. If they donāt even attempt to activate the account, then the account can wait until the scheduled task executes. I would only do a āhard checkā to delete the accounts on a scheduled basis perhaps once every 5 minutes. Thereās probably no real need to do it any more often than that.
(And I would absolutely remove all signal-based logic from this process. Itās not helping you here.)