Browse Source

Updated the AppCommand API to support apps without a models module.

Aymeric Augustin 11 years ago
parent
commit
bb8ec71f61

+ 32 - 19
django/core/management/base.py

@@ -7,6 +7,7 @@ from __future__ import unicode_literals
 
 import os
 import sys
+import warnings
 
 from optparse import make_option, OptionParser
 
@@ -112,8 +113,8 @@ class BaseCommand(object):
     ``args``
         A string listing the arguments accepted by the command,
         suitable for use in help messages; e.g., a command which takes
-        a list of application names might set this to '<appname
-        appname ...>'.
+        a list of application names might set this to '<app_label
+        app_label ...>'.
 
     ``can_import_settings``
         A boolean indicating whether the command needs to be able to
@@ -331,19 +332,18 @@ class BaseCommand(object):
 
 class AppCommand(BaseCommand):
     """
-    A management command which takes one or more installed application
-    names as arguments, and does something with each of them.
+    A management command which takes one or more installed application labels
+    as arguments, and does something with each of them.
 
     Rather than implementing ``handle()``, subclasses must implement
-    ``handle_app()``, which will be called once for each application.
-
+    ``handle_app_config()``, which will be called once for each application.
     """
-    args = '<appname appname ...>'
+    args = '<app_label app_label ...>'
 
     def handle(self, *app_labels, **options):
         from django.apps import apps
         if not app_labels:
-            raise CommandError('Enter at least one appname.')
+            raise CommandError("Enter at least one application label.")
         # Populate models and don't use only_with_models_module=True when
         # calling get_app_config() to tell apart missing apps from apps
         # without a model module -- which can't be supported with the legacy
@@ -355,23 +355,36 @@ class AppCommand(BaseCommand):
             raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e)
         output = []
         for app_config in app_configs:
-            if app_config.models_module is None:
-                raise CommandError(
-                    "AppCommand cannot handle app %r because it doesn't have "
-                    "a models module." % app_config.label)
-            app_output = self.handle_app(app_config.models_module, **options)
+            app_output = self.handle_app_config(app_config, **options)
             if app_output:
                 output.append(app_output)
         return '\n'.join(output)
 
-    def handle_app(self, app, **options):
+    def handle_app_config(self, app_config, **options):
         """
-        Perform the command's actions for ``app``, which will be the
-        Python module corresponding to an application name given on
-        the command line.
-
+        Perform the command's actions for app_config, an AppConfig instance
+        corresponding to an application label given on the command line.
         """
-        raise NotImplementedError('subclasses of AppCommand must provide a handle_app() method')
+        try:
+            # During the deprecation path, keep delegating to handle_app if
+            # handle_app_config isn't implemented in a subclass.
+            handle_app = self.handle_app
+        except AttributeError:
+            # Keep only this exception when the deprecation completes.
+            raise NotImplementedError(
+                "Subclasses of AppCommand must provide"
+                "a handle_app_config() method.")
+        else:
+            warnings.warn(
+                "AppCommand.handle_app() is superseded by "
+                "AppCommand.handle_app_config().",
+                PendingDeprecationWarning, stacklevel=2)
+            if app_config.models_module is None:
+                raise CommandError(
+                    "AppCommand cannot handle app '%s' in legacy mode "
+                    "because it doesn't have a models module."
+                    % app_config.label)
+            return handle_app(app_config.models_module, **options)
 
 
 class LabelCommand(BaseCommand):

+ 25 - 8
docs/howto/custom-management-commands.txt

@@ -313,17 +313,34 @@ BaseCommand subclasses
 
 .. class:: AppCommand
 
-A management command which takes one or more installed application
-names as arguments, and does something with each of them.
+A management command which takes one or more installed application labels as
+arguments, and does something with each of them.
 
-Rather than implementing :meth:`~BaseCommand.handle`, subclasses must implement
-:meth:`~AppCommand.handle_app`, which will be called once for each application.
+Rather than implementing :meth:`~BaseCommand.handle`, subclasses must
+implement :meth:`~AppCommand.handle_app_config`, which will be called once for
+each application.
+
+.. method:: AppCommand.handle_app_config(app_config, **options)
+
+    Perform the command's actions for ``app_config``, which will be an
+    :class:`~django.apps.AppConfig` instance corresponding to an application
+    label given on the command line.
+
+.. versionchanged:: 1.7
+
+    Previously, :class:`AppCommand` subclasses had to implement
+    ``handle_app(app, **options)`` where ``app`` was a models module. The new
+    API makes it possible to handle applications without a models module. The
+    fastest way to migrate is as follows::
 
-.. method:: AppCommand.handle_app(app, **options)
+        def handle_app_config(app_config, **options):
+            if app_config.models_module is None:
+                return                                  # Or raise an exception.
+            app = app_config.models_module
+            # Copy the implementation of handle_app(app_config, **options) here.
 
-    Perform the command's actions for ``app``, which will be the
-    Python module corresponding to an application name given on
-    the command line.
+    However, you may be able to simplify the implementation by using directly
+    the attributes of ``app_config``.
 
 .. class:: LabelCommand
 

+ 2 - 0
docs/internals/deprecation.txt

@@ -179,6 +179,8 @@ these changes.
 
 * The model and form ``IPAddressField`` will be removed.
 
+* ``AppCommand.handle_app()`` will no longer be supported.
+
 * FastCGI support via the ``runfcgi`` management command will be
   removed. Please deploy your project using WSGI.
 

+ 4 - 0
docs/releases/1.7.txt

@@ -593,6 +593,10 @@ methods are only referring to fields or other items in ``model._meta``.
 App-loading changes
 ~~~~~~~~~~~~~~~~~~~
 
+Subclasses of :class:`~django.core.management.AppCommand` must now implement a
+:meth:`~django.core.management.AppCommand.handle_app_config` method instead of
+``handle_app()``. This method receives an :class:`~django.apps.AppConfig` instance.
+
 Since :setting:`INSTALLED_APPS` now supports application configuration classes
 in addition to application modules, you should review code that accesses this
 setting directly and use the app registry (:attr:`django.apps.apps`) instead.