From bb8ec71f61066587aa3a8feb0aedab213422775a Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Tue, 24 Dec 2013 23:43:47 +0100 Subject: [PATCH] Updated the AppCommand API to support apps without a models module. --- django/core/management/base.py | 51 ++++++++++++++--------- docs/howto/custom-management-commands.txt | 33 +++++++++++---- docs/internals/deprecation.txt | 2 + docs/releases/1.7.txt | 4 ++ 4 files changed, 63 insertions(+), 27 deletions(-) diff --git a/django/core/management/base.py b/django/core/management/base.py index 5c3a1a6c8c..59224049d2 100644 --- a/django/core/management/base.py +++ b/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 ''. + a list of application names might set this to ''. ``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 = '' + args = '' 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): diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index 2325e32ac2..4f95838760 100644 --- a/docs/howto/custom-management-commands.txt +++ b/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(app, **options) +.. method:: AppCommand.handle_app_config(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``, 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:: + + 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. + + However, you may be able to simplify the implementation by using directly + the attributes of ``app_config``. .. class:: LabelCommand diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 29a68ff0c1..29f7df53ae 100644 --- a/docs/internals/deprecation.txt +++ b/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. diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 5aba3ca096..2c3e3f217d 100644 --- a/docs/releases/1.7.txt +++ b/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.