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

This commit is contained in:
Aymeric Augustin 2013-12-24 23:43:47 +01:00
parent aff57793b4
commit bb8ec71f61
4 changed files with 63 additions and 27 deletions

View File

@ -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):

View File

@ -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(app, **options) .. method:: AppCommand.handle_app_config(app_config, **options)
Perform the command's actions for ``app``, which will be the Perform the command's actions for ``app_config``, which will be an
Python module corresponding to an application name given on :class:`~django.apps.AppConfig` instance corresponding to an application
the command line. 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 .. class:: LabelCommand

View File

@ -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.

View File

@ -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.