Commit 621c5cf4 authored by Claude Paroz's avatar Claude Paroz
Browse files

Fixed #21732 -- Made compilemessages complain about non-writable location

Instead of crashing with a CommandError, now a non-writable location
of mo files will only make compilemessages complain and continue.
Thanks Ramiro Morales for the review.
parent 203f55b5
Loading
Loading
Loading
Loading
+70 −40
Original line number Diff line number Diff line
@@ -17,10 +17,35 @@ def has_bom(fn):
        sample.startswith(codecs.BOM_UTF16_BE)


def compile_messages(stdout, locale=None):
def is_writable(path):
    # Known side effect: updating file access/modified time to current time if
    # it is writable.
    try:
        with open(path, 'a'):
            os.utime(path, None)
    except (IOError, OSError):
        return False
    return True


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.'),
    )
    help = 'Compiles .po files to .mo files for use with builtin gettext support.'

    requires_system_checks = False
    leave_locale_alone = True
    program = 'msgfmt'
    if find_command(program) is None:
        raise CommandError("Can't find %s. Make sure you have GNU gettext tools 0.15 or newer installed." % program)

    def handle(self, **options):
        locale = options.get('locale')
        self.verbosity = int(options.get('verbosity'))

        if find_command(self.program) is None:
            raise CommandError("Can't find %s. Make sure you have GNU gettext "
                               "tools 0.15 or newer installed." % self.program)

        basedirs = [os.path.join('conf', 'locale'), 'locale']
        if os.environ.get('DJANGO_SETTINGS_MODULE'):
@@ -31,43 +56,48 @@ def compile_messages(stdout, locale=None):
        basedirs = set(map(os.path.abspath, filter(os.path.isdir, basedirs)))

        if not basedirs:
        raise CommandError("This script should be run from the Django Git checkout or your project or app tree, or with the settings module specified.")
            raise CommandError("This script should be run from the Django Git "
                               "checkout or your project or app tree, or with "
                               "the settings module specified.")

        for basedir in basedirs:
            if locale:
                dirs = [os.path.join(basedir, l, 'LC_MESSAGES') for l in locale]
            else:
                dirs = [basedir]
            locations = []
            for ldir in dirs:
                for dirpath, dirnames, filenames in os.walk(ldir):
                for f in filenames:
                    if not f.endswith('.po'):
                        continue
                    stdout.write('processing file %s in %s\n' % (f, dirpath))
                    fn = os.path.join(dirpath, f)
                    if has_bom(fn):
                        raise CommandError("The %s file has a BOM (Byte Order Mark). Django only supports .po files encoded in UTF-8 and without any BOM." % fn)
                    pf = os.path.splitext(fn)[0]
                    args = [program, '--check-format', '-o', npath(pf + '.mo'), npath(pf + '.po')]
                    locations.extend((dirpath, f) for f in filenames if f.endswith('.po'))
            if locations:
                self.compile_messages(locations)

    def compile_messages(self, locations):
        """
        Locations is a list of tuples: [(directory, file), ...]
        """
        for i, (dirpath, f) in enumerate(locations):
            if self.verbosity > 0:
                self.stdout.write('processing file %s in %s\n' % (f, dirpath))
            po_path = os.path.join(dirpath, f)
            if has_bom(po_path):
                raise CommandError("The %s file has a BOM (Byte Order Mark). "
                                   "Django only supports .po files encoded in "
                                   "UTF-8 and without any BOM." % po_path)
            base_path = os.path.splitext(po_path)[0]

            # 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)
                return

            args = [self.program, '--check-format', '-o',
                    npath(base_path + '.mo'), npath(base_path + '.po')]
            output, errors, status = popen_wrapper(args)
            if status:
                if errors:
                            msg = "Execution of %s failed: %s" % (program, errors)
                    msg = "Execution of %s failed: %s" % (self.program, errors)
                else:
                            msg = "Execution of %s failed" % program
                    msg = "Execution of %s failed" % self.program
                raise CommandError(msg)


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.'),
    )
    help = 'Compiles .po files to .mo files for use with builtin gettext support.'

    requires_system_checks = False
    leave_locale_alone = True

    def handle(self, **options):
        locale = options.get('locale')
        compile_messages(self.stdout, locale=locale)
+378 B

File added.

No diff preview for this file type.

+27 −0
Original line number Diff line number Diff line
# 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-01-04 22:05+0100\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:4
msgid "This is a translatable string."
msgstr ""

#: __init__.py:7
msgid "This is another translatable string."
msgstr ""
+14 −0
Original line number Diff line number Diff line
import os
import stat
import unittest

from django.core.management import call_command, CommandError
@@ -37,6 +38,19 @@ class PoFileTests(MessageCompilationTests):
        self.assertIn("file has a BOM (Byte Order Mark)", cm.exception.args[0])
        self.assertFalse(os.path.exists(self.MO_FILE))

    def test_no_write_access(self):
        mo_file_en = 'locale/en/LC_MESSAGES/django.mo'
        err_buffer = StringIO()
        # put file in read-only mode
        old_mode = os.stat(mo_file_en).st_mode
        os.chmod(mo_file_en, stat.S_IREAD)
        try:
            call_command('compilemessages', locale=['en'], stderr=err_buffer, verbosity=0)
            err = err_buffer.getvalue()
            self.assertIn("not writable location", err)
        finally:
            os.chmod(mo_file_en, old_mode)


class PoFileContentsTests(MessageCompilationTests):
    # Ticket #11240