diff --git a/.gitignore b/.gitignore index 5ed36ad..ca6bfe9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ pip-log.txt .DS_Store *.egg-info +.tox/ diff --git a/.travis.yml b/.travis.yml index 35748bf..b9b9370 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,11 @@ language: python -python: 2.6 -env: - - TOX_ENV=py26-1.4 - - TOX_ENV=py26-1.5 - - TOX_ENV=py26-1.6 - - TOX_ENV=py27-1.4 - - TOX_ENV=py27-1.5 - - TOX_ENV=py27-1.6 +matrix: + include: + - python: 2.7 + env: TOXENV=py27-1.5,py27-1.6,py27-1.7,py27-1.8 + - python: 3.6 + env: TOXENV=py36-1.8 install: - pip install tox script: - - tox -e $TOX_ENV + - tox diff --git a/README.rst b/README.rst index 8177d16..ffdda36 100644 --- a/README.rst +++ b/README.rst @@ -11,7 +11,7 @@ and the Babel library. * Author: Wil Clouser and contributors_ * Licence: BSD -* Compatibility: Python 2.6 and 2.7, Django 1.4, 1.5 and 1.6 +* Compatibility: Python 2.7 and 3.6, Django 1.4, 1.5 and 1.6, 1.8, 1.8 * Requirements: django, babel, jinja2, jingo and translate-toolkit * Project URL: https://github.com/clouserw/tower * Documentation: http://tower.readthedocs.org/en/latest/ diff --git a/examples/tower-project/settings.py b/examples/tower-project/settings.py index 744dced..d7b4af3 100644 --- a/examples/tower-project/settings.py +++ b/examples/tower-project/settings.py @@ -31,3 +31,12 @@ def JINJA_CONFIG(): } SECRET_KEY = 'useless not secret key used for tests' + +MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +) diff --git a/requirements.txt b/requirements.txt index 41dabba..e2f02a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ babel==1.3 jinja2 translate-toolkit -e git://github.com/jbalogh/jingo.git#egg=jingo +future # Requirements for running the tests django-nose==1.1 diff --git a/run_tests.py b/run_tests.py index 74b0fae..26649b1 100755 --- a/run_tests.py +++ b/run_tests.py @@ -18,4 +18,10 @@ from django.core.management import call_command # Run the equivalent of "django-admin.py test" +try: + from django import setup + setup() +except ImportError: + # Django 1.6 and below does not require setup() + pass call_command('test') diff --git a/setup.py b/setup.py index 2d939e6..439db29 100644 --- a/setup.py +++ b/setup.py @@ -23,8 +23,8 @@ 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.6', 'Topic :: Software Development :: Libraries :: Python Modules', ] ) diff --git a/tower/__init__.py b/tower/__init__.py index 95245b6..bd29e67 100644 --- a/tower/__init__.py +++ b/tower/__init__.py @@ -1,11 +1,14 @@ +from past.builtins import basestring +from builtins import object +from importlib import import_module import copy import gettext import re import django from django.conf import settings +from django.utils import six from django.utils.functional import lazy -from django.utils.importlib import import_module from django.utils.translation import (trans_real as django_trans, ugettext as django_ugettext, ungettext as django_nugettext) @@ -52,8 +55,9 @@ def ungettext(singular, plural, number, context=None): return plural_stripped return ret -ugettext_lazy = lazy(ugettext, unicode) -ungettext_lazy = lazy(ungettext, unicode) + +ugettext_lazy = lazy(ugettext, six.text_type) +ungettext_lazy = lazy(ungettext, six.text_type) def add_context(context, message): @@ -87,7 +91,11 @@ class Translation(object): ungettext = staticmethod(ungettext) import jingo - jingo.env.install_gettext_translations(Translation) + try: + jingo.env.install_gettext_translations(Translation) + except Exception: + # jingo 0.8 requires get_env() instead. + jingo.get_env().install_gettext_translations(Translation) def activate(locale): @@ -116,6 +124,7 @@ def _activate(locale): # Django caches the translation objects here t = django_trans._translations.get(locale, None) + if t is not None: return t @@ -127,7 +136,9 @@ def _activate(locale): # our foreign catalog into en-US. Since Django stuck the en-US catalog # into its cache for this locale, we have to update that too. t = copy.deepcopy(django_trans.translation(locale)) - t.set_language(locale) + if hasattr(t, 'set_language'): + # not required for django 1.8+ + t.set_language(locale) try: # When trying to load css, js, and images through the Django server # gettext() throws an exception saying it can't find the .mo files. I @@ -144,8 +155,8 @@ def _activate(locale): # If you've got extra .mo files to load, this is the place. path = import_module(settings_module).path domain = getattr(settings, 'TEXT_DOMAIN', 'messages') - bonus = gettext.translation(domain, path('locale'), [locale], - django_trans.DjangoTranslation) + bonus = gettext.translation(domain=domain, localedir=path('locale'), languages=[locale], + codeset='utf-8') t.merge(bonus) # Overwrite t (defaults to en-US) with our real locale's plural form diff --git a/tower/management/commands/amalgamate.py b/tower/management/commands/amalgamate.py index c430bb5..0d29266 100644 --- a/tower/management/commands/amalgamate.py +++ b/tower/management/commands/amalgamate.py @@ -1,3 +1,4 @@ +from __future__ import print_function import os import sys from subprocess import Popen @@ -58,10 +59,10 @@ def handle(self, *args, **options): 'messages.po') if not os.path.isfile(r_messages): - print " Can't find (%s). Skipping..." % (r_messages) + print(" Can't find (%s). Skipping..." % (r_messages)) continue - print "Mushing python strings into messages.po for %s" % (locale) + print("Mushing python strings into messages.po for %s" % (locale)) # Step 3: Merge our new combined .pot with the .po file if locale == "en_US": @@ -90,7 +91,7 @@ def handle(self, *args, **options): # commands in the middle of Step 3. for domain in standalone_domains: - print "Merging %s strings to each locale..." % domain + print("Merging %s strings to each locale..." % domain) z_domain_keys = os.path.join(locale_dir, 'z-%s.pot' % domain) if not os.path.isfile(z_domain_keys): sys.exit("Can't find z-%s.pot" % domain) @@ -104,11 +105,11 @@ def handle(self, *args, **options): 'z-%s.po' % domain) if not os.path.isfile(z_domain_messages): - print " Can't find (%s). Creating..." % (z_domain_messages) + print(" Can't find (%s). Creating..." % (z_domain_messages)) t = open(z_domain_messages, 'w') t.close() - print "Merging z-%s.po for %s" % (domain, locale) + print("Merging z-%s.po for %s" % (domain, locale)) z_domain_keys_file = open(z_domain_keys) @@ -133,6 +134,6 @@ def handle(self, *args, **options): p4.communicate() mergeme.close() - print "Domain %s finished" % domain + print("Domain %s finished" % domain) - print "All finished" + print("All finished") diff --git a/tower/management/commands/extract.py b/tower/management/commands/extract.py index be2f586..c72bf82 100644 --- a/tower/management/commands/extract.py +++ b/tower/management/commands/extract.py @@ -1,3 +1,6 @@ +from __future__ import print_function +from io import StringIO + import os import tempfile from optparse import make_option @@ -72,9 +75,9 @@ def create_pofile_from_babel(extracted): if settings.TOWER_ADD_HEADERS: catalog = po.pofile() else: - catalog = po.pofile(inputfile="") + catalog = po.pofile(inputfile=StringIO(u"")) except AttributeError: - catalog = po.pofile(inputfile="") + catalog = po.pofile(inputfile=StringIO(u"")) for extracted_unit in extracted: # Babel 1.3 has an additional value: context. @@ -121,7 +124,7 @@ def handle(self, *args, **options): os.makedirs(outputdir) if domains == "all": - domains = settings.DOMAIN_METHODS.keys() + domains = list(settings.DOMAIN_METHODS.keys()) else: domains = [domains] @@ -129,11 +132,11 @@ def handle(self, *args, **options): def callback(filename, method, options): if method != 'ignore': - print " %s" % filename + print(" %s" % filename) for domain in domains: - print "Extracting all strings in domain %s..." % (domain) + print("Extracting all strings in domain %s..." % (domain)) methods = settings.DOMAIN_METHODS[domain] extracted = extract_from_dir(root, @@ -179,4 +182,4 @@ def callback(filename, method, options): for i in [x for x in domains if x not in standalone_domains]: os.remove(os.path.join(outputdir, '%s.pot' % i)) - print 'done' + print('done') diff --git a/tower/management/commands/merge.py b/tower/management/commands/merge.py index 6d6a52b..4fd063f 100644 --- a/tower/management/commands/merge.py +++ b/tower/management/commands/merge.py @@ -1,3 +1,4 @@ +from __future__ import print_function import os import sys from optparse import make_option @@ -50,7 +51,7 @@ def handle(self, *args, **options): for domain in domains: - print "Merging %s strings to each locale..." % domain + print("Merging %s strings to each locale..." % domain) domain_pot = os.path.join(locale_dir, 'templates', 'LC_MESSAGES', '%s.pot' % domain) if not os.path.isfile(domain_pot): @@ -69,7 +70,7 @@ def handle(self, *args, **options): '%s.po' % domain) if not os.path.isfile(domain_po): - print " Can't find (%s). Creating..." % (domain_po) + print(" Can't find (%s). Creating..." % (domain_po)) if not call(["which", "msginit"], stdout=PIPE) == 0: raise Exception("You do not have gettext installed.") p1 = Popen(["msginit", @@ -80,7 +81,7 @@ def handle(self, *args, **options): "--width=200",]) p1.communicate() - print "Merging %s.po for %s" % (domain, locale) + print("Merging %s.po for %s" % (domain, locale)) domain_pot_file = open(domain_pot) @@ -100,13 +101,13 @@ def handle(self, *args, **options): domain_po, "-"] if os.path.isfile(compendium): - print "(using a compendium)" + print("(using a compendium)") command.insert(1, '--compendium=%s' % compendium) p3 = Popen(command, stdin=mergeme) p3.communicate() mergeme.close() - print "Domain %s finished" % domain + print("Domain %s finished" % domain) - print "All finished" + print("All finished") Command.help = Command.__doc__ diff --git a/tower/tests/test_l10n.py b/tower/tests/test_l10n.py index 9d2c9b0..4b09bbf 100644 --- a/tower/tests/test_l10n.py +++ b/tower/tests/test_l10n.py @@ -1,4 +1,7 @@ -from cStringIO import StringIO +from future import standard_library +standard_library.install_aliases() +from builtins import str +from io import StringIO, BytesIO import django from django.utils import translation @@ -43,10 +46,19 @@ def teardown(): tower.deactivate_all() +def get_jingo_env(): + try: + return jingo.env + except Exception: + # jingo 0.8 requires get_env() instead. + return jingo.get_env() + def test_install_jinja_translations(): - jingo.env.install_null_translations() + env = get_jingo_env() + + env.install_null_translations() tower.activate('xx') - eq_(jingo.env.globals['gettext'], _) + eq_(env.globals['gettext'], _) @patch.object(tower, 'INSTALL_JINJA_TRANSLATIONS', False) @@ -55,9 +67,11 @@ def test_no_install_jinja_translations(): Setting `TOWER_INSTALL_JINJA_TRANSLATIONS` to False should skip setting the gettext and ngettext functions in the Jinja2 environment. """ - jingo.env.install_null_translations() + env = get_jingo_env() + + env.install_null_translations() tower.activate('xx') - ok_(jingo.env.globals['gettext'] != _) + ok_(env.globals['gettext'] != _) @with_setup(setup, teardown) @@ -116,16 +130,16 @@ def test_ungettext_not_found(): @with_setup(setup, teardown) def test_ugettext_lazy(): - eq_(unicode(_lazy_strings['nocontext']), 'you ran a test!') - eq_(unicode(_lazy_strings['context']), 'What time is it? (context=1)') + eq_(str(_lazy_strings['nocontext']), 'you ran a test!') + eq_(str(_lazy_strings['context']), 'What time is it? (context=1)') @with_setup(setup, teardown) def test_ungettext_lazy(): - eq_(unicode(n_lazy_strings['s_nocontext']), 'you found a light!') - eq_(unicode(n_lazy_strings['p_nocontext']), 'you found a pile of lights!') - eq_(unicode(n_lazy_strings['s_context']), '%d poodle (context=1)') - eq_(unicode(n_lazy_strings['p_context']), '%d poodles (context=1)') + eq_(str(n_lazy_strings['s_nocontext']), 'you found a light!') + eq_(str(n_lazy_strings['p_nocontext']), 'you found a pile of lights!') + eq_(str(n_lazy_strings['s_context']), '%d poodle (context=1)') + eq_(str(n_lazy_strings['p_context']), '%d poodles (context=1)') def test_add_context(): @@ -262,47 +276,59 @@ def test_template_gettext_functions(): def test_extract_tower_python(): - fileobj = StringIO(TEST_PO_INPUT) + fileobj = BytesIO(TEST_PO_INPUT) method = 'tower.extract_tower_python' output = fake_extract_from_dir(filename="filename", fileobj=fileobj, method=method) # god help you if these are ever unequal - eq_(TEST_PO_OUTPUT, unicode(create_pofile_from_babel(output))) + out = BytesIO() + create_pofile_from_babel(output).serialize(out) + out.seek(0) + eq_(TEST_PO_OUTPUT, out.read()) def test_extract_tower_template(): - fileobj = StringIO(TEST_TEMPLATE_INPUT) + fileobj = BytesIO(TEST_TEMPLATE_INPUT) method = 'tower.extract_tower_template' output = fake_extract_from_dir(filename="filename", fileobj=fileobj, method=method) # god help you if these are ever unequal - eq_(TEST_TEMPLATE_OUTPUT, unicode(create_pofile_from_babel(output))) + out = BytesIO() + create_pofile_from_babel(output).serialize(out) + out.seek(0) + eq_(TEST_TEMPLATE_OUTPUT, out.read()) def test_extract_tower_python_backwards_compatible(): - fileobj = StringIO(TEST_PO_INPUT) + fileobj = BytesIO(TEST_PO_INPUT) method = 'tower.management.commands.extract.extract_tower_python' output = fake_extract_from_dir(filename="filename", fileobj=fileobj, method=method) # god help you if these are ever unequal - eq_(TEST_PO_OUTPUT, unicode(create_pofile_from_babel(output))) + out = BytesIO() + create_pofile_from_babel(output).serialize(out) + out.seek(0) + eq_(TEST_PO_OUTPUT, out.read()) def test_extract_tower_template_backwards_compatible(): - fileobj = StringIO(TEST_TEMPLATE_INPUT) + fileobj = BytesIO(TEST_TEMPLATE_INPUT) method = 'tower.management.commands.extract.extract_tower_template' output = fake_extract_from_dir(filename="filename", fileobj=fileobj, method=method) # god help you if these are ever unequal - eq_(TEST_TEMPLATE_OUTPUT, unicode(create_pofile_from_babel(output))) + out = BytesIO() + create_pofile_from_babel(output).serialize(out) + out.seek(0) + eq_(TEST_TEMPLATE_OUTPUT, out.read()) -TEST_PO_INPUT = """ +TEST_PO_INPUT = b""" # Make sure multiple contexts stay separate _('fligtar') _('fligtar', 'atwork') @@ -328,7 +354,7 @@ def test_extract_tower_template_backwards_compatible(): _lazy('a lazy string') """ -TEST_PO_OUTPUT = """\ +TEST_PO_OUTPUT = b"""\ #: filename:3 msgid "fligtar" msgstr "" @@ -371,7 +397,7 @@ def test_extract_tower_template_backwards_compatible(): msgstr "" """ -TEST_TEMPLATE_INPUT = """ +TEST_TEMPLATE_INPUT = b""" {{ _('sunshine') }} {{ _('sunshine', 'nothere') }} {{ _('sunshine', 'outside') }} @@ -398,7 +424,7 @@ def test_extract_tower_template_backwards_compatible(): {% endtrans %} """ -TEST_TEMPLATE_OUTPUT = """\ +TEST_TEMPLATE_OUTPUT = b"""\ #: filename:2 msgid "sunshine" msgstr "" diff --git a/tox.ini b/tox.ini index 342cc93..72c9cd1 100644 --- a/tox.ini +++ b/tox.ini @@ -1,50 +1,48 @@ [tox] -envlist = py26-1.4, py26-1.5, py26-1.6, py27-1.4, py27-1.5, py27-1.6 -toxworkdir = {homedir}/.tox-tower +envlist = py27-1.5, py27-1.6, py27-1.7, py36-1.8 [testenv] commands = python run_tests.py -deps = -egit+https://github.com/jbalogh/jingo.git#egg=jingo +deps = babel==1.3 jinja2 translate-toolkit - django-nose + django-nose==1.4.3 mock + future -[testenv:py26-1.4] -basepython = python2.6 -deps = - Django==1.4.10 - {[testenv]deps} - -[testenv:py26-1.5] -basepython = python2.6 +[testenv:py27-1.5] +basepython = python2.7 deps = Django==1.5.5 + -egit+https://github.com/jbalogh/jingo.git@v0.7.1#egg=jingo {[testenv]deps} -[testenv:py26-1.6] -basepython = python2.6 +[testenv:py27-1.6] +basepython = python2.7 deps = Django==1.6 + -egit+https://github.com/jbalogh/jingo.git@v0.7.1#egg=jingo {[testenv]deps} -[testenv:py27-1.4] +[testenv:py27-1.7] basepython = python2.7 deps = - Django==1.4.10 + Django==1.7.11 + -egit+https://github.com/jbalogh/jingo.git@v0.7.1#egg=jingo {[testenv]deps} - -[testenv:py27-1.5] +[testenv:py27-1.8] basepython = python2.7 deps = - Django==1.5.5 + Django==1.8 + -egit+https://github.com/jbalogh/jingo.git@v0.8.2#egg=jingo {[testenv]deps} -[testenv:py27-1.6] -basepython = python2.7 +[testenv:py36-1.8] +basepython = python3.6 deps = - Django==1.6 + Django==1.8 + -egit+https://github.com/jbalogh/jingo.git@v0.8.2#egg=jingo {[testenv]deps}