From 822d6d6dabc959532fb2904376580e8947c519f6 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 19 May 2012 13:51:54 +0200 Subject: [PATCH] Fixed #18325 -- Wrapped self.stdout/stderr in OutputWrapper class --- .../management/commands/createsuperuser.py | 14 +++---- .../management/commands/collectstatic.py | 7 +--- .../management/commands/findstatic.py | 6 +-- django/core/management/base.py | 38 +++++++++++++++---- .../management/commands/createcachetable.py | 4 +- django/core/management/commands/dumpdata.py | 5 ++- django/core/management/commands/loaddata.py | 36 +++++++++--------- django/core/management/commands/runserver.py | 4 +- django/core/management/templates.py | 6 +-- docs/howto/custom-management-commands.txt | 2 +- tests/modeltests/user_commands/tests.py | 4 +- 11 files changed, 72 insertions(+), 54 deletions(-) diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py index ad659773f8..f3f1a7b671 100644 --- a/django/contrib/auth/management/commands/createsuperuser.py +++ b/django/contrib/auth/management/commands/createsuperuser.py @@ -80,7 +80,7 @@ class Command(BaseCommand): if default_username and username == '': username = default_username if not RE_VALID_USERNAME.match(username): - sys.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.\n") + self.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.") username = None continue try: @@ -88,7 +88,7 @@ class Command(BaseCommand): except User.DoesNotExist: break else: - sys.stderr.write("Error: That username is already taken.\n") + self.stderr.write("Error: That username is already taken.") username = None # Get an email @@ -98,7 +98,7 @@ class Command(BaseCommand): try: is_valid_email(email) except exceptions.ValidationError: - sys.stderr.write("Error: That e-mail address is invalid.\n") + self.stderr.write("Error: That e-mail address is invalid.") email = None else: break @@ -109,19 +109,19 @@ class Command(BaseCommand): password = getpass.getpass() password2 = getpass.getpass('Password (again): ') if password != password2: - sys.stderr.write("Error: Your passwords didn't match.\n") + self.stderr.write("Error: Your passwords didn't match.") password = None continue if password.strip() == '': - sys.stderr.write("Error: Blank passwords aren't allowed.\n") + self.stderr.write("Error: Blank passwords aren't allowed.") password = None continue break except KeyboardInterrupt: - sys.stderr.write("\nOperation cancelled.\n") + self.stderr.write("\nOperation cancelled.") sys.exit(1) User.objects.db_manager(database).create_superuser(username, email, password) if verbosity >= 1: - self.stdout.write("Superuser created successfully.\n") + self.stdout.write("Superuser created successfully.") diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py index cab96c8d44..669c04043b 100644 --- a/django/contrib/staticfiles/management/commands/collectstatic.py +++ b/django/contrib/staticfiles/management/commands/collectstatic.py @@ -4,7 +4,7 @@ from optparse import make_option from django.core.files.storage import FileSystemStorage from django.core.management.base import CommandError, NoArgsCommand -from django.utils.encoding import smart_str, smart_unicode +from django.utils.encoding import smart_unicode from django.utils.datastructures import SortedDict from django.contrib.staticfiles import finders, storage @@ -178,15 +178,12 @@ Type 'yes' to continue, or 'no' to cancel: """ ', %s post-processed' % post_processed_count or ''), } - self.stdout.write(smart_str(summary)) + self.stdout.write(summary) def log(self, msg, level=2): """ Small log helper """ - msg = smart_str(msg) - if not msg.endswith("\n"): - msg += "\n" if self.verbosity >= level: self.stdout.write(msg) diff --git a/django/contrib/staticfiles/management/commands/findstatic.py b/django/contrib/staticfiles/management/commands/findstatic.py index bcf0c2f055..dc4406e458 100644 --- a/django/contrib/staticfiles/management/commands/findstatic.py +++ b/django/contrib/staticfiles/management/commands/findstatic.py @@ -23,9 +23,7 @@ class Command(LabelCommand): result = [result] output = u'\n '.join( (smart_unicode(os.path.realpath(path)) for path in result)) - self.stdout.write( - smart_str(u"Found '%s' here:\n %s\n" % (path, output))) + self.stdout.write(u"Found '%s' here:\n %s" % (path, output)) else: if verbosity >= 1: - self.stderr.write( - smart_str("No matching file found for '%s'.\n" % path)) + self.stderr.write("No matching file found for '%s'." % path) diff --git a/django/core/management/base.py b/django/core/management/base.py index 121a5c6ab7..16feb91f49 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -45,6 +45,29 @@ def handle_default_options(options): sys.path.insert(0, options.pythonpath) +class OutputWrapper(object): + """ + Wrapper around stdout/stderr + """ + def __init__(self, out, style_func=None): + self._out = out + self.style_func = None + if hasattr(out, 'isatty') and out.isatty(): + self.style_func = style_func + + def __getattr__(self, name): + return getattr(self._out, name) + + def write(self, msg, style_func=None, ending='\n'): + if ending and not msg.endswith(ending): + msg += ending + if style_func is not None: + msg = style_func(msg) + elif self.style_func is not None: + msg = self.style_func(msg) + self._out.write(smart_str(msg)) + + class BaseCommand(object): """ The base class from which all management commands ultimately @@ -210,6 +233,9 @@ class BaseCommand(object): # But only do this if we can assume we have a working settings file, # because django.utils.translation requires settings. saved_lang = None + self.stdout = OutputWrapper(options.get('stdout', sys.stdout)) + self.stderr = OutputWrapper(options.get('stderr', sys.stderr), self.style.ERROR) + if self.can_import_settings: try: from django.utils import translation @@ -221,12 +247,10 @@ class BaseCommand(object): if show_traceback: traceback.print_exc() else: - sys.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e))) + self.stderr.write('Error: %s' % e) sys.exit(1) try: - self.stdout = options.get('stdout', sys.stdout) - self.stderr = options.get('stderr', sys.stderr) if self.requires_model_validation and not options.get('skip_validation'): self.validate() output = self.handle(*args, **options) @@ -237,15 +261,15 @@ class BaseCommand(object): from django.db import connections, DEFAULT_DB_ALIAS connection = connections[options.get('database', DEFAULT_DB_ALIAS)] if connection.ops.start_transaction_sql(): - self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()) + '\n') + self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql())) self.stdout.write(output) if self.output_transaction: - self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;") + '\n') + self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;")) except CommandError as e: if show_traceback: traceback.print_exc() else: - self.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e))) + self.stderr.write('Error: %s' % e) sys.exit(1) finally: if saved_lang is not None: @@ -266,7 +290,7 @@ class BaseCommand(object): error_text = s.read() raise CommandError("One or more models did not validate:\n%s" % error_text) if display_num_errors: - self.stdout.write("%s error%s found\n" % (num_errors, num_errors != 1 and 's' or '')) + self.stdout.write("%s error%s found" % (num_errors, num_errors != 1 and 's' or '')) def handle(self, *args, **options): """ diff --git a/django/core/management/commands/createcachetable.py b/django/core/management/commands/createcachetable.py index 82a126b0fa..fec3aff67c 100644 --- a/django/core/management/commands/createcachetable.py +++ b/django/core/management/commands/createcachetable.py @@ -56,8 +56,8 @@ class Command(LabelCommand): curs.execute("\n".join(full_statement)) except DatabaseError as e: self.stderr.write( - self.style.ERROR("Cache table '%s' could not be created.\nThe error was: %s.\n" % - (tablename, e))) + "Cache table '%s' could not be created.\nThe error was: %s." % + (tablename, e)) transaction.rollback_unless_managed(using=db) else: for statement in index_output: diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py index 71d6fa74f1..87b100b44d 100644 --- a/django/core/management/commands/dumpdata.py +++ b/django/core/management/commands/dumpdata.py @@ -109,8 +109,9 @@ class Command(BaseCommand): objects.extend(model._default_manager.using(using).all()) try: - return serializers.serialize(format, objects, indent=indent, - use_natural_keys=use_natural_keys) + self.stdout.write(serializers.serialize(format, objects, + indent=indent, use_natural_keys=use_natural_keys), + ending='') except Exception as e: if show_traceback: raise diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index 848b63705a..f44edf7ade 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -34,11 +34,11 @@ class Command(BaseCommand): using = options.get('database') connection = connections[using] - self.style = no_style() if not len(fixture_labels): self.stderr.write( - self.style.ERROR("No database fixture specified. Please provide the path of at least one fixture in the command line.\n") + "No database fixture specified. Please provide the path of at " + "least one fixture in the command line." ) return @@ -124,11 +124,11 @@ class Command(BaseCommand): if formats: if verbosity >= 2: - self.stdout.write("Loading '%s' fixtures...\n" % fixture_name) + self.stdout.write("Loading '%s' fixtures..." % fixture_name) else: self.stderr.write( - self.style.ERROR("Problem installing fixture '%s': %s is not a known serialization format.\n" % - (fixture_name, format))) + "Problem installing fixture '%s': %s is not a known serialization format." % + (fixture_name, format)) if commit: transaction.rollback(using=using) transaction.leave_transaction_management(using=using) @@ -141,7 +141,7 @@ class Command(BaseCommand): for fixture_dir in fixture_dirs: if verbosity >= 2: - self.stdout.write("Checking %s for fixtures...\n" % humanize(fixture_dir)) + self.stdout.write("Checking %s for fixtures..." % humanize(fixture_dir)) label_found = False for combo in product([using, None], formats, compression_formats): @@ -154,7 +154,7 @@ class Command(BaseCommand): ) if verbosity >= 3: - self.stdout.write("Trying %s for %s fixture '%s'...\n" % \ + self.stdout.write("Trying %s for %s fixture '%s'..." % \ (humanize(fixture_dir), file_name, fixture_name)) full_path = os.path.join(fixture_dir, file_name) open_method = compression_types[compression_format] @@ -162,13 +162,13 @@ class Command(BaseCommand): fixture = open_method(full_path, 'r') except IOError: if verbosity >= 2: - self.stdout.write("No %s fixture '%s' in %s.\n" % \ + self.stdout.write("No %s fixture '%s' in %s." % \ (format, fixture_name, humanize(fixture_dir))) else: try: if label_found: - self.stderr.write(self.style.ERROR("Multiple fixtures named '%s' in %s. Aborting.\n" % - (fixture_name, humanize(fixture_dir)))) + self.stderr.write("Multiple fixtures named '%s' in %s. Aborting." % + (fixture_name, humanize(fixture_dir))) if commit: transaction.rollback(using=using) transaction.leave_transaction_management(using=using) @@ -178,7 +178,7 @@ class Command(BaseCommand): objects_in_fixture = 0 loaded_objects_in_fixture = 0 if verbosity >= 2: - self.stdout.write("Installing %s fixture '%s' from %s.\n" % \ + self.stdout.write("Installing %s fixture '%s' from %s." % \ (format, fixture_name, humanize(fixture_dir))) objects = serializers.deserialize(format, fixture, using=using) @@ -209,8 +209,8 @@ class Command(BaseCommand): # error was encountered during fixture loading. if objects_in_fixture == 0: self.stderr.write( - self.style.ERROR("No fixture data found for '%s'. (File format may be invalid.)\n" % - (fixture_name))) + "No fixture data found for '%s'. (File format may be invalid.)" % + (fixture_name)) if commit: transaction.rollback(using=using) transaction.leave_transaction_management(using=using) @@ -231,16 +231,16 @@ class Command(BaseCommand): traceback.print_exc() else: self.stderr.write( - self.style.ERROR("Problem installing fixture '%s': %s\n" % + "Problem installing fixture '%s': %s" % (full_path, ''.join(traceback.format_exception(sys.exc_type, - sys.exc_value, sys.exc_traceback))))) + sys.exc_value, sys.exc_traceback)))) return # If we found even one object in a fixture, we need to reset the # database sequences. if loaded_object_count > 0: - sequence_sql = connection.ops.sequence_reset_sql(self.style, models) + sequence_sql = connection.ops.sequence_reset_sql(no_style(), models) if sequence_sql: if verbosity >= 2: self.stdout.write("Resetting sequences\n") @@ -253,10 +253,10 @@ class Command(BaseCommand): if verbosity >= 1: if fixture_object_count == loaded_object_count: - self.stdout.write("Installed %d object(s) from %d fixture(s)\n" % ( + self.stdout.write("Installed %d object(s) from %d fixture(s)" % ( loaded_object_count, fixture_count)) else: - self.stdout.write("Installed %d object(s) (of %d) from %d fixture(s)\n" % ( + self.stdout.write("Installed %d object(s) (of %d) from %d fixture(s)" % ( loaded_object_count, fixture_object_count, fixture_count)) # Close the DB connection. This is required as a workaround for an diff --git a/django/core/management/commands/runserver.py b/django/core/management/commands/runserver.py index e6182198ec..c067c9c322 100644 --- a/django/core/management/commands/runserver.py +++ b/django/core/management/commands/runserver.py @@ -120,12 +120,12 @@ class Command(BaseCommand): error_text = ERRORS[e.args[0].args[0]] except (AttributeError, KeyError): error_text = str(e) - sys.stderr.write(self.style.ERROR("Error: %s" % error_text) + '\n') + sys.stderr.write("Error: %s" % error_text) # Need to use an OS exit because sys.exit doesn't work in a thread os._exit(1) except KeyboardInterrupt: if shutdown_message: - self.stdout.write("%s\n" % shutdown_message) + self.stdout.write(shutdown_message) sys.exit(0) diff --git a/django/core/management/templates.py b/django/core/management/templates.py index 735e29ad7f..623aa69deb 100644 --- a/django/core/management/templates.py +++ b/django/core/management/templates.py @@ -16,7 +16,6 @@ from os import path import django from django.template import Template, Context from django.utils import archive -from django.utils.encoding import smart_str from django.utils._os import rmtree_errorhandler from django.core.management.base import BaseCommand, CommandError from django.core.management.commands.makemessages import handle_extensions @@ -166,11 +165,10 @@ class TemplateCommand(BaseCommand): shutil.copymode(old_path, new_path) self.make_writeable(new_path) except OSError: - notice = self.style.NOTICE( + self.stderr.write( "Notice: Couldn't set permission bits on %s. You're " "probably using an uncommon filesystem setup. No " - "problem.\n" % new_path) - sys.stderr.write(smart_str(notice)) + "problem." % new_path, self.style.NOTICE) if self.paths_to_remove: if self.verbosity >= 2: diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index ba8765b253..2b34d35de7 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -61,7 +61,7 @@ look like this: poll.opened = False poll.save() - self.stdout.write('Successfully closed poll "%s"\n' % poll_id) + self.stdout.write('Successfully closed poll "%s"' % poll_id) .. note:: When you are using management commands and wish to provide console diff --git a/tests/modeltests/user_commands/tests.py b/tests/modeltests/user_commands/tests.py index 896dd667ff..c1e2bf9a74 100644 --- a/tests/modeltests/user_commands/tests.py +++ b/tests/modeltests/user_commands/tests.py @@ -11,13 +11,13 @@ class CommandTests(TestCase): out = StringIO() management.call_command('dance', stdout=out) self.assertEqual(out.getvalue(), - "I don't feel like dancing Rock'n'Roll.") + "I don't feel like dancing Rock'n'Roll.\n") def test_command_style(self): out = StringIO() management.call_command('dance', style='Jive', stdout=out) self.assertEqual(out.getvalue(), - "I don't feel like dancing Jive.") + "I don't feel like dancing Jive.\n") def test_language_preserved(self): out = StringIO()