diff --git a/django/core/management/commands/diffsettings.py b/django/core/management/commands/diffsettings.py index cc3e32b6a5..5d1621eb08 100644 --- a/django/core/management/commands/diffsettings.py +++ b/django/core/management/commands/diffsettings.py @@ -8,15 +8,17 @@ def module_to_dict(module, omittable=lambda k: k.startswith('_')): class Command(BaseCommand): help = """Displays differences between the current settings.py and Django's - default settings. Settings that don't appear in the defaults are - followed by "###".""" + default settings.""" requires_system_checks = False def add_arguments(self, parser): parser.add_argument( '--all', action='store_true', dest='all', - help='Display all settings, regardless of their value. Default values are prefixed by "###".', + help=( + 'Display all settings, regardless of their value. In "hash" ' + 'mode, default values are prefixed by "###".' + ), ) parser.add_argument( '--default', dest='default', metavar='MODULE', default=None, @@ -25,9 +27,18 @@ class Command(BaseCommand): "compare against Django's default settings." ), ) + parser.add_argument( + '--output', default='hash', choices=('hash', 'unified'), dest='output', + help=( + "Selects the output format. 'hash' mode displays each changed " + "setting, with the settings that don't appear in the defaults " + "followed by ###. 'unified' mode prefixes the default setting " + "with a minus sign, followed by the changed setting prefixed " + "with a plus sign." + ), + ) def handle(self, **options): - # Inspired by Postfix's "postconf -n". from django.conf import settings, Settings, global_settings # Because settings are imported lazily, we need to explicitly load them. @@ -36,7 +47,14 @@ class Command(BaseCommand): user_settings = module_to_dict(settings._wrapped) default = options['default'] default_settings = module_to_dict(Settings(default) if default else global_settings) + output_func = { + 'hash': self.output_hash, + 'unified': self.output_unified, + }[options['output']] + return '\n'.join(output_func(user_settings, default_settings, **options)) + def output_hash(self, user_settings, default_settings, **options): + # Inspired by Postfix's "postconf -n". output = [] for key in sorted(user_settings): if key not in default_settings: @@ -45,4 +63,16 @@ class Command(BaseCommand): output.append("%s = %s" % (key, user_settings[key])) elif options['all']: output.append("### %s = %s" % (key, user_settings[key])) - return '\n'.join(output) + return output + + def output_unified(self, user_settings, default_settings, **options): + output = [] + for key in sorted(user_settings): + if key not in default_settings: + output.append(self.style.SUCCESS("+ %s = %s" % (key, user_settings[key]))) + elif user_settings[key] != default_settings[key]: + output.append(self.style.ERROR("- %s = %s" % (key, default_settings[key]))) + output.append(self.style.SUCCESS("+ %s = %s" % (key, user_settings[key]))) + elif options['all']: + output.append(" %s = %s" % (key, user_settings[key])) + return output diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 68fb762623..7f120c28b3 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -240,6 +240,16 @@ are prefixed by ``"###"``. The settings module to compare the current settings against. Leave empty to compare against Django's default settings. +.. django-admin-option:: --output {hash,unified} + +.. versionadded:: 2.0 + +Specifies the output format. Available values are ``hash`` and ``unified``. +``hash`` is the default mode that displays the output that's described above. +``unified`` displays the output similar to ``diff -u``. Default settings are +prefixed with a minus sign, followed by the changed setting prefixed with a +plus sign. + ``dumpdata`` ------------ diff --git a/docs/releases/2.0.txt b/docs/releases/2.0.txt index de5b38a098..ee9ae181b4 100644 --- a/docs/releases/2.0.txt +++ b/docs/releases/2.0.txt @@ -187,6 +187,9 @@ Management Commands * :djadmin:`loaddata` can now :ref:`read from stdin `. +* The new :option:`diffsettings --output` option allows formatting the output + in a unified diff format. + Migrations ~~~~~~~~~~ diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index fa7f9e060e..f52eac6761 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -2120,6 +2120,32 @@ class DiffSettings(AdminScriptTestCase): self.assertNotInOutput(out, "FOO") self.assertOutput(out, "BAR = 'bar2'") + def test_unified(self): + """--output=unified emits settings diff in unified mode.""" + self.write_settings('settings_to_diff.py', sdict={'FOO': '"bar"'}) + self.addCleanup(self.remove_settings, 'settings_to_diff.py') + args = ['diffsettings', '--settings=settings_to_diff', '--output=unified'] + out, err = self.run_manage(args) + self.assertNoOutput(err) + self.assertOutput(out, "+ FOO = 'bar'") + self.assertOutput(out, "- SECRET_KEY = ''") + self.assertOutput(out, "+ SECRET_KEY = 'django_tests_secret_key'") + self.assertNotInOutput(out, " APPEND_SLASH = True") + + def test_unified_all(self): + """ + --output=unified --all emits settings diff in unified mode and includes + settings with the default value. + """ + self.write_settings('settings_to_diff.py', sdict={'FOO': '"bar"'}) + self.addCleanup(self.remove_settings, 'settings_to_diff.py') + args = ['diffsettings', '--settings=settings_to_diff', '--output=unified', '--all'] + out, err = self.run_manage(args) + self.assertNoOutput(err) + self.assertOutput(out, " APPEND_SLASH = True") + self.assertOutput(out, "+ FOO = 'bar'") + self.assertOutput(out, "- SECRET_KEY = ''") + class Dumpdata(AdminScriptTestCase): """Tests for dumpdata management command."""