From 0707b824fe77e08ca8b75fcc3738ada694f2a3a6 Mon Sep 17 00:00:00 2001 From: Ana Krivokapic Date: Mon, 24 Mar 2014 14:03:06 +0100 Subject: [PATCH] Fixed #22328 -- Added --exclude option to compilemessages and makemessages. --- AUTHORS | 1 + .../management/commands/compilemessages.py | 27 ++++-- .../core/management/commands/makemessages.py | 25 +++-- docs/man/django-admin.1 | 7 +- docs/ref/django-admin.txt | 21 +++++ docs/releases/1.8.txt | 4 + tests/i18n/exclude/__init__.py | 12 +++ .../canned_locale/en/LC_MESSAGES/django.po | 27 ++++++ .../canned_locale/fr/LC_MESSAGES/django.po | 28 ++++++ .../canned_locale/it/LC_MESSAGES/django.po | 28 ++++++ tests/i18n/test_compilation.py | 91 ++++++++++++++----- tests/i18n/test_extraction.py | 69 +++++++++++++- 12 files changed, 299 insertions(+), 41 deletions(-) create mode 100644 tests/i18n/exclude/__init__.py create mode 100644 tests/i18n/exclude/canned_locale/en/LC_MESSAGES/django.po create mode 100644 tests/i18n/exclude/canned_locale/fr/LC_MESSAGES/django.po create mode 100644 tests/i18n/exclude/canned_locale/it/LC_MESSAGES/django.po diff --git a/AUTHORS b/AUTHORS index 3adade2b19..1af66b078c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -367,6 +367,7 @@ answer newbie questions, and generally made Django that much better: Martin Kosír Arthur Koziel Meir Kriheli + Ana Krivokapic Bruce Kroeze krzysiek.pawlik@silvermedia.pl konrad@gwu.edu diff --git a/django/core/management/commands/compilemessages.py b/django/core/management/commands/compilemessages.py index eaba797aad..d4b311bdd9 100644 --- a/django/core/management/commands/compilemessages.py +++ b/django/core/management/commands/compilemessages.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import codecs +import glob import os from optparse import make_option @@ -30,8 +31,11 @@ def is_writable(path): class Command(BaseCommand): option_list = BaseCommand.option_list + ( - make_option('--locale', '-l', dest='locale', action='append', - help='locale(s) to process (e.g. de_AT). Default is to process all. Can be used multiple times.'), + make_option('--locale', '-l', dest='locale', action='append', default=[], + help='Locale(s) to process (e.g. de_AT). Default is to process all. Can be ' + 'used multiple times.'), + make_option('--exclude', '-e', dest='exclude', action='append', default=[], + help='Locales to exclude. Default is none. Can be used multiple times.'), ) help = 'Compiles .po files to .mo files for use with builtin gettext support.' @@ -43,6 +47,7 @@ class Command(BaseCommand): def handle(self, **options): locale = options.get('locale') + exclude = options.get('exclude') self.verbosity = int(options.get('verbosity')) if find_command(self.program) is None: @@ -62,9 +67,19 @@ class Command(BaseCommand): "checkout or your project or app tree, or with " "the settings module specified.") + # Build locale list + all_locales = [] for basedir in basedirs: - if locale: - dirs = [os.path.join(basedir, l, 'LC_MESSAGES') for l in locale] + locale_dirs = filter(os.path.isdir, glob.glob('%s/*' % basedir)) + all_locales.extend(map(os.path.basename, locale_dirs)) + + # Account for excluded locales + locales = locale or all_locales + locales = set(locales) - set(exclude) + + for basedir in basedirs: + if locales: + dirs = [os.path.join(basedir, l, 'LC_MESSAGES') for l in locales] else: dirs = [basedir] locations = [] @@ -90,8 +105,8 @@ class Command(BaseCommand): # Check writability on first location if i == 0 and not is_writable(npath(base_path + '.mo')): - self.stderr.write("The po files under %s are in a seemingly not " - "writable location. mo files will not be updated/created." % dirpath) + self.stderr.write("The po files under %s are in a seemingly not writable location. " + "mo files will not be updated/created." % dirpath) return args = [self.program] + self.program_options + ['-o', diff --git a/django/core/management/commands/makemessages.py b/django/core/management/commands/makemessages.py index 7c2555ba2f..2b3afe3f4f 100644 --- a/django/core/management/commands/makemessages.py +++ b/django/core/management/commands/makemessages.py @@ -160,9 +160,11 @@ def write_pot_file(potfile, msgs): class Command(NoArgsCommand): option_list = NoArgsCommand.option_list + ( - make_option('--locale', '-l', default=None, dest='locale', action='append', + make_option('--locale', '-l', default=[], dest='locale', action='append', help='Creates or updates the message files for the given locale(s) (e.g. pt_BR). ' 'Can be used multiple times.'), + make_option('--exclude', '-e', default=[], dest='exclude', action='append', + help='Locales to exclude. Default is none. Can be used multiple times.'), make_option('--domain', '-d', default='django', dest='domain', help='The domain of the message files (default: "django").'), make_option('--all', '-a', action='store_true', dest='all', @@ -189,7 +191,7 @@ class Command(NoArgsCommand): "pulls out all strings marked for translation. It creates (or updates) a message " "file in the conf/locale (in the django tree) or locale (for projects and " "applications) directory.\n\nYou must run this command with one of either the " -"--locale or --all options.") +"--locale, --exclude or --all options.") requires_system_checks = False leave_locale_alone = True @@ -201,6 +203,7 @@ class Command(NoArgsCommand): def handle_noargs(self, *args, **options): locale = options.get('locale') + exclude = options.get('exclude') self.domain = options.get('domain') self.verbosity = int(options.get('verbosity')) process_all = options.get('all') @@ -235,7 +238,7 @@ class Command(NoArgsCommand): exts = extensions if extensions else ['html', 'txt'] self.extensions = handle_extensions(exts) - if (locale is None and not process_all) or self.domain is None: + if (locale is None and not exclude and not process_all) or self.domain is None: raise CommandError("Type '%s help %s' for usage information." % ( os.path.basename(sys.argv[0]), sys.argv[1])) @@ -270,12 +273,16 @@ class Command(NoArgsCommand): os.makedirs(self.default_locale_path) # Build locale list - locales = [] - if locale is not None: - locales = locale - elif process_all: - locale_dirs = filter(os.path.isdir, glob.glob('%s/*' % self.default_locale_path)) - locales = [os.path.basename(l) for l in locale_dirs] + locale_dirs = filter(os.path.isdir, glob.glob('%s/*' % self.default_locale_path)) + all_locales = map(os.path.basename, locale_dirs) + + # Account for excluded locales + if process_all: + locales = all_locales + else: + locales = locale or all_locales + locales = set(locales) - set(exclude) + if locales: check_programs('msguniq', 'msgmerge', 'msgattrib') diff --git a/docs/man/django-admin.1 b/docs/man/django-admin.1 index c9932ac36f..fd254d5268 100644 --- a/docs/man/django-admin.1 +++ b/docs/man/django-admin.1 @@ -21,7 +21,7 @@ script found at the top level of each Django project directory. .BI cleanup Cleans out old data from the database (only expired sessions at the moment). .TP -.BI "compilemessages [" "\-\-locale=LOCALE" "]" +.BI "compilemessages [" "\-\-locale=LOCALE" "] [" "\-\-exclude=LOCALE" "]" Compiles .po files to .mo files for use with builtin gettext support. .TP .BI "createcachetable [" "tablename" "]" @@ -59,7 +59,7 @@ Executes .B sqlall for the given app(s) in the current database. .TP -.BI "makemessages [" "\-\-locale=LOCALE" "] [" "\-\-domain=DOMAIN" "] [" "\-\-extension=EXTENSION" "] [" "\-\-all" "] [" "\-\-symlinks" "] [" "\-\-ignore=PATTERN" "] [" "\-\-no\-default\-ignore" "] [" "\-\-no\-wrap" "] [" "\-\-no\-location" "]" +.BI "makemessages [" "\-\-locale=LOCALE" "] [" "\-\-exclude=LOCALE" "] [" "\-\-domain=DOMAIN" "] [" "\-\-extension=EXTENSION" "] [" "\-\-all" "] [" "\-\-symlinks" "] [" "\-\-ignore=PATTERN" "] [" "\-\-no\-default\-ignore" "] [" "\-\-no\-wrap" "] [" "\-\-no\-location" "]" Runs over the entire source tree of the current directory and pulls out all strings marked for translation. It creates (or updates) a message file in the conf/locale (in the django tree) or locale (for project and application) directory. @@ -176,6 +176,9 @@ output a full stack trace whenever an exception is raised. .I \-l, \-\-locale=LOCALE The locale to process when using makemessages or compilemessages. .TP +.I \-e, \-\-exclude=LOCALE +The locale to exclude from processing when using makemessages or compilemessages. +.TP .I \-d, \-\-domain=DOMAIN The domain of the message files (default: "django") when using makemessages. .TP diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index cefb61d1e3..945f0b4409 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -141,12 +141,22 @@ the builtin gettext support. See :doc:`/topics/i18n/index`. Use the :djadminopt:`--locale` option (or its shorter version ``-l``) to specify the locale(s) to process. If not provided, all locales are processed. +.. versionadded:: 1.8 + +Use the :djadminopt:`--exclude` option (or its shorter version ``-e``) to +specify the locale(s) to exclude from processing. If not provided, no locales +are excluded. + Example usage:: django-admin.py compilemessages --locale=pt_BR django-admin.py compilemessages --locale=pt_BR --locale=fr django-admin.py compilemessages -l pt_BR django-admin.py compilemessages -l pt_BR -l fr + django-admin.py compilemessages --exclude=pt_BR + django-admin.py compilemessages --exclude=pt_BR --exclude=fr + django-admin.py compilemessages -e pt_BR + django-admin.py compilemessages -e pt_BR -e fr createcachetable ---------------- @@ -551,12 +561,23 @@ Separate multiple extensions with commas or use -e or --extension multiple times Use the :djadminopt:`--locale` option (or its shorter version ``-l``) to specify the locale(s) to process. +.. versionadded:: 1.8 + +Use the :djadminopt:`--exclude` option (or its shorter version ``-e``) to +specify the locale(s) to exclude from processing. If not provided, no locales +are excluded. + Example usage:: django-admin.py makemessages --locale=pt_BR django-admin.py makemessages --locale=pt_BR --locale=fr django-admin.py makemessages -l pt_BR django-admin.py makemessages -l pt_BR -l fr + django-admin.py makemessages --exclude=pt_BR + django-admin.py makemessages --exclude=pt_BR --exclude=fr + django-admin.py makemessages -e pt_BR + django-admin.py makemessages -e pt_BR -e fr + .. versionchanged:: 1.7 diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index e34017e363..c4e0ade59e 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -128,6 +128,10 @@ Management Commands * :djadmin:`dumpdata` now has the option :djadminopt:`--output` which allows specifying the file to which the serialized data is written. +* :djadmin:`makemessages` and :djadmin:`compilemessages` now have the option + :djadminopt:`--exclude` which allows exclusion of specific locales from + processing. + Models ^^^^^^ diff --git a/tests/i18n/exclude/__init__.py b/tests/i18n/exclude/__init__.py new file mode 100644 index 0000000000..ff4b2bb2ad --- /dev/null +++ b/tests/i18n/exclude/__init__.py @@ -0,0 +1,12 @@ +# This package is used to test the --exclude option of +# the makemessages and compilemessages management commands. +# The locale directory for this app is generated automatically +# by the test cases. + +from django.utils.translation import ugettext as _ + +# Translators: This comment should be extracted +dummy1 = _("This is a translatable string.") + +# This comment should not be extracted +dummy2 = _("This is another translatable string.") diff --git a/tests/i18n/exclude/canned_locale/en/LC_MESSAGES/django.po b/tests/i18n/exclude/canned_locale/en/LC_MESSAGES/django.po new file mode 100644 index 0000000000..7ca97afcc1 --- /dev/null +++ b/tests/i18n/exclude/canned_locale/en/LC_MESSAGES/django.po @@ -0,0 +1,27 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-04-25 15:39-0500\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Translators: This comment should be extracted +#: __init__.py:8 +msgid "This is a translatable string." +msgstr "" + +#: __init__.py:11 +msgid "This is another translatable string." +msgstr "" diff --git a/tests/i18n/exclude/canned_locale/fr/LC_MESSAGES/django.po b/tests/i18n/exclude/canned_locale/fr/LC_MESSAGES/django.po new file mode 100644 index 0000000000..94d1f5d2dd --- /dev/null +++ b/tests/i18n/exclude/canned_locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,28 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-04-25 15:39-0500\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. Translators: This comment should be extracted +#: __init__.py:8 +msgid "This is a translatable string." +msgstr "" + +#: __init__.py:11 +msgid "This is another translatable string." +msgstr "" diff --git a/tests/i18n/exclude/canned_locale/it/LC_MESSAGES/django.po b/tests/i18n/exclude/canned_locale/it/LC_MESSAGES/django.po new file mode 100644 index 0000000000..53f492bb8a --- /dev/null +++ b/tests/i18n/exclude/canned_locale/it/LC_MESSAGES/django.po @@ -0,0 +1,28 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-04-25 15:39-0500\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. Translators: This comment should be extracted +#: __init__.py:8 +msgid "This is a translatable string." +msgstr "" + +#: __init__.py:11 +msgid "This is another translatable string." +msgstr "" diff --git a/tests/i18n/test_compilation.py b/tests/i18n/test_compilation.py index 463ff21d23..325c597aa9 100644 --- a/tests/i18n/test_compilation.py +++ b/tests/i18n/test_compilation.py @@ -1,4 +1,5 @@ import os +import shutil import stat import unittest @@ -10,17 +11,23 @@ from django.utils import translation from django.utils._os import upath from django.utils.six import StringIO -test_dir = os.path.abspath(os.path.join(os.path.dirname(upath(__file__)), 'commands')) has_msgfmt = find_command('msgfmt') @unittest.skipUnless(has_msgfmt, 'msgfmt is mandatory for compilation tests') class MessageCompilationTests(SimpleTestCase): + test_dir = os.path.abspath(os.path.join(os.path.dirname(upath(__file__)), 'commands')) + def setUp(self): self._cwd = os.getcwd() self.addCleanup(os.chdir, self._cwd) - os.chdir(test_dir) + os.chdir(self.test_dir) + + def _rmrf(self, dname): + if os.path.commonprefix([self.test_dir, os.path.abspath(dname)]) != self.test_dir: + return + shutil.rmtree(dname) def rmfile(self, filepath): if os.path.exists(filepath): @@ -60,7 +67,7 @@ class PoFileContentsTests(MessageCompilationTests): def setUp(self): super(PoFileContentsTests, self).setUp() - self.addCleanup(os.unlink, os.path.join(test_dir, self.MO_FILE)) + self.addCleanup(os.unlink, os.path.join(self.test_dir, self.MO_FILE)) def test_percent_symbol_in_po_file(self): call_command('compilemessages', locale=[self.LOCALE], stdout=StringIO()) @@ -76,45 +83,85 @@ class PercentRenderingTests(MessageCompilationTests): def setUp(self): super(PercentRenderingTests, self).setUp() - self.addCleanup(os.unlink, os.path.join(test_dir, self.MO_FILE)) + self.addCleanup(os.unlink, os.path.join(self.test_dir, self.MO_FILE)) - @override_settings(LOCALE_PATHS=(os.path.join(test_dir, 'locale'),)) def test_percent_symbol_escaping(self): - from django.template import Template, Context - call_command('compilemessages', locale=[self.LOCALE], stdout=StringIO()) - with translation.override(self.LOCALE): - t = Template('{% load i18n %}{% trans "Looks like a str fmt spec %% o but shouldn\'t be interpreted as such" %}') - rendered = t.render(Context({})) - self.assertEqual(rendered, 'IT translation contains %% for the above string') + with override_settings(LOCALE_PATHS=(os.path.join(self.test_dir, 'locale'),)): + from django.template import Template, Context + call_command('compilemessages', locale=[self.LOCALE], stdout=StringIO()) + with translation.override(self.LOCALE): + t = Template('{% load i18n %}{% trans "Looks like a str fmt spec %% o but shouldn\'t be interpreted as such" %}') + rendered = t.render(Context({})) + self.assertEqual(rendered, 'IT translation contains %% for the above string') - t = Template('{% load i18n %}{% trans "Completed 50%% of all the tasks" %}') - rendered = t.render(Context({})) - self.assertEqual(rendered, 'IT translation of Completed 50%% of all the tasks') + t = Template('{% load i18n %}{% trans "Completed 50%% of all the tasks" %}') + rendered = t.render(Context({})) + self.assertEqual(rendered, 'IT translation of Completed 50%% of all the tasks') -@override_settings(LOCALE_PATHS=(os.path.join(test_dir, 'locale'),)) class MultipleLocaleCompilationTests(MessageCompilationTests): + MO_FILE_HR = None MO_FILE_FR = None def setUp(self): super(MultipleLocaleCompilationTests, self).setUp() - localedir = os.path.join(test_dir, 'locale') + localedir = os.path.join(self.test_dir, 'locale') self.MO_FILE_HR = os.path.join(localedir, 'hr/LC_MESSAGES/django.mo') self.MO_FILE_FR = os.path.join(localedir, 'fr/LC_MESSAGES/django.mo') self.addCleanup(self.rmfile, os.path.join(localedir, self.MO_FILE_HR)) self.addCleanup(self.rmfile, os.path.join(localedir, self.MO_FILE_FR)) def test_one_locale(self): - call_command('compilemessages', locale=['hr'], stdout=StringIO()) + with override_settings(LOCALE_PATHS=(os.path.join(self.test_dir, 'locale'),)): + call_command('compilemessages', locale=['hr'], stdout=StringIO()) - self.assertTrue(os.path.exists(self.MO_FILE_HR)) + self.assertTrue(os.path.exists(self.MO_FILE_HR)) def test_multiple_locales(self): - call_command('compilemessages', locale=['hr', 'fr'], stdout=StringIO()) + with override_settings(LOCALE_PATHS=(os.path.join(self.test_dir, 'locale'),)): + call_command('compilemessages', locale=['hr', 'fr'], stdout=StringIO()) - self.assertTrue(os.path.exists(self.MO_FILE_HR)) - self.assertTrue(os.path.exists(self.MO_FILE_FR)) + self.assertTrue(os.path.exists(self.MO_FILE_HR)) + self.assertTrue(os.path.exists(self.MO_FILE_FR)) + + +class ExcludedLocaleCompilationTests(MessageCompilationTests): + + test_dir = os.path.abspath(os.path.join(os.path.dirname(upath(__file__)), 'exclude')) + + MO_FILE = 'locale/%s/LC_MESSAGES/django.mo' + + def setUp(self): + super(ExcludedLocaleCompilationTests, self).setUp() + + shutil.copytree('canned_locale', 'locale') + self.addCleanup(self._rmrf, os.path.join(self.test_dir, 'locale')) + + def test_one_locale_excluded(self): + call_command('compilemessages', exclude=['it'], stdout=StringIO()) + self.assertTrue(os.path.exists(self.MO_FILE % 'en')) + self.assertTrue(os.path.exists(self.MO_FILE % 'fr')) + self.assertFalse(os.path.exists(self.MO_FILE % 'it')) + + def test_multiple_locales_excluded(self): + call_command('compilemessages', exclude=['it', 'fr'], stdout=StringIO()) + self.assertTrue(os.path.exists(self.MO_FILE % 'en')) + self.assertFalse(os.path.exists(self.MO_FILE % 'fr')) + self.assertFalse(os.path.exists(self.MO_FILE % 'it')) + + def test_one_locale_excluded_with_locale(self): + call_command('compilemessages', locale=['en', 'fr'], exclude=['fr'], stdout=StringIO()) + self.assertTrue(os.path.exists(self.MO_FILE % 'en')) + self.assertFalse(os.path.exists(self.MO_FILE % 'fr')) + self.assertFalse(os.path.exists(self.MO_FILE % 'it')) + + def test_multiple_locales_excluded_with_locale(self): + call_command('compilemessages', locale=['en', 'fr', 'it'], exclude=['fr', 'it'], + stdout=StringIO()) + self.assertTrue(os.path.exists(self.MO_FILE % 'en')) + self.assertFalse(os.path.exists(self.MO_FILE % 'fr')) + self.assertFalse(os.path.exists(self.MO_FILE % 'it')) class CompilationErrorHandling(MessageCompilationTests): @@ -124,7 +171,7 @@ class CompilationErrorHandling(MessageCompilationTests): def setUp(self): super(CompilationErrorHandling, self).setUp() - self.addCleanup(self.rmfile, os.path.join(test_dir, self.MO_FILE)) + self.addCleanup(self.rmfile, os.path.join(self.test_dir, self.MO_FILE)) def test_error_reported_by_msgfmt(self): with self.assertRaises(CommandError): diff --git a/tests/i18n/test_extraction.py b/tests/i18n/test_extraction.py index f0a7b35fae..717ece6c9d 100644 --- a/tests/i18n/test_extraction.py +++ b/tests/i18n/test_extraction.py @@ -5,6 +5,7 @@ import io import os import re import shutil +import time from unittest import SkipTest, skipUnless import warnings @@ -27,12 +28,12 @@ has_xgettext = find_command('xgettext') @skipUnless(has_xgettext, 'xgettext is mandatory for extraction tests') class ExtractorTests(SimpleTestCase): + test_dir = os.path.abspath(os.path.join(os.path.dirname(upath(__file__)), 'commands')) + PO_FILE = 'locale/%s/LC_MESSAGES/django.po' % LOCALE def setUp(self): self._cwd = os.getcwd() - self.test_dir = os.path.abspath( - os.path.join(os.path.dirname(upath(__file__)), 'commands')) def _rmrf(self, dname): if os.path.commonprefix([self.test_dir, os.path.abspath(dname)]) != self.test_dir: @@ -103,6 +104,20 @@ class ExtractorTests(SimpleTestCase): """Check the opposite of assertLocationComment()""" return self._assertPoLocComment(False, po_filename, line_number, *comment_parts) + def assertRecentlyModified(self, path): + """ + Assert that file was recently modified (modification time was less than 10 seconds ago). + """ + delta = time.time() - os.stat(path).st_mtime + self.assertLess(delta, 10, "%s was recently modified" % path) + + def assertNotRecentlyModified(self, path): + """ + Assert that file was not recently modified (modification time was more than 10 seconds ago). + """ + delta = time.time() - os.stat(path).st_mtime + self.assertGreater(delta, 10, "%s wasn't recently modified" % path) + class BasicExtractorTests(ExtractorTests): @@ -402,6 +417,7 @@ class SymlinkExtractorTests(ExtractorTests): class CopyPluralFormsExtractorTests(ExtractorTests): + PO_FILE_ES = 'locale/es/LC_MESSAGES/django.po' def tearDown(self): @@ -527,7 +543,56 @@ class MultipleLocaleExtractionTests(ExtractorTests): self.assertTrue(os.path.exists(self.PO_FILE_DE)) +class ExcludedLocaleExtractionTests(ExtractorTests): + + LOCALES = ['en', 'fr', 'it'] + PO_FILE = 'locale/%s/LC_MESSAGES/django.po' + + test_dir = os.path.abspath(os.path.join(os.path.dirname(upath(__file__)), 'exclude')) + + def _set_times_for_all_po_files(self): + """ + Set access and modification times to the Unix epoch time for all the .po files. + """ + for locale in self.LOCALES: + os.utime(self.PO_FILE % locale, (0, 0)) + + def setUp(self): + super(ExcludedLocaleExtractionTests, self).setUp() + + os.chdir(self.test_dir) # ExtractorTests.tearDown() takes care of restoring. + shutil.copytree('canned_locale', 'locale') + self._set_times_for_all_po_files() + self.addCleanup(self._rmrf, os.path.join(self.test_dir, 'locale')) + + def test_one_locale_excluded(self): + management.call_command('makemessages', exclude=['it'], stdout=StringIO()) + self.assertRecentlyModified(self.PO_FILE % 'en') + self.assertRecentlyModified(self.PO_FILE % 'fr') + self.assertNotRecentlyModified(self.PO_FILE % 'it') + + def test_multiple_locales_excluded(self): + management.call_command('makemessages', exclude=['it', 'fr'], stdout=StringIO()) + self.assertRecentlyModified(self.PO_FILE % 'en') + self.assertNotRecentlyModified(self.PO_FILE % 'fr') + self.assertNotRecentlyModified(self.PO_FILE % 'it') + + def test_one_locale_excluded_with_locale(self): + management.call_command('makemessages', locale=['en', 'fr'], exclude=['fr'], stdout=StringIO()) + self.assertRecentlyModified(self.PO_FILE % 'en') + self.assertNotRecentlyModified(self.PO_FILE % 'fr') + self.assertNotRecentlyModified(self.PO_FILE % 'it') + + def test_multiple_locales_excluded_with_locale(self): + management.call_command('makemessages', locale=['en', 'fr', 'it'], exclude=['fr', 'it'], + stdout=StringIO()) + self.assertRecentlyModified(self.PO_FILE % 'en') + self.assertNotRecentlyModified(self.PO_FILE % 'fr') + self.assertNotRecentlyModified(self.PO_FILE % 'it') + + class CustomLayoutExtractionTests(ExtractorTests): + def setUp(self): self._cwd = os.getcwd() self.test_dir = os.path.join(os.path.dirname(upath(__file__)), 'project_dir')