Search code examples
djangocelerydjango-signals

Django signal based on the datetime field value


I'm struggling with the following. I'm trying to create a custom signal that will trigger when the current time will be equal to the value of my model's notify_on DateTimeField.

Something like this:

class Notification(models.Model):
    ...
    notify_on = models.DateTimeField()



def send_email(*args, **kwargs):
    # send email


signals.when_its_time.connect(send_email, sender=User)

After I've read through all docs and I found no information on how to implement such a signal.

Any ideas?

UPDATE: Less naive approach with ability to discard irrelevant tasks: https://stackoverflow.com/a/55337663/9631956


Solution

  • Ok, thanks to comments by @SergeyPugach I've done the following:

    Added a post_save signal that calls a function that adds a task to the celery. apply_async let's you pass eta - estimated time of arrival which can accept DateTimeField directly, that's very convenient.

    # models.py
    from django.db.models import signals
    from django.db import models
    from .tasks import send_notification
    
    class Notification(models.Model):
        ...
        notify_on = models.DateTimeField()
    
    
    def notification_post_save(instance, *args, **kwargs):
        send_notification.apply_async((instance,), eta=instance.notify_on)
    
    
    signals.post_save.connect(notification_post_save, sender=Notification)
    

    And the actual task in the tasks.py

    import logging
    from user_api.celery import app
    from django.core.mail import send_mail
    from django.template.loader import render_to_string
    
    
    @app.task
    def send_notification(self, instance):
        try:
            mail_subject = 'Your notification.'
            message = render_to_string('notify.html', {
                'title': instance.title,
                'content': instance.content
            })
            send_mail(mail_subject, message, recipient_list=[instance.user.email], from_email=None)
        except instance.DoesNotExist:
            logging.warning("Notification does not exist anymore")
    

    I will not get into details of setting up celery, there's plenty of information out there.

    Now I will try to figure out how to update the task after it's notification instance was updated, but that's a completely different story.