Browse Source

Django autodiscovery no longer requires an argument to work.

If `app.autodiscover_tasks()` is called without a packages argument, the
Django fixup will now take the list of modules from the app config registry.

Closes #2596
Ask Solem 9 years ago
parent
commit
e436454d02

+ 19 - 5
celery/app/base.py

@@ -348,17 +348,31 @@ class Celery(object):
         return setup_security(allowed_serializers, key, cert,
                               store, digest, serializer, app=self)
 
-    def autodiscover_tasks(self, packages, related_name='tasks', force=False):
+    def autodiscover_tasks(self, packages=None,
+                           related_name='tasks', force=False):
         if force:
             return self._autodiscover_tasks(packages, related_name)
         signals.import_modules.connect(promise(
             self._autodiscover_tasks, (packages, related_name),
         ), weak=False, sender=self)
 
-    def _autodiscover_tasks(self, packages, related_name='tasks', **kwargs):
-        # argument may be lazy
-        packages = packages() if callable(packages) else packages
-        self.loader.autodiscover_tasks(packages, related_name)
+    def _autodiscover_tasks(self, packages, related_name, **kwargs):
+        if packages:
+            return self._autodiscover_tasks_from_names(packages, related_name)
+        return self._autodiscover_tasks_from_fixups(related_name)
+
+    def _autodiscover_tasks_from_names(self, packages, related_name):
+        # packages argument can be lazy
+        return self.loader.autodiscover_tasks(
+            packages() if callable(packages) else packages, related_name,
+        )
+
+    def _autodiscover_tasks_from_fixups(self, related_name):
+        return self._autodiscover_tasks_from_names([
+            pkg for fixup in self._fixups
+                for pkg in fixup.autodiscover_tasks()
+                    if hasattr(fixup, 'autodiscover_tasks')
+        ], related_name=related_name)
 
     def send_task(self, name, args=None, kwargs=None, countdown=None,
                   eta=None, task_id=None, producer=None, connection=None,

+ 9 - 0
celery/fixups/django.py

@@ -57,6 +57,7 @@ class DjangoFixup(object):
         # Need to add project directory to path
         sys.path.append(os.getcwd())
 
+        self._settings = symbol_by_name('django.conf:settings')
         self.app.loader.now = self.now
         self.app.loader.mail_admins = self.mail_admins
 
@@ -83,6 +84,14 @@ class DjangoFixup(object):
     def mail_admins(self, subject, body, fail_silently=False, **kwargs):
         return self._mail_admins(subject, body, fail_silently=fail_silently)
 
+    def autodiscover_tasks(self):
+        try:
+            from django.apps import apps
+        except ImportError:
+            return self._settings.INSTALLED_APPS
+        else:
+            return [config.name for config in apps.get_app_configs()]
+
     @cached_property
     def _mail_admins(self):
         return symbol_by_name('django.core.mail:mail_admins')

+ 4 - 3
docs/django/first-steps-with-django.rst

@@ -92,10 +92,10 @@ autodiscover these modules:
 
 .. code-block:: python
 
-    app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
+    app.autodiscover_tasks()
 
-With the line above Celery will automatically discover tasks in reusable
-apps if you follow the ``tasks.py`` convention::
+With the line above Celery will automatically discover tasks from all
+of your installed apps, following the ``tasks.py`` convention::
 
     - app1/
         - tasks.py
@@ -104,6 +104,7 @@ apps if you follow the ``tasks.py`` convention::
         - tasks.py
         - models.py
 
+
 This way you do not have to manually add the individual modules
 to the :setting:`CELERY_IMPORTS` setting.  The ``lambda`` so that the
 autodiscovery can happen only when needed, and so that importing your

+ 4 - 2
examples/django/proj/celery.py

@@ -4,7 +4,7 @@ import os
 
 from celery import Celery
 
-from django.conf import settings
+from django.apps import apps as django_apps
 
 # set the default Django settings module for the 'celery' program.
 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings')
@@ -14,7 +14,9 @@ app = Celery('proj')
 # 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)
+
+# load task modules from all registered Django app configs.
+app.autodiscover_tasks()
 
 
 @app.task(bind=True)