6

I'm trying to learn how to use celery to check a date every day on one of my models. One of my models holds an expiration date and a boolean field that says whether their insurance is expired or not.

The model is pretty big so I'm going to post a condensed version. I think I have two options. Either run a celery task on the model method or rewrite the function in my tasks.py. Then I need to use Celery beat to run the schedule to check daily.

I've got the function to work, but I'm passing in the model objects directly which I think is wrong.

I'm also having trouble on how to use the args in the celery beat scheduler inside of my celery.py.

I'm really close to getting this to work, but I think I'm going about executing a task in the wrong way. and I think executing a task on the model method might be the cleanest, I'm just not sure how to accomplish that.

models.py

class CarrierCompany(models.Model):
    name = models.CharField(max_length=255, unique=True)
    insurance_expiration = models.DateTimeField(null=True)
    insurance_active = models.BooleanField()

    def insurance_expiration_check(self):
        if self.insurance_expiration > datetime.today().date():
            self.insurance_active = True
            self.save()
            print("Insurance Active")
        else:
            self.insurance_active = False
            self.save()
            print("Insurance Inactive")

tasks.py

from __future__ import absolute_import, unicode_literals
from celery.decorators import task
from datetime import datetime, date
from django.utils import timezone
from .models import CarrierCompany



@task(name="insurance_expired")
def insurance_date():
    carriers = CarrierCompany.objects.all()
    for carrier in carriers:
        date = datetime.now(timezone.utc)
        if carrier.insurance_expiration > date:
            carrier.insurance_active = True
            carrier.save()
            print("Insurance Active")
        else:
            carrier.insurance_active = False
            carrier.save()
            print("Insurance Inactive")

celery.py

from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
from celery.schedules import crontab

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.local')

app = Celery('POTRTMS')

app.config_from_object('django.conf:settings', namespace='CELERY')

app.autodiscover_tasks()


@app.task(bind=True)
def debug_task(self):
    print('Request: {0!r}'.format(self.request))


app.conf.beat_schedule = {
    'check-insurance-daily': {
        'task': 'insurance_expired',
        'schedule': crontab(hour='8')
    },
}

*** updated the beat schedule to reflect when I actually want to run it.

Kris Tryber
  • 227
  • 1
  • 8
  • 21
  • wow, I was using the wrong settings file so my task wasn't able to access the database. I've got that fixed and I'm updating my code here. I still think i'm calling my model objects wrong though. – Kris Tryber Mar 05 '18 at 18:57
  • Hey, just so I understand, you want to pass carriers to the function? Bc you have it get the objects inside, which is fine, so there's no need to pass any value to the function. Also, I would use celery-beat bc its pretty easy to setup and just works. Within the model, if you define that function as a property, when looping carriers, you can call carrier.insurance_expiration_check. I can give a more in depth answer if I am on track, or if you want to go another way, I can try and help too... – CoolestNerdIII Mar 05 '18 at 19:55
  • @CoolestNerdIII whoops, yes ok you're definitely on the right track. I've removed passing "carriers" into the function and this now works as intended. 1. Now my question, is it ok to pass the model objects like that into the function while using celery, it sounds like it's ok. 2. The second part sounds like a better way to write the code. Calling it within the model. That's where I really get lost with celery and celery beat. – Kris Tryber Mar 05 '18 at 21:00
  • I guess I thought I was already using celery-beat by using the app.conf.beat_schedule in my celery.py – Kris Tryber Mar 05 '18 at 21:16
  • I may have to just give an answer with an example, but yes calling it this way is fine. The problem would arise if you called that function passing an object instead of an ID (you are now passing nothing so you're good). I will right up an example in the answer because there's not enough space here. – CoolestNerdIII Mar 05 '18 at 22:15
  • Ok, the reading that I've been doing has been saying that passing the model object into the function is a bad idea. In this case, I'm not doing that, I'm running a function that retrieves the model objects after the function is called which is ok. I think I'm getting this. Thanks for the help! – Kris Tryber Mar 05 '18 at 22:18

1 Answers1

4

An example for how I might do it would be as follows. Also, instead of using traditional datetime, if you are including timezones in your Django App, you will probably want to use the timezone library instead found here.

models.py

class CarrierCompany(models.Model):
    ...

    @property
    def is_insurance_expired(self):
        from django.utils import timezone
        if self.insurance_expiration > timezone.datetime.today():
            print("Insurance Active")
            return True
        else:
            print("Insurance Active")
            return False

tasks.py

def insurance_date():
    carriers = CarrierCompany.objects.all()
    for carrier in carriers:
        if carrier.is_insurance_expired:
            carrier.insurance_active = True
            carrier.save()
        else:
            carrier.insurance_active = False
            carrier.save()

There are other things you could do, like not update it if its False and make the default False, or vice versa in the True case. You could also just do all of that in the models function, though I personally like to keep logic a bit separate (just how I organize my stuff). Hopefully this helps get you past where you are stuck though.

CoolestNerdIII
  • 770
  • 4
  • 10
  • ok there we go, I like this a lot better. Now the task is being run based on the function within the model. That's more of what I was trying to accomplish. On the timezone. using what I have is the only way I could avoid this error: TypeError: can't compare offset-naive and offset-aware datetimes – Kris Tryber Mar 06 '18 at 01:57
  • @KrisTryber You get a `TypeError: can't compare offset-naive and offset-aware datetimes` because you're trying to compare a datetime object w/ timezone informations with a datetime object w/o timezone informations. You might want to make your naive datetime object timezone-aware by using `django.utils.timezone.make_aware()` https://docs.djangoproject.com/en/2.0/ref/utils/#django.utils.timezone.make_aware – Dominique Barton Apr 17 '18 at 13:23