moku
moku

Reputation: 4249

How to add custom model to django_celery

I'm working on making celery suitable for high availability I've forked the django_celery project and this fork of celery in order to make the customizations that I need. The celery link shows the modifications to the beat.py that utilizes the following code:

I've added this Lock model to django_celery models.py file and was able to migrate just fine:

from django.db import models

@python_2_unicode_compatible
class Lock(models.Model):
    name = models.CharField(max_length=127, unique=True)
    created = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name_plural = _('locks')

    def __str__(self):
        return self.name

In celery in the utils folder I've added this locked.py file:

from djcelery.models import Lock
from datetime import datetime, timedelta
from django.db import transaction, IntegrityError


class Locked(object):
    """A context manager to add a distributed mutex."""

    def __init__(self, name, timeout):
        self.name = name
        self.lock = None
        self.timeout = timeout

    def __enter__(self):
        # first delete any expired locks
        expired = datetime.utcnow() - timedelta(seconds=self.timeout)
        Lock.objects.filter(name=self.name, created__lte=expired).delete()
        # then try to get the lock
        try:
            Lock(name=self.name).save()
        except IntegrityError:
            transaction.rollback()
            raise LockError('Could not acquire lock: {0}'.format(self.name))

    def __exit__(self, *args):
        Lock.objects.filter(name=self.name).delete()


class LockError(Exception):
    """Exception thrown when the requested lock already exists."""

    pass

with these changes I'm able to run the following commands:

celery worker
python manage.py runserver
python manage.py shell

I issues arrises when I try to run the scheduler:

celery beat

I get the following error:

Traceback (most recent call last):
  File "venv/bin/celery", line 11, in <module>
    load_entry_point('celery', 'console_scripts', 'celery')()
  File "/venv/src/celery/celery/__main__.py", line 30, in main
    main()
  File "/venv/src/celery/celery/bin/celery.py", line 81, in main
    cmd.execute_from_commandline(argv)
  File "/venv/src/celery/celery/bin/celery.py", line 793, in execute_from_commandline
    super(CeleryCommand, self).execute_from_commandline(argv)))
  File "/venv/src/celery/celery/bin/base.py", line 311, in execute_from_commandline
    return self.handle_argv(self.prog_name, argv[1:])
  File "/venv/src/celery/celery/bin/celery.py", line 785, in handle_argv
    return self.execute(command, argv)
  File "/venv/src/celery/celery/bin/celery.py", line 717, in execute
    ).run_from_argv(self.prog_name, argv[1:], command=argv[0])
  File "/venv/src/celery/celery/bin/base.py", line 315, in run_from_argv
    sys.argv if argv is None else argv, command)
  File "/venv/src/celery/celery/bin/base.py", line 377, in handle_argv
    return self(*args, **options)
  File "/venv/src/celery/celery/bin/base.py", line 274, in __call__
    ret = self.run(*args, **kwargs)
  File "/venv/src/celery/celery/bin/beat.py", line 72, in run
    beat = partial(self.app.Beat,
  File "/venv/lib/python2.7/site-packages/kombu/utils/__init__.py", line 325, in __get__
    value = obj.__dict__[self.__name__] = self.__get(obj)
  File "/venv/src/celery/celery/app/base.py", line 572, in Beat
    return self.subclass_with_self('celery.apps.beat:Beat')
  File "/venv/src/celery/celery/app/base.py", line 504, in subclass_with_self
    Class = symbol_by_name(Class)
  File "/venv/lib/python2.7/site-packages/kombu/utils/__init__.py", line 96, in symbol_by_name
    module = imp(module_name, package=package, **kwargs)
  File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
  File "/venv/src/celery/celery/apps/beat.py", line 19, in <module>
    from celery import VERSION_BANNER, platforms, beat
  File "/venv/src/celery/celery/beat.py", line 35, in <module>
    from .utils.locked import Locked, LockError
  File "/venv/src/celery/celery/utils/locked.py", line 1, in <module>
    from djcelery.models import Lock
  File "/venv/src/django-celery/djcelery/models.py", line 30, in <module>
    class TaskMeta(models.Model):
  File "/venv/lib/python2.7/site-packages/django/db/models/base.py", line 105, in __new__
    app_config = apps.get_containing_app_config(module)
  File "/venv/lib/python2.7/site-packages/django/apps/registry.py", line 237, in get_containing_app_config
    self.check_apps_ready()
  File "/venv/lib/python2.7/site-packages/django/apps/registry.py", line 124, in check_apps_ready
    raise AppRegistryNotReady("Apps aren't loaded yet.")
django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.

I have djcelery in my INSTALLED_APPS setting so I don't know what is going on at this point?

Upvotes: 1

Views: 301

Answers (2)

Dmitry Shilyaev
Dmitry Shilyaev

Reputation: 733

You must specify app instance to use for the celery command

-A APP, --app=APP app instance to use (e.g. module.attr_name)

For example if i have structure

pybilling
- pybilling
  - celeryconfig.py

then i should start celery beat with the command

celery --app pybilling.celeryconfig:app beat

Here is the contents of celeryconfig.py

from __future__ import absolute_import

import os

from celery import Celery

# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pybilling.settings')

from django.conf import settings  # noqa

app = Celery('pybilling')

# Using a string here means the worker will not have to
# pickle the object when using Windows.
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

Upvotes: 1

Louis
Louis

Reputation: 151451

Celery is often used with Django and is compatible with Django but is is not inherently a Django application. The modifications you've made are such that when you run celery beat, Django models are loaded. In order to be able to use the models, the applications must be initialized first. The standard way to do this is to call django.setup() after having set things so that Django's code can find the Django settings. It could be something like:

import os
import django

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
django.setup()

You need to change project.settings to the actual module name that contains the settings.

Upvotes: 0

Related Questions