From 83f5f700b0a3ac885042a7f1e68f4e7616d1485e Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 16 Aug 2007 14:34:01 +0000 Subject: [PATCH] Improved error handling for management.py commands, especially for no argument or non-applabel argument commands. git-svn-id: http://code.djangoproject.com/svn/django/trunk@5903 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/management/base.py | 112 +++++++++++------- .../management/commands/createcachetable.py | 7 +- django/core/management/commands/dbshell.py | 6 +- .../core/management/commands/diffsettings.py | 6 +- django/core/management/commands/flush.py | 6 +- django/core/management/commands/inspectdb.py | 6 +- django/core/management/commands/runserver.py | 4 +- django/core/management/commands/shell.py | 6 +- django/core/management/commands/sqlflush.py | 6 +- django/core/management/commands/startapp.py | 13 +- .../core/management/commands/startproject.py | 9 +- django/core/management/commands/syncdb.py | 8 +- django/core/management/commands/validate.py | 6 +- 13 files changed, 116 insertions(+), 79 deletions(-) diff --git a/django/core/management/base.py b/django/core/management/base.py index b0a9de5b86..4041787bc3 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -65,6 +65,7 @@ class BaseCommand(object): def handle(self, *args, **options): raise NotImplementedError() + class AppCommand(BaseCommand): args = '[appname ...]' @@ -86,46 +87,77 @@ class AppCommand(BaseCommand): def handle_app(self, app, **options): raise NotImplementedError() -class CopyFilesCommand(BaseCommand): - requires_model_validation = False - def copy_helper(self, app_or_project, name, directory, other_name=''): - import django - import os - import re - import shutil - other = {'project': 'app', 'app': 'project'}[app_or_project] - if not re.search(r'^\w+$', name): # If it's not a valid directory name. - raise CommandError("%r is not a valid %s name. Please use only numbers, letters and underscores." % (name, app_or_project)) - top_dir = os.path.join(directory, name) - try: - os.mkdir(top_dir) - except OSError, e: - raise CommandError(e) +class LabelCommand(BaseCommand): + args = '[label ...]' + label = 'label' + + def handle(self, *labels, **options): + if not labels: + raise CommandError('Enter at least one %s.' % self.label) - # Determine where the app or project templates are. Use - # django.__path__[0] because we don't know into which directory - # django has been installed. - template_dir = os.path.join(django.__path__[0], 'conf', '%s_template' % app_or_project) + output = [] + for label in labels: + label_output = self.handle_label(label, **options) + if label_output: + output.append(label_output) + return '\n'.join(output) - for d, subdirs, files in os.walk(template_dir): - relative_dir = d[len(template_dir)+1:].replace('%s_name' % app_or_project, name) - if relative_dir: - os.mkdir(os.path.join(top_dir, relative_dir)) - for i, subdir in enumerate(subdirs): - if subdir.startswith('.'): - del subdirs[i] - for f in files: - if f.endswith('.pyc'): - continue - path_old = os.path.join(d, f) - path_new = os.path.join(top_dir, relative_dir, f.replace('%s_name' % app_or_project, name)) - fp_old = open(path_old, 'r') - fp_new = open(path_new, 'w') - fp_new.write(fp_old.read().replace('{{ %s_name }}' % app_or_project, name).replace('{{ %s_name }}' % other, other_name)) - fp_old.close() - fp_new.close() - try: - shutil.copymode(path_old, path_new) - except OSError: - sys.stderr.write(self.style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new)) + def handle_label(self, label, **options): + raise NotImplementedError() + + +class NoArgsCommand(BaseCommand): + args = '' + + def handle(self, *args, **options): + from django.db import models + if len(args) != 0: + raise CommandError("Command doesn't accept any arguments") + + return self.handle_noargs(**options) + + def handle_noargs(self, **options): + raise NotImplementedError() + + +def copy_helper(app_or_project, name, directory, other_name=''): + import django + import os + import re + import shutil + other = {'project': 'app', 'app': 'project'}[app_or_project] + if not re.search(r'^\w+$', name): # If it's not a valid directory name. + raise CommandError("%r is not a valid %s name. Please use only numbers, letters and underscores." % (name, app_or_project)) + top_dir = os.path.join(directory, name) + try: + os.mkdir(top_dir) + except OSError, e: + raise CommandError(e) + + # Determine where the app or project templates are. Use + # django.__path__[0] because we don't know into which directory + # django has been installed. + template_dir = os.path.join(django.__path__[0], 'conf', '%s_template' % app_or_project) + + for d, subdirs, files in os.walk(template_dir): + relative_dir = d[len(template_dir)+1:].replace('%s_name' % app_or_project, name) + if relative_dir: + os.mkdir(os.path.join(top_dir, relative_dir)) + for i, subdir in enumerate(subdirs): + if subdir.startswith('.'): + del subdirs[i] + for f in files: + if f.endswith('.pyc'): + continue + path_old = os.path.join(d, f) + path_new = os.path.join(top_dir, relative_dir, f.replace('%s_name' % app_or_project, name)) + fp_old = open(path_old, 'r') + fp_new = open(path_new, 'w') + fp_new.write(fp_old.read().replace('{{ %s_name }}' % app_or_project, name).replace('{{ %s_name }}' % other, other_name)) + fp_old.close() + fp_new.close() + try: + shutil.copymode(path_old, path_new) + except OSError: + sys.stderr.write(self.style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new)) diff --git a/django/core/management/commands/createcachetable.py b/django/core/management/commands/createcachetable.py index df1812174a..dc8b1b7854 100644 --- a/django/core/management/commands/createcachetable.py +++ b/django/core/management/commands/createcachetable.py @@ -1,12 +1,13 @@ -from django.core.management.base import BaseCommand +from django.core.management.base import LabelCommand -class Command(BaseCommand): +class Command(LabelCommand): help = "Creates the table needed to use the SQL cache backend." args = "[tablename]" + label = 'tablename' requires_model_validation = False - def handle(self, tablename, **options): + def handle_label(self, tablename, **options): from django.db import backend, connection, transaction, models fields = ( # "key" is a reserved word in MySQL, so use "cache_key" instead. diff --git a/django/core/management/commands/dbshell.py b/django/core/management/commands/dbshell.py index 401ed847b0..ec2a961530 100644 --- a/django/core/management/commands/dbshell.py +++ b/django/core/management/commands/dbshell.py @@ -1,10 +1,10 @@ -from django.core.management.base import BaseCommand +from django.core.management.base import NoArgsCommand -class Command(BaseCommand): +class Command(NoArgsCommand): help = "Runs the command-line client for the current DATABASE_ENGINE." requires_model_validation = False - def handle(self, **options): + def handle_noargs(self, **options): from django.db import runshell runshell() diff --git a/django/core/management/commands/diffsettings.py b/django/core/management/commands/diffsettings.py index a51bddc477..2459f11700 100644 --- a/django/core/management/commands/diffsettings.py +++ b/django/core/management/commands/diffsettings.py @@ -1,17 +1,17 @@ -from django.core.management.base import BaseCommand +from django.core.management.base import NoArgsCommand def module_to_dict(module, omittable=lambda k: k.startswith('_')): "Converts a module namespace to a Python dictionary. Used by get_settings_diff." return dict([(k, repr(v)) for k, v in module.__dict__.items() if not omittable(k)]) -class Command(BaseCommand): +class Command(NoArgsCommand): help = """Displays differences between the current settings.py and Django's default settings. Settings that don't appear in the defaults are followed by "###".""" requires_model_validation = False - def handle(self, **options): + def handle_noargs(self, **options): # Inspired by Postfix's "postconf -n". from django.conf import settings, global_settings diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index c6e902b946..bd1dc204d2 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -1,11 +1,11 @@ -from django.core.management.base import BaseCommand, CommandError +from django.core.management.base import NoArgsCommand, CommandError from django.core.management.color import no_style -class Command(BaseCommand): +class Command(NoArgsCommand): help = "Executes ``sqlflush`` on the current database." args = '[--verbosity] [--noinput]' - def handle(self, **options): + def handle_noargs(self, **options): from django.conf import settings from django.db import connection, transaction, models from django.dispatch import dispatcher diff --git a/django/core/management/commands/inspectdb.py b/django/core/management/commands/inspectdb.py index 6f28b6f980..11bc390289 100644 --- a/django/core/management/commands/inspectdb.py +++ b/django/core/management/commands/inspectdb.py @@ -1,11 +1,11 @@ -from django.core.management.base import BaseCommand, CommandError +from django.core.management.base import NoArgsCommand, CommandError -class Command(BaseCommand): +class Command(NoArgsCommand): help = "Introspects the database tables in the given database and outputs a Django model module." requires_model_validation = False - def handle(self, **options): + def handle_noargs(self, **options): try: for line in self.handle_inspection(): print line diff --git a/django/core/management/commands/runserver.py b/django/core/management/commands/runserver.py index cdab46c5a0..d34a25f3e2 100644 --- a/django/core/management/commands/runserver.py +++ b/django/core/management/commands/runserver.py @@ -9,10 +9,12 @@ class Command(BaseCommand): # Validation is called explicitly each time the server is reloaded. requires_model_validation = False - def handle(self, addrport='', **options): + def handle(self, addrport='', *args, **options): import django from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException from django.core.handlers.wsgi import WSGIHandler + if len(args) != 0: + raise CommandError('Usage is runserver %s' % self.args) if not addrport: addr = '' port = '8000' diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py index 68f8c7b4e5..bc76c44b97 100644 --- a/django/core/management/commands/shell.py +++ b/django/core/management/commands/shell.py @@ -1,12 +1,12 @@ -from django.core.management.base import BaseCommand +from django.core.management.base import NoArgsCommand -class Command(BaseCommand): +class Command(NoArgsCommand): help = "Runs a Python interactive interpreter. Tries to use IPython, if it's available." args = '[--plain]' requires_model_validation = False - def handle(self, **options): + def handle_noargs(self, **options): # XXX: (Temporary) workaround for ticket #1796: force early loading of all # models from installed apps. from django.db.models.loading import get_models diff --git a/django/core/management/commands/sqlflush.py b/django/core/management/commands/sqlflush.py index ff6a9cceea..7d14fe61e1 100644 --- a/django/core/management/commands/sqlflush.py +++ b/django/core/management/commands/sqlflush.py @@ -1,10 +1,10 @@ -from django.core.management.base import BaseCommand +from django.core.management.base import NoArgsCommand -class Command(BaseCommand): +class Command(NoArgsCommand): help = "Returns a list of the SQL statements required to return all tables in the database to the state they were in just after they were installed." output_transaction = True - def handle(self, **options): + def handle_noargs(self, **options): from django.core.management.sql import sql_flush return '\n'.join(sql_flush(self.style)) diff --git a/django/core/management/commands/startapp.py b/django/core/management/commands/startapp.py index ae37afc11e..acc965cf44 100644 --- a/django/core/management/commands/startapp.py +++ b/django/core/management/commands/startapp.py @@ -1,16 +1,17 @@ -from django.core.management.base import CopyFilesCommand, CommandError +from django.core.management.base import copy_helper, CommandError, LabelCommand import os -class Command(CopyFilesCommand): +class Command(LabelCommand): help = "Creates a Django app directory structure for the given app name in the current directory." args = "[appname]" + label = 'application name' requires_model_validation = False # Can't import settings during this command, because they haven't # necessarily been created. can_import_settings = False - def handle(self, app_name, directory=None, **options): + def handle_label(self, app_name, directory=None, **options): if directory is None: directory = os.getcwd() # Determine the project_name a bit naively -- by looking at the name of @@ -20,7 +21,7 @@ class Command(CopyFilesCommand): project_name = os.path.basename(directory) if app_name == project_name: raise CommandError("You cannot create an app with the same name (%r) as your project." % app_name) - self.copy_helper('app', app_name, directory, parent_dir) + copy_helper('app', app_name, directory, parent_dir) class ProjectCommand(Command): help = "Creates a Django app directory structure for the given app name in this project's directory." @@ -29,5 +30,5 @@ class ProjectCommand(Command): super(ProjectCommand, self).__init__() self.project_directory = project_directory - def handle(self, app_name, **options): - super(ProjectCommand, self).handle(app_name, self.project_directory, **options) + def handle_label(self, app_name, **options): + super(ProjectCommand, self).handle_label(app_name, self.project_directory, **options) diff --git a/django/core/management/commands/startproject.py b/django/core/management/commands/startproject.py index 5be4edd9ad..8394117b70 100644 --- a/django/core/management/commands/startproject.py +++ b/django/core/management/commands/startproject.py @@ -1,20 +1,21 @@ -from django.core.management.base import CopyFilesCommand, CommandError +from django.core.management.base import copy_helper, CommandError, LabelCommand import os import re from random import choice INVALID_PROJECT_NAMES = ('django', 'site', 'test') -class Command(CopyFilesCommand): +class Command(LabelCommand): help = "Creates a Django project directory structure for the given project name in the current directory." args = "[projectname]" + label = 'project name' requires_model_validation = False # Can't import settings during this command, because they haven't # necessarily been created. can_import_settings = False - def handle(self, project_name, **options): + def handle_label(self, project_name, **options): # Determine the project_name a bit naively -- by looking at the name of # the parent directory. directory = os.getcwd() @@ -22,7 +23,7 @@ class Command(CopyFilesCommand): if project_name in INVALID_PROJECT_NAMES: raise CommandError("%r conflicts with the name of an existing Python module and cannot be used as a project name. Please try another name." % project_name) - self.copy_helper('project', project_name, directory) + copy_helper('project', project_name, directory) # Create a random SECRET_KEY hash, and put it in the main settings. main_settings_file = os.path.join(directory, project_name, 'settings.py') diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py index b388b41043..d87b0d38d9 100644 --- a/django/core/management/commands/syncdb.py +++ b/django/core/management/commands/syncdb.py @@ -1,4 +1,4 @@ -from django.core.management.base import BaseCommand +from django.core.management.base import NoArgsCommand from django.core.management.color import no_style try: @@ -6,15 +6,15 @@ try: except NameError: from sets import Set as set # Python 2.3 fallback -class Command(BaseCommand): +class Command(NoArgsCommand): help = "Create the database tables for all apps in INSTALLED_APPS whose tables haven't already been created." args = '[--verbosity] [--noinput]' - def handle(self, **options): + def handle_noargs(self, **options): from django.db import backend, connection, transaction, models from django.conf import settings from django.core.management.sql import table_list, installed_models, sql_model_create, sql_for_pending_references, many_to_many_sql_for_model, custom_sql_for_model, sql_indexes_for_model, emit_post_sync_signal - + verbosity = int(options.get('verbosity', 1)) interactive = options.get('interactive') diff --git a/django/core/management/commands/validate.py b/django/core/management/commands/validate.py index 77d414acd8..2751f41abd 100644 --- a/django/core/management/commands/validate.py +++ b/django/core/management/commands/validate.py @@ -1,9 +1,9 @@ -from django.core.management.base import BaseCommand +from django.core.management.base import NoArgsCommand -class Command(BaseCommand): +class Command(NoArgsCommand): help = "Validates all installed models." requires_model_validation = False - def handle(self, **options): + def handle_noargs(self, **options): self.validate()