Fixed #22328 -- Added --exclude option to compilemessages and makemessages.

This commit is contained in:
Ana Krivokapic 2014-03-24 14:03:06 +01:00 committed by Loic Bistuer
parent d1f93e9c1e
commit 0707b824fe
12 changed files with 299 additions and 41 deletions

View File

@ -367,6 +367,7 @@ answer newbie questions, and generally made Django that much better:
Martin Kosír <martin@martinkosir.net> Martin Kosír <martin@martinkosir.net>
Arthur Koziel <http://arthurkoziel.com> Arthur Koziel <http://arthurkoziel.com>
Meir Kriheli <http://mksoft.co.il/> Meir Kriheli <http://mksoft.co.il/>
Ana Krivokapic <https://github.com/infraredgirl>
Bruce Kroeze <http://coderseye.com/> Bruce Kroeze <http://coderseye.com/>
krzysiek.pawlik@silvermedia.pl krzysiek.pawlik@silvermedia.pl
konrad@gwu.edu konrad@gwu.edu

View File

@ -1,6 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import codecs import codecs
import glob
import os import os
from optparse import make_option from optparse import make_option
@ -30,8 +31,11 @@ def is_writable(path):
class Command(BaseCommand): class Command(BaseCommand):
option_list = BaseCommand.option_list + ( option_list = BaseCommand.option_list + (
make_option('--locale', '-l', dest='locale', action='append', 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.'), 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.' help = 'Compiles .po files to .mo files for use with builtin gettext support.'
@ -43,6 +47,7 @@ class Command(BaseCommand):
def handle(self, **options): def handle(self, **options):
locale = options.get('locale') locale = options.get('locale')
exclude = options.get('exclude')
self.verbosity = int(options.get('verbosity')) self.verbosity = int(options.get('verbosity'))
if find_command(self.program) is None: if find_command(self.program) is None:
@ -62,9 +67,19 @@ class Command(BaseCommand):
"checkout or your project or app tree, or with " "checkout or your project or app tree, or with "
"the settings module specified.") "the settings module specified.")
# Build locale list
all_locales = []
for basedir in basedirs: for basedir in basedirs:
if locale: locale_dirs = filter(os.path.isdir, glob.glob('%s/*' % basedir))
dirs = [os.path.join(basedir, l, 'LC_MESSAGES') for l in locale] 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: else:
dirs = [basedir] dirs = [basedir]
locations = [] locations = []
@ -90,8 +105,8 @@ class Command(BaseCommand):
# Check writability on first location # Check writability on first location
if i == 0 and not is_writable(npath(base_path + '.mo')): if i == 0 and not is_writable(npath(base_path + '.mo')):
self.stderr.write("The po files under %s are in a seemingly not " self.stderr.write("The po files under %s are in a seemingly not writable location. "
"writable location. mo files will not be updated/created." % dirpath) "mo files will not be updated/created." % dirpath)
return return
args = [self.program] + self.program_options + ['-o', args = [self.program] + self.program_options + ['-o',

View File

@ -160,9 +160,11 @@ def write_pot_file(potfile, msgs):
class Command(NoArgsCommand): class Command(NoArgsCommand):
option_list = NoArgsCommand.option_list + ( 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). ' help='Creates or updates the message files for the given locale(s) (e.g. pt_BR). '
'Can be used multiple times.'), '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', make_option('--domain', '-d', default='django', dest='domain',
help='The domain of the message files (default: "django").'), help='The domain of the message files (default: "django").'),
make_option('--all', '-a', action='store_true', dest='all', 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 " "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 " "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 " "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 requires_system_checks = False
leave_locale_alone = True leave_locale_alone = True
@ -201,6 +203,7 @@ class Command(NoArgsCommand):
def handle_noargs(self, *args, **options): def handle_noargs(self, *args, **options):
locale = options.get('locale') locale = options.get('locale')
exclude = options.get('exclude')
self.domain = options.get('domain') self.domain = options.get('domain')
self.verbosity = int(options.get('verbosity')) self.verbosity = int(options.get('verbosity'))
process_all = options.get('all') process_all = options.get('all')
@ -235,7 +238,7 @@ class Command(NoArgsCommand):
exts = extensions if extensions else ['html', 'txt'] exts = extensions if extensions else ['html', 'txt']
self.extensions = handle_extensions(exts) 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." % ( raise CommandError("Type '%s help %s' for usage information." % (
os.path.basename(sys.argv[0]), sys.argv[1])) os.path.basename(sys.argv[0]), sys.argv[1]))
@ -270,12 +273,16 @@ class Command(NoArgsCommand):
os.makedirs(self.default_locale_path) os.makedirs(self.default_locale_path)
# Build locale list # 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)) locale_dirs = filter(os.path.isdir, glob.glob('%s/*' % self.default_locale_path))
locales = [os.path.basename(l) for l in locale_dirs] 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: if locales:
check_programs('msguniq', 'msgmerge', 'msgattrib') check_programs('msguniq', 'msgmerge', 'msgattrib')

