Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

magic-removal: Fixed #1491 -- Added nice terminal colors to django-ad…

…min SQL output. Thanks, plmeister@gmail.com

git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@2585 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 493f8b9e5bc71de20b71b456966647dd46912d59 1 parent 639fe6a
@adrianholovaty adrianholovaty authored
Showing with 194 additions and 63 deletions.
  1. +124 −63 django/core/management.py
  2. +70 −0 django/utils/termcolors.py
View
187 django/core/management.py
@@ -5,6 +5,7 @@
from django.core.exceptions import ImproperlyConfigured
import os, re, sys, textwrap
from optparse import OptionParser
+from django.utils import termcolors
# For Python 2.3
if not hasattr(__builtins__, 'set'):
@@ -26,6 +27,28 @@
INVALID_PROJECT_NAMES = ('django', 'test')
+# Set up the terminal color scheme.
+class dummy: pass
+style = dummy()
+style.ERROR = termcolors.make_style(fg='red', opts=('bold',))
+style.ERROR_OUTPUT = termcolors.make_style(fg='red', opts=('bold',))
+style.SQL_FIELD = termcolors.make_style(fg='green', opts=('bold',))
+style.SQL_COLTYPE = termcolors.make_style(fg='green')
+style.SQL_KEYWORD = termcolors.make_style(fg='yellow')
+style.SQL_TABLE = termcolors.make_style(opts=('bold',))
+del dummy
+
+def disable_termcolors():
+ class dummy:
+ def __getattr__(self, attr):
+ return lambda x: x
+ global style
+ style = dummy()
+
+# Disable terminal coloring if somebody's piping the output.
+if (not sys.stdout.isatty()) or (sys.platform == 'win32'):
+ disable_termcolors()
+
def _is_valid_dir_name(s):
return bool(re.search(r'^\w+$', s))
@@ -66,9 +89,9 @@ def get_sql_create(app):
if not data_types:
# This must be the "dummy" database backend, which means the user
# hasn't set DATABASE_ENGINE.
- sys.stderr.write("Error: Django doesn't know which syntax to use for your SQL statements,\n" +
+ sys.stderr.write(style.ERROR("Error: Django doesn't know which syntax to use for your SQL statements,\n" +
"because you haven't specified the DATABASE_ENGINE setting.\n" +
- "Edit your settings file and change DATABASE_ENGINE to something like 'postgresql' or 'mysql'.\n")
+ "Edit your settings file and change DATABASE_ENGINE to something like 'postgresql' or 'mysql'.\n"))
sys.exit(1)
# Get installed models, so we generate REFERENCES right
@@ -128,29 +151,33 @@ def _get_sql_model_create(klass, models_already_seen=set()):
col_type = data_types[data_type]
if col_type is not None:
# Make the definition (e.g. 'foo VARCHAR(30)') for this field.
- field_output = [backend.quote_name(f.column), col_type % rel_field.__dict__]
- field_output.append('%sNULL' % (not f.null and 'NOT ' or ''))
+ field_output = [style.SQL_FIELD(backend.quote_name(f.column)),
+ style.SQL_COLTYPE(col_type % rel_field.__dict__)]
+ field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')))
if f.unique:
- field_output.append('UNIQUE')
+ field_output.append(style.SQL_KEYWORD('UNIQUE'))
if f.primary_key:
- field_output.append('PRIMARY KEY')
+ field_output.append(style.SQL_KEYWORD('PRIMARY KEY'))
if f.rel:
if f.rel.to in models_already_seen:
- field_output.append('REFERENCES %s (%s)' % \
- (backend.quote_name(f.rel.to._meta.db_table),
- backend.quote_name(f.rel.to._meta.get_field(f.rel.field_name).column)))
+ field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \
+ style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)) + ' (' + \
+ style.SQL_FIELD(backend.quote_name(f.rel.to._meta.get_field(f.rel.field_name).column)) + ')'
+ )
else:
# We haven't yet created the table to which this field
# is related, so save it for later.
pr = pending_references.setdefault(f.rel.to, []).append((klass, f))
table_output.append(' '.join(field_output))
if opts.order_with_respect_to:
- table_output.append('%s %s NULL' % (backend.quote_name('_order'), data_types['IntegerField']))
+ table_output.append(style.SQL_FIELD(backend.quote_name('_order')) + ' ' + \
+ style.SQL_COLTYPE(data_types['IntegerField']) + ' ' + \
+ style.SQL_KEYWORD('NULL'))
for field_constraints in opts.unique_together:
- table_output.append('UNIQUE (%s)' % \
- ", ".join([backend.quote_name(opts.get_field(f).column) for f in field_constraints]))
+ table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
+ ", ".join([backend.quote_name(style.SQL_FIELD(opts.get_field(f).column)) for f in field_constraints]))
- full_statement = ['CREATE TABLE %s (' % backend.quote_name(opts.db_table)]
+ full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(backend.quote_name(opts.db_table)) + ' (']
for i, line in enumerate(table_output): # Combine and add commas.
full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or ''))
full_statement.append(');')
@@ -175,9 +202,9 @@ def _get_sql_for_pending_references(klass, pending_references):
r_col = f.column
table = opts.db_table
col = opts.get_field(f.rel.field_name).column
- final_output.append('ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s);' % \
+ final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s);' % \
(backend.quote_name(r_table),
- backend.quote_name("%s_referencing_%s_%s" % (r_col, table, col)),
+ backend.quote_name('%s_referencing_%s_%s' % (r_col, table, col)),
backend.quote_name(r_col), backend.quote_name(table), backend.quote_name(col)))
del pending_references[klass]
return final_output
@@ -189,21 +216,28 @@ def _get_many_to_many_sql_for_model(klass):
opts = klass._meta
final_output = []
for f in opts.many_to_many:
- table_output = ['CREATE TABLE %s (' % backend.quote_name(f.m2m_db_table())]
- table_output.append(' %s %s NOT NULL PRIMARY KEY,' % (backend.quote_name('id'), data_types['AutoField']))
- table_output.append(' %s %s NOT NULL REFERENCES %s (%s),' % \
- (backend.quote_name(f.m2m_column_name()),
- data_types[get_rel_data_type(opts.pk)] % opts.pk.__dict__,
- backend.quote_name(opts.db_table),
- backend.quote_name(opts.pk.column)))
- table_output.append(' %s %s NOT NULL REFERENCES %s (%s),' % \
- (backend.quote_name(f.m2m_reverse_name()),
- data_types[get_rel_data_type(f.rel.to._meta.pk)] % f.rel.to._meta.pk.__dict__,
- backend.quote_name(f.rel.to._meta.db_table),
- backend.quote_name(f.rel.to._meta.pk.column)))
- table_output.append(' UNIQUE (%s, %s)' % \
- (backend.quote_name(f.m2m_column_name()),
- backend.quote_name(f.m2m_reverse_name())))
+ table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
+ style.SQL_TABLE(backend.quote_name(f.m2m_db_table())) + ' (']
+ table_output.append(' %s %s %s,' % \
+ (style.SQL_FIELD(backend.quote_name('id')),
+ style.SQL_COLTYPE(data_types['AutoField']),
+ style.SQL_KEYWORD('NOT NULL PRIMARY KEY')))
+ table_output.append(' %s %s %s %s (%s),' % \
+ (style.SQL_FIELD(backend.quote_name(f.m2m_column_name())),
+ style.SQL_COLTYPE(data_types[get_rel_data_type(opts.pk)] % opts.pk.__dict__),
+ style.SQL_KEYWORD('NOT NULL REFERENCES'),
+ style.SQL_TABLE(backend.quote_name(opts.db_table)),
+ style.SQL_FIELD(backend.quote_name(opts.pk.column))))
+ table_output.append(' %s %s %s %s (%s),' % \
+ (style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name())),
+ style.SQL_COLTYPE(data_types[get_rel_data_type(f.rel.to._meta.pk)] % f.rel.to._meta.pk.__dict__),
+ style.SQL_KEYWORD('NOT NULL REFERENCES'),
+ style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)),
+ style.SQL_FIELD(backend.quote_name(f.rel.to._meta.pk.column))))
+ table_output.append(' %s (%s, %s)' % \
+ (style.SQL_KEYWORD('UNIQUE'),
+ style.SQL_FIELD(backend.quote_name(f.m2m_column_name())),
+ style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name()))))
table_output.append(');')
final_output.append('\n'.join(table_output))
return final_output
@@ -244,17 +278,20 @@ def get_sql_delete(app):
for klass in app_models:
if cursor and klass._meta.db_table in table_names:
- # Drop the able now
- output.append("DROP TABLE %s;" % backend.quote_name(klass._meta.db_table))
+ # Drop the table now
+ output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
+ style.SQL_TABLE(backend.quote_name(klass._meta.db_table))))
if backend.supports_constraints and references_to_delete.has_key(klass):
for rel_class, f in references_to_delete[klass]:
table = rel_class._meta.db_table
col = f.column
r_table = klass._meta.db_table
r_col = klass._meta.get_field(f.rel.field_name).column
- output.append('ALTER TABLE %s DROP CONSTRAINT %s;' % \
- (backend.quote_name(table),
- backend.quote_name("%s_referencing_%s_%s" % (col, r_table, r_col))))
+ output.append('%s %s %s %s;' % \
+ (style.SQL_KEYWORD('ALTER TABLE'),
+ style.SQL_TABLE(backend.quote_name(table)),
+ style.SQL_KEYWORD('DROP CONSTRAINT'),
+ style.SQL_FIELD(backend.quote_name("%s_referencing_%s_%s" % (col, r_table, r_col)))))
del references_to_delete[klass]
# Output DROP TABLE statements for many-to-many tables.
@@ -262,10 +299,13 @@ def get_sql_delete(app):
opts = klass._meta
for f in opts.many_to_many:
if cursor and f.m2m_db_table() in table_names:
- output.append("DROP TABLE %s;" % backend.quote_name(f.m2m_db_table()))
+ output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'),
+ style.SQL_TABLE(backend.quote_name(f.m2m_db_table()))))
app_label = app_models[0]._meta.app_label
+ # TODO: Remove the following section.
+
# Delete from django_package, auth_permission, django_content_type.
if cursor and "django_content_type" in table_names:
@@ -346,12 +386,22 @@ def get_sql_sequence_reset(app):
for klass in models.get_models(app):
for f in klass._meta.fields:
if isinstance(f, models.AutoField):
- output.append("SELECT setval('%s_%s_seq', (SELECT max(%s) FROM %s));" % \
- (klass._meta.db_table, f.column, backend.quote_name(f.column),
- backend.quote_name(klass._meta.db_table)))
+ output.append("%s setval('%s', (%s max(%s) %s %s));" % \
+ (style.SQL_KEYWORD('SELECT'),
+ style.SQL_FIELD('%s_%s_seq' % (klass._meta.db_table, f.column)),
+ style.SQL_KEYWORD('SELECT'),
+ style.SQL_FIELD(backend.quote_name(f.column)),
+ style.SQL_KEYWORD('FROM'),
+ style.SQL_TABLE(backend.quote_name(klass._meta.db_table))))
+ break # Only one AutoField is allowed per model, so don't bother continuing.
for f in klass._meta.many_to_many:
- output.append("SELECT setval('%s_id_seq', (SELECT max(%s) FROM %s));" % \
- (f.m2m_db_table(), backend.quote_name('id'), f.m2m_db_table()))
+ output.append("%s setval('%s', (%s max(%s) %s %s));" % \
+ (style.SQL_KEYWORD('SELECT'),
+ style.SQL_FIELD('%s_id_seq' % f.m2m_db_table()),
+ style.SQL_KEYWORD('SELECT'),
+ style.SQL_FIELD(backend.quote_name('id')),
+ style.SQL_KEYWORD('FROM'),
+ style.SQL_TABLE(f.m2m_db_table())))
return output
get_sql_sequence_reset.help_doc = "Prints the SQL statements for resetting PostgreSQL sequences for the given app name(s)."
get_sql_sequence_reset.args = APP_ARGS
@@ -364,10 +414,14 @@ def get_sql_indexes(app):
for klass in models.get_models(app):
for f in klass._meta.fields:
if f.db_index:
- unique = f.unique and "UNIQUE " or ""
- output.append("CREATE %sINDEX %s_%s ON %s (%s);" % \
- (unique, klass._meta.db_table, f.column,
- backend.quote_name(klass._meta.db_table), backend.quote_name(f.column)))
+ unique = f.unique and 'UNIQUE ' or ''
+ output.append(
+ style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \
+ style.SQL_TABLE('%s_%s' % (klass._meta.db_table, f.column)) + ' ' + \
+ style.SQL_KEYWORD('ON') + ' ' + \
+ style.SQL_TABLE(backend.quote_name(klass._meta.db_table)) + ' ' + \
+ "(%s);" % style.SQL_FIELD(backend.quote_name(f.column))
+ )
return output
get_sql_indexes.help_doc = "Prints the CREATE INDEX SQL statements for the given model module name(s)."
get_sql_indexes.args = APP_ARGS
@@ -385,7 +439,9 @@ def syncdb():
from django.conf import settings
from django.dispatch import dispatcher
- # Check that there are no validation errors before continuing
+ disable_termcolors()
+
+ # First, try validating the models.
_check_for_validation_errors()
# Import the 'management' module within each installed app, to register
@@ -489,6 +545,8 @@ def install(app):
app_name = app.__name__.split('.')[-2]
+ disable_termcolors()
+
# First, try validating the models.
_check_for_validation_errors(app)
@@ -499,12 +557,12 @@ def install(app):
for sql in sql_list:
cursor.execute(sql)
except Exception, e:
- sys.stderr.write("""Error: %s couldn't be installed. Possible reasons:
+ sys.stderr.write(style.ERROR("""Error: %s couldn't be installed. Possible reasons:
* The database isn't running or isn't configured correctly.
* At least one of the database tables already exists.
* The SQL was invalid.
Hint: Look at the output of 'django-admin.py sqlall %s'. That's the SQL this command wasn't able to run.
-The full error: %s\n""" % (app_name, app_name, e))
+The full error: """ % (app_name, app_name)) + style.ERROR_OUTPUT(e) + '\n')
transaction.rollback_unless_managed()
sys.exit(1)
transaction.commit_unless_managed()
@@ -517,6 +575,8 @@ def reset(app):
from cStringIO import StringIO
app_name = app.__name__.split('.')[-2]
+ disable_termcolors()
+
# First, try validating the models.
_check_for_validation_errors(app)
sql_list = get_sql_reset(app)
@@ -533,12 +593,12 @@ def reset(app):
for sql in sql_list:
cursor.execute(sql)
except Exception, e:
- sys.stderr.write("""Error: %s couldn't be installed. Possible reasons:
+ sys.stderr.write(style.ERROR("""Error: %s couldn't be installed. Possible reasons:
* The database isn't running or isn't configured correctly.
* At least one of the database tables already exists.
* The SQL was invalid.
Hint: Look at the output of 'django-admin.py sqlreset %s'. That's the SQL this command wasn't able to run.
-The full error: %s\n""" % (app_name, app_name, e))
+The full error: """ % (app_name, app_name)) + style.ERROR_OUTPUT(e) + '\n')
transaction.rollback_unless_managed()
sys.exit(1)
transaction.commit_unless_managed()
@@ -550,13 +610,13 @@ def reset(app):
def _start_helper(app_or_project, name, directory, other_name=''):
other = {'project': 'app', 'app': 'project'}[app_or_project]
if not _is_valid_dir_name(name):
- sys.stderr.write("Error: %r is not a valid %s name. Please use only numbers, letters and underscores.\n" % (name, app_or_project))
+ sys.stderr.write(style.ERROR("Error: %r is not a valid %s name. Please use only numbers, letters and underscores.\n" % (name, app_or_project)))
sys.exit(1)
top_dir = os.path.join(directory, name)
try:
os.mkdir(top_dir)
except OSError, e:
- sys.stderr.write("Error: %s\n" % e)
+ sys.stderr.write(style.ERROR("Error: %s\n" % e))
sys.exit(1)
template_dir = PROJECT_TEMPLATE_DIR % app_or_project
for d, subdirs, files in os.walk(template_dir):
@@ -579,7 +639,7 @@ def startproject(project_name, directory):
"Creates a Django project for the given project_name in the given directory."
from random import choice
if project_name in INVALID_PROJECT_NAMES:
- sys.stderr.write("Error: %r isn't a valid project name. Please try another.\n" % project_name)
+ sys.stderr.write(style.ERROR("Error: %r isn't a valid project name. Please try another.\n" % project_name))
sys.exit(1)
_start_helper('project', project_name, directory)
# Create a random SECRET_KEY hash, and put it in the main settings.
@@ -714,7 +774,7 @@ def __init__(self, outfile=sys.stdout):
def add(self, opts, error):
self.errors.append((opts, error))
- self.outfile.write("%s.%s: %s\n" % (opts.app_label, opts.module_name, error))
+ self.outfile.write(style.ERROR("%s.%s: %s\n" % (opts.app_label, opts.module_name, error)))
def get_validation_errors(outfile, app=None):
"""
@@ -885,7 +945,7 @@ def _check_for_validation_errors(app=None):
s = StringIO()
num_errors = get_validation_errors(s, app)
if num_errors:
- sys.stderr.write("Error: %s couldn't be installed, because there were errors in your model:\n" % app)
+ sys.stderr.write(style.ERROR("Error: %s couldn't be installed, because there were errors in your model:\n" % app))
s.seek(0)
sys.stderr.write(s.read())
sys.exit(1)
@@ -897,7 +957,7 @@ def runserver(addr, port):
if not addr:
addr = '127.0.0.1'
if not port.isdigit():
- sys.stderr.write("Error: %r is not a valid port number.\n" % port)
+ sys.stderr.write(style.ERROR("Error: %r is not a valid port number.\n" % port))
sys.exit(1)
def inner_run():
from django.conf import settings
@@ -919,7 +979,7 @@ def inner_run():
error_text = ERRORS[e.args[0].args[0]]
except (AttributeError, KeyError):
error_text = str(e)
- sys.stderr.write("Error: %s\n" % error_text)
+ sys.stderr.write(style.ERROR("Error: %s" % error_text) + '\n')
sys.exit(1)
except KeyboardInterrupt:
sys.exit(0)
@@ -1005,7 +1065,8 @@ def run_shell(use_plain=False):
'createcachetable',
'install',
'reset',
- 'sqlindexes'
+ 'sqlindexes',
+ 'syncdb'
)
class DjangoOptionParser(OptionParser):
@@ -1029,7 +1090,7 @@ def get_usage(action_mapping):
return '\n'.join(usage[:-1]) # Cut off last list element, an empty space.
def print_error(msg, cmd):
- sys.stderr.write('Error: %s\nRun "%s --help" for help.\n' % (msg, cmd))
+ sys.stderr.write(style.ERROR('Error: %s' % msg) + '\nRun "%s --help" for help.\n' % cmd)
sys.exit(1)
def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING):
@@ -1078,7 +1139,7 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING):
for line in action_mapping[action](param):
print line
except NotImplementedError:
- sys.stderr.write("Error: %r isn't supported for the currently selected database backend.\n" % action)
+ sys.stderr.write(style.ERROR("Error: %r isn't supported for the currently selected database backend.\n" % action))
sys.exit(1)
elif action == 'createcachetable':
try:
@@ -1106,18 +1167,18 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING):
try:
mod_list = [models.get_app(app_label) for app_label in args[1:]]
except ImportError, e:
- sys.stderr.write("Error: %s. Are you sure your INSTALLED_APPS setting is correct?\n" % e)
+ sys.stderr.write(style.ERROR("Error: %s. Are you sure your INSTALLED_APPS setting is correct?\n" % e))
sys.exit(1)
if not mod_list:
parser.print_usage_and_exit()
if action not in NO_SQL_TRANSACTION:
- print "BEGIN;"
+ print style.SQL_KEYWORD("BEGIN;")
for mod in mod_list:
output = action_mapping[action](mod)
if output:
print '\n'.join(output)
if action not in NO_SQL_TRANSACTION:
- print "COMMIT;"
+ print style.SQL_KEYWORD("COMMIT;")
def execute_manager(settings_mod):
# Add this project to sys.path so that it's importable in the conventional
View
70 django/utils/termcolors.py
@@ -0,0 +1,70 @@
+"""
+termcolors.py
+"""
+
+import types
+
+color_names = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white')
+foreground = dict([(color_names[x], '3%s' % x) for x in range(8)])
+background = dict([(color_names[x], '4%s' % x) for x in range(8)])
+del color_names
+
+RESET = '0'
+opt_dict = {'bold': '1', 'underscore': '4', 'blink': '5', 'reverse': '7', 'conceal': '8'}
+
+def colorize(text='', opts=(), **kwargs):
+ """
+ Returns your text, enclosed in ANSI graphics codes.
+
+ Depends on the keyword arguments 'fg' and 'bg', and the contents of
+ the opts tuple/list.
+
+ Returns the RESET code if no parameters are given.
+
+ Valid colors:
+ 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'
+
+ Valid options:
+ 'bold'
+ 'underscore'
+ 'blink'
+ 'reverse'
+ 'conceal'
+ 'noreset' - string will not be auto-terminated with the RESET code
+
+ Examples:
+ colorize('hello', fg='red', bg='blue', opts=('blink',))
+ colorize()
+ colorize('goodbye', opts=('underscore',))
+ print colorize('first line', fg='red', opts=('noreset',))
+ print 'this should be red too'
+ print colorize('and so should this')
+ print 'this should not be red'
+ """
+ text = str(text)
+ code_list = []
+ if text == '' and len(opts) == 1 and opts[0] == 'reset':
+ return '\x1b[%sm' % RESET
+ for k, v in kwargs.iteritems():
+ if k == 'fg':
+ code_list.append(foreground[v])
+ elif k == 'bg':
+ code_list.append(background[v])
+ for o in opts:
+ if o in opt_dict:
+ code_list.append(opt_dict[o])
+ if 'noreset' not in opts:
+ text = text + '\x1b[%sm' % RESET
+ return ('\x1b[%sm' % ';'.join(code_list)) + text
+
+def make_style(opts=(), **kwargs):
+ """
+ Returns a function with default parameters for colorize()
+
+ Example:
+ bold_red = make_style(opts=('bold',), fg='red')
+ print bold_red('hello')
+ KEYWORD = make_style(fg='yellow')
+ COMMENT = make_style(fg='blue', opts=('bold',))
+ """
+ return lambda text: colorize(text, opts, **kwargs)
Please sign in to comment.
Something went wrong with that request. Please try again.