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 os
 import sys
 import sys
+import warnings
 
 
 from optparse import make_option, OptionParser
 from optparse import make_option, OptionParser
 
 
@@ -112,8 +113,8 @@ class BaseCommand(object):
     ``args``
     ``args``
         A string listing the arguments accepted by the command,
         A string listing the arguments accepted by the command,
         suitable for use in help messages; e.g., a command which takes
         suitable for use in help messages; e.g., a command which takes
-        a list of application names might set this to '<appname
+        a list of application names might set this to '<app_label
-        appname ...>'.
+        app_label ...>'.
 
 
     ``can_import_settings``
     ``can_import_settings``
         A boolean indicating whether the command needs to be able to
         A boolean indicating whether the command needs to be able to
@@ -331,19 +332,18 @@ class BaseCommand(object):
 
 
 class AppCommand(BaseCommand):
 class AppCommand(BaseCommand):
     """
     """
-    A management command which takes one or more installed application
+    A management command which takes one or more installed application labels
-    names as arguments, and does something with each of them.
+    as arguments, and does something with each of them.
 
 
     Rather than implementing ``handle()``, subclasses must implement
     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):
     def handle(self, *app_labels, **options):
         from django.apps import apps
         from django.apps import apps
         if not app_labels:
         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
         # Populate models and don't use only_with_models_module=True when
         # calling get_app_config() to tell apart missing apps from apps
         # calling get_app_config() to tell apart missing apps from apps
         # without a model module -- which can't be supported with the legacy
         # 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)
             raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e)
         output = []
         output = []
         for app_config in app_configs:
         for app_config in app_configs:
-            if app_config.models_module is None:
+            app_output = self.handle_app_config(app_config, **options)
-                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)
             if app_output:
             if app_output:
                 output.append(app_output)
                 output.append(app_output)
         return '\n'.join(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
+        Perform the command's actions for app_config, an AppConfig instance
-        Python module corresponding to an application name given on
+        corresponding to an application label given on the command line.
-        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):
 class LabelCommand(BaseCommand):

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

@@ -313,17 +313,34 @@ BaseCommand subclasses
 
 
 .. class:: AppCommand
 .. class:: AppCommand
 
 
-A management command which takes one or more installed application
+A management command which takes one or more installed application labels as
-names as arguments, and does something with each of them.
+arguments, and does something with each of them.
 
 
-Rather than implementing :meth:`~BaseCommand.handle`, subclasses must implement
+Rather than implementing :meth:`~BaseCommand.handle`, subclasses must
-:meth:`~AppCommand.handle_app`, which will be called once for each application.
+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
+    However, you may be able to simplify the implementation by using directly
-    Python module corresponding to an application name given on
+    the attributes of ``app_config``.
-    the command line.
 
 
 .. class:: LabelCommand
 .. class:: LabelCommand
 
 

+ 2 - 0
docs/internals/deprecation.txt

@@ -179,6 +179,8 @@ these changes.
 
 
 * The model and form ``IPAddressField`` will be removed.
 * 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
 * FastCGI support via the ``runfcgi`` management command will be
   removed. Please deploy your project using WSGI.
   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
 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
 Since :setting:`INSTALLED_APPS` now supports application configuration classes
 in addition to application modules, you should review code that accesses this
 in addition to application modules, you should review code that accesses this
 setting directly and use the app registry (:attr:`django.apps.apps`) instead.
 setting directly and use the app registry (:attr:`django.apps.apps`) instead.