View File

@ -21,7 +21,7 @@ script found at the top level of each Django project directory.
.BI cleanup .BI cleanup
Cleans out old data from the database (only expired sessions at the moment). Cleans out old data from the database (only expired sessions at the moment).
.TP .TP
.BI "compilemessages [" "\-\-locale=LOCALE" "]" .BI "compilemessages [" "\-\-locale=LOCALE" "] [" "\-\-exclude=LOCALE" "]"
Compiles .po files to .mo files for use with builtin gettext support. Compiles .po files to .mo files for use with builtin gettext support.
.TP .TP
.BI "createcachetable [" "tablename" "]" .BI "createcachetable [" "tablename" "]"
@ -59,7 +59,7 @@ Executes
.B sqlall .B sqlall
for the given app(s) in the current database. for the given app(s) in the current database.
.TP .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 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 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. 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 .I \-l, \-\-locale=LOCALE
The locale to process when using makemessages or compilemessages. The locale to process when using makemessages or compilemessages.
.TP .TP
.I \-e, \-\-exclude=LOCALE
The locale to exclude from processing when using makemessages or compilemessages.
.TP
.I \-d, \-\-domain=DOMAIN .I \-d, \-\-domain=DOMAIN
The domain of the message files (default: "django") when using makemessages. The domain of the message files (default: "django") when using makemessages.
.TP .TP

View File

@ -141,12 +141,22 @@ the builtin gettext support. See :doc:`/topics/i18n/index`.
Use the :djadminopt:`--locale` option (or its shorter version ``-l``) to Use the :djadminopt:`--locale` option (or its shorter version ``-l``) to
specify the locale(s) to process. If not provided, all locales are processed. 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:: Example usage::
django-admin.py compilemessages --locale=pt_BR django-admin.py compilemessages --locale=pt_BR
django-admin.py compilemessages --locale=pt_BR --locale=fr django-admin.py compilemessages --locale=pt_BR --locale=fr
django-admin.py compilemessages -l pt_BR django-admin.py compilemessages -l pt_BR
django-admin.py compilemessages -l pt_BR -l fr 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 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 Use the :djadminopt:`--locale` option (or its shorter version ``-l``) to
specify the locale(s) to process. 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:: Example usage::
django-admin.py makemessages --locale=pt_BR django-admin.py makemessages --locale=pt_BR
django-admin.py makemessages --locale=pt_BR --locale=fr django-admin.py makemessages --locale=pt_BR --locale=fr
django-admin.py makemessages -l pt_BR django-admin.py makemessages -l pt_BR
django-admin.py makemessages -l pt_BR -l fr 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 .. versionchanged:: 1.7

View File

@ -128,6 +128,10 @@ Management Commands
* :djadmin:`dumpdata` now has the option :djadminopt:`--output` which allows * :djadmin:`dumpdata` now has the option :djadminopt:`--output` which allows
specifying the file to which the serialized data is written. 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 Models
^^^^^^ ^^^^^^

View File

@ -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.")

View File

@ -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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View File

@ -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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View File

@ -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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View File

@ -1,4 +1,5 @@
import os import os
import shutil
import stat import stat
import unittest import unittest
@ -10,17 +11,23 @@ from django.utils import translation
from django.utils._os import upath from django.utils._os import upath
from django.utils.six import StringIO 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') has_msgfmt = find_command('msgfmt')
@unittest.skipUnless(has_msgfmt, 'msgfmt is mandatory for compilation tests') @unittest.skipUnless(has_msgfmt, 'msgfmt is mandatory for compilation tests')
class MessageCompilationTests(SimpleTestCase): class MessageCompilationTests(SimpleTestCase):
test_dir = os.path.abspath(os.path.join(os.path.dirname(upath(__file__)), 'commands'))
def setUp(self): def setUp(self):
self._cwd = os.getcwd() self._cwd = os.getcwd()
self.addCleanup(os.chdir, self._cwd) 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): def rmfile(self, filepath):
if os.path.exists(filepath): if os.path.exists(filepath):
@ -60,7 +67,7 @@ class PoFileContentsTests(MessageCompilationTests):
def setUp(self): def setUp(self):
super(PoFileContentsTests, self).setUp() 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): def test_percent_symbol_in_po_file(self):
call_command('compilemessages', locale=[self.LOCALE], stdout=StringIO()) call_command('compilemessages', locale=[self.LOCALE], stdout=StringIO())
@ -76,10 +83,10 @@ class PercentRenderingTests(MessageCompilationTests):
def setUp(self): def setUp(self):
super(PercentRenderingTests, self).setUp() 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): def test_percent_symbol_escaping(self):
with override_settings(LOCALE_PATHS=(os.path.join(self.test_dir, 'locale'),)):
from django.template import Template, Context from django.template import Template, Context
call_command('compilemessages', locale=[self.LOCALE], stdout=StringIO()) call_command('compilemessages', locale=[self.LOCALE], stdout=StringIO())
with translation.override(self.LOCALE): with translation.override(self.LOCALE):
@ -92,31 +99,71 @@ class PercentRenderingTests(MessageCompilationTests):
self.assertEqual(rendered, 'IT translation of Completed 50%% of all the tasks') 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): class MultipleLocaleCompilationTests(MessageCompilationTests):
MO_FILE_HR = None MO_FILE_HR = None
MO_FILE_FR = None MO_FILE_FR = None
def setUp(self): def setUp(self):
super(MultipleLocaleCompilationTests, self).setUp() 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_HR = os.path.join(localedir, 'hr/LC_MESSAGES/django.mo')
self.MO_FILE_FR = os.path.join(localedir, 'fr/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_HR))
self.addCleanup(self.rmfile, os.path.join(localedir, self.MO_FILE_FR)) self.addCleanup(self.rmfile, os.path.join(localedir, self.MO_FILE_FR))
def test_one_locale(self): def test_one_locale(self):
with override_settings(LOCALE_PATHS=(os.path.join(self.test_dir, 'locale'),)):
call_command('compilemessages', locale=['hr'], stdout=StringIO()) 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): def test_multiple_locales(self):
with override_settings(LOCALE_PATHS=(os.path.join(self.test_dir, 'locale'),)):
call_command('compilemessages', locale=['hr', 'fr'], stdout=StringIO()) 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_HR))
self.assertTrue(os.path.exists(self.MO_FILE_FR)) 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): class CompilationErrorHandling(MessageCompilationTests):
LOCALE = 'ja' LOCALE = 'ja'
@ -124,7 +171,7 @@ class CompilationErrorHandling(MessageCompilationTests):
def setUp(self): def setUp(self):
super(CompilationErrorHandling, self).setUp() 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): def test_error_reported_by_msgfmt(self):
with self.assertRaises(CommandError): with self.assertRaises(CommandError):

View File

@ -5,6 +5,7 @@ import io
import os import os
import re import re
import shutil import shutil
import time
from unittest import SkipTest, skipUnless from unittest import SkipTest, skipUnless
import warnings import warnings
@ -27,12 +28,12 @@ has_xgettext = find_command('xgettext')
@skipUnless(has_xgettext, 'xgettext is mandatory for extraction tests') @skipUnless(has_xgettext, 'xgettext is mandatory for extraction tests')
class ExtractorTests(SimpleTestCase): 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 PO_FILE = 'locale/%s/LC_MESSAGES/django.po' % LOCALE
def setUp(self): def setUp(self):
self._cwd = os.getcwd() self._cwd = os.getcwd()
self.test_dir = os.path.abspath(
os.path.join(os.path.dirname(upath(__file__)), 'commands'))
def _rmrf(self, dname): def _rmrf(self, dname):
if os.path.commonprefix([self.test_dir, os.path.abspath(dname)]) != self.test_dir: 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()""" """Check the opposite of assertLocationComment()"""
return self._assertPoLocComment(False, po_filename, line_number, *comment_parts) 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): class BasicExtractorTests(ExtractorTests):
@ -402,6 +417,7 @@ class SymlinkExtractorTests(ExtractorTests):
class CopyPluralFormsExtractorTests(ExtractorTests): class CopyPluralFormsExtractorTests(ExtractorTests):
PO_FILE_ES = 'locale/es/LC_MESSAGES/django.po' PO_FILE_ES = 'locale/es/LC_MESSAGES/django.po'
def tearDown(self): def tearDown(self):
@ -527,7 +543,56 @@ class MultipleLocaleExtractionTests(ExtractorTests):
self.assertTrue(os.path.exists(self.PO_FILE_DE)) 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): class CustomLayoutExtractionTests(ExtractorTests):
def setUp(self): def setUp(self):
self._cwd = os.getcwd() self._cwd = os.getcwd()
self.test_dir = os.path.join(os.path.dirname(upath(__file__)), 'project_dir') self.test_dir = os.path.join(os.path.dirname(upath(__file__)), 'project_dir')