diff --git a/.pep8 b/.pep8 index 4ad0546..d8b79ea 100644 --- a/.pep8 +++ b/.pep8 @@ -1,3 +1,3 @@ [flake8] -ignore = E501 +ignore = E501,W503 exclude = south_migrations,migrations,.venv_*,docs diff --git a/.travis.yml b/.travis.yml index b357bb1..091bf66 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,10 +3,6 @@ services: memcached matrix: include: - - python: "2.7" - env: TOX_ENV=py27-django111 - - python: "3.6" - env: TOX_ENV=py36-django111 - python: "3.6" env: TOX_ENV=py36-django20 - python: "3.6" diff --git a/CHANGES b/CHANGES index efa42ed..f3bf884 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,8 @@ Version 0.9.5 (unreleased) * Added Kyrgyz translation (#239,thanks @Soyuzbek) * Ignore translator context hints checking unmatched variables (#238, #239, thanks @jeancochrane and @mondeja) * Uncheck fuzzy on translation keyup instead of change (#235 @mondeja) +* Allow passing a function itself to the setting ROSETTA_ACCESS_CONTROL (#227, thanks @alvra) +* Dropped support for Django 1.11 and Python 2 Version 0.9.4 diff --git a/docs/settings.rst b/docs/settings.rst index face5ba..abf055d 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -13,7 +13,7 @@ Rosetta can be configured via the following parameters, to be defined in your pr * ``ROSETTA_REQUIRES_AUTH``: Require authentication for all Rosetta views. Defaults to ``True``. * ``ROSETTA_POFILE_WRAP_WIDTH``: Sets the line-length of the edited PO file. Set this to ``0`` to mimic ``makemessage``'s ``--no-wrap`` option. Defaults to ``78``. * ``ROSETTA_STORAGE_CLASS``: See the note below on Storages. Defaults to ``rosetta.storage.CacheRosettaStorage`` -* ``ROSETTA_ACCESS_CONTROL_FUNCTION``: An alternative function that determines if a given user can access the translation views. This function receives a ``user`` as its argument, and returns a boolean specifying whether the passed user is allowed to use Rosetta or not. +* ``ROSETTA_ACCESS_CONTROL_FUNCTION``: An alternative function (string or a callable) that determines if a given user can access the translation views. This function receives a ``user`` as its argument, and returns a boolean specifying whether the passed user is allowed to use Rosetta or not. * ``ROSETTA_LANGUAGE_GROUPS``: Set to ``True`` to enable language-specific groups, which can be used to give different translators access to different languages. Instead of creating a global ``translators`` group, create individual per-language groups, e.g. ``translators-de``, ``translators-fr``, and assign users to these. * ``ROSETTA_CACHE_NAME``: When using ``rosetta.storage.CacheRosettaStorage``, you can store the Rosetta data in a specific cache. This is particularly useful when your ``default`` cache is a ``django.core.cache.backends.dummy.DummyCache`` (which happens on pre-production environments). If unset, it will default to ``rosetta`` if a cache with this name exists, or ``default`` if not. * ``ROSETTA_POFILENAMES``: Defines which po file names are exposed in the web interface. Defaults to ``('django.po', 'djangojs.po')`` diff --git a/rosetta/tests/tests.py b/rosetta/tests/tests.py index d3c28ab..3fd2189 100644 --- a/rosetta/tests/tests.py +++ b/rosetta/tests/tests.py @@ -1,34 +1,27 @@ -# -*- coding: utf-8 -*- import filecmp import hashlib import os import shutil -import vcr -try: - # Python 3 - from urllib.parse import urlencode -except ImportError: - # Python 2 - from urllib import urlencode +from urllib.parse import urlencode -from django.conf import settings -from django.dispatch import receiver -from django.core.exceptions import ImproperlyConfigured -from django.http import Http404 -from django.test import TestCase, RequestFactory, override_settings -from django.test.client import Client from django import VERSION -from django.urls import reverse, resolve +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.dispatch import receiver +from django.http import Http404 +from django.test import RequestFactory, TestCase, override_settings +from django.test.client import Client +from django.urls import resolve, reverse from django.utils.encoding import force_bytes -import six +import six +import vcr +from rosetta import views from rosetta.signals import entry_changed, post_save from rosetta.storage import get_storage -from rosetta import views class RosettaTestCase(TestCase): - def __init__(self, *args, **kwargs): super(RosettaTestCase, self).__init__(*args, **kwargs) self.curdir = os.path.dirname(__file__) @@ -39,9 +32,15 @@ class RosettaTestCase(TestCase): def setUp(self): from django.contrib.auth.models import User - user = User.objects.create_superuser('test_admin', 'test@test.com', 'test_password') - user2 = User.objects.create_superuser('test_admin2', 'test@test2.com', 'test_password') - user3 = User.objects.create_superuser('test_admin3', 'test@test2.com', 'test_password') + user = User.objects.create_superuser( + 'test_admin', 'test@test.com', 'test_password' + ) + user2 = User.objects.create_superuser( + 'test_admin2', 'test@test2.com', 'test_password' + ) + user3 = User.objects.create_superuser( + 'test_admin3', 'test@test2.com', 'test_password' + ) user3.is_staff = False user3.save() @@ -84,15 +83,14 @@ class RosettaTestCase(TestCase): def test_1_ListLoading(self): r = self.client.get(self.third_party_file_list_url) self.assertTrue( - os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') in str(r.content) + os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') + in r.content.decode() ) - @override_settings(ROSETTA_LANGUAGES=( - ('xx', 'dummy language'), - )) + @override_settings(ROSETTA_LANGUAGES=(('xx', 'dummy language'),)) def test_2_PickFile(self): r = self.client.get(self.xx_form_url) - self.assertTrue('dummy language' in str(r.content)) + self.assertTrue('dummy language' in r.content.decode()) def test_3_DownloadZIP(self): kwargs = {'po_filter': 'third-party', 'lang_id': 'xx', 'idx': 0} @@ -101,9 +99,7 @@ class RosettaTestCase(TestCase): self.assertTrue('content-type' in r._headers.keys()) self.assertTrue('application/x-zip' in r._headers.get('content-type')) - @override_settings(ROSETTA_LANGUAGES=( - ('xx', 'dummy language'), - )) + @override_settings(ROSETTA_LANGUAGES=(('xx', 'dummy language'),)) def test_4_DoChanges(self): self.copy_po_file_from_template('./django.po.template') untranslated_url = self.xx_form_url + '?msg_filter=untranslated' @@ -113,10 +109,10 @@ class RosettaTestCase(TestCase): r = self.client.get(untranslated_url) # make sure both strings are untranslated - self.assertTrue('dummy language' in str(r.content)) - self.assertTrue('String 1' in str(r.content)) - self.assertTrue('String 2' in str(r.content)) - self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in str(r.content)) + self.assertTrue('dummy language' in r.content.decode()) + self.assertTrue('String 1' in r.content.decode()) + self.assertTrue('String 2' in r.content.decode()) + self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in r.content.decode()) # post a translation data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world'} @@ -126,20 +122,18 @@ class RosettaTestCase(TestCase): r = self.client.get(untranslated_url) # the translated string no longer is up for translation - self.assertTrue('String 1' in str(r.content)) - self.assertTrue('String 2' not in str(r.content)) + self.assertTrue('String 1' in r.content.decode()) + self.assertTrue('String 2' not in r.content.decode()) # display only translated strings r = self.client.get(translated_url) # The translation was persisted - self.assertTrue('String 1' not in str(r.content)) - self.assertTrue('String 2' in str(r.content)) - self.assertTrue('Hello, world' in str(r.content)) + self.assertTrue('String 1' not in r.content.decode()) + self.assertTrue('String 2' in r.content.decode()) + self.assertTrue('Hello, world' in r.content.decode()) - @override_settings(ROSETTA_LANGUAGES=( - ('xx', 'dummy language'), - )) + @override_settings(ROSETTA_LANGUAGES=(('xx', 'dummy language'),)) def test_5_TestIssue67(self): # issue 67: http://code.google.com/p/django-rosetta/issues/detail?id=67 self.copy_po_file_from_template('./django.po.issue67.template') @@ -149,15 +143,15 @@ class RosettaTestCase(TestCase): content = f_.read() self.assertTrue('Hello, world' not in six.text_type(content)) self.assertTrue('|| n%100>=20) ? 1 : 2)' in six.text_type(content)) - del(content) + del content r = self.client.get(self.xx_form_url + '?msg_filter=untranslated') # make sure all strings are untranslated - self.assertTrue('dummy language' in str(r.content)) - self.assertTrue('String 1' in str(r.content)) - self.assertTrue('String 2' in str(r.content)) - self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in str(r.content)) + self.assertTrue('dummy language' in r.content.decode()) + self.assertTrue('String 1' in r.content.decode()) + self.assertTrue('String 2' in r.content.decode()) + self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in r.content.decode()) # post a translation data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world'} @@ -169,13 +163,11 @@ class RosettaTestCase(TestCase): self.assertTrue('Hello, world' in str(content)) self.assertTrue('|| n%100>=20) ? 1 : 2)' in str(content)) self.assertTrue('or n%100>=20) ? 1 : 2)' not in str(content)) - del(content) + del content - @override_settings(ROSETTA_LANGUAGES=( - ('xx', 'dummy language'), - )) + @override_settings(ROSETTA_LANGUAGES=(('xx', 'dummy language'),)) def test_6_ExcludedApps(self): - with self.settings(ROSETTA_EXCLUDED_APPLICATIONS=('rosetta', )): + with self.settings(ROSETTA_EXCLUDED_APPLICATIONS=('rosetta',)): r = self.client.get(self.third_party_file_list_url) self.assertNotContains(r, 'rosetta/locale/xx/LC_MESSAGES/django.po') @@ -190,17 +182,15 @@ class RosettaTestCase(TestCase): r = self.client.get(self.project_file_list_url) self.assertNotContains(r, 'rosetta/locale/xx/LC_MESSAGES/django.po') - @override_settings(ROSETTA_LANGUAGES=( - ('xx', 'dummy language'), - )) + @override_settings(ROSETTA_LANGUAGES=(('xx', 'dummy language'),)) def test_8_hideObsoletes(self): r = self.client.get(self.xx_form_url) # not in listing for p in range(1, 5): r = self.client.get(self.xx_form_url + '?page=%d' % p) - self.assertTrue('dummy language' in str(r.content)) - self.assertTrue('Les deux' not in str(r.content)) + self.assertTrue('dummy language' in r.content.decode()) + self.assertTrue('Les deux' not in r.content.decode()) r = self.client.get(self.xx_form_url + '?query=Les%20Deux') self.assertContains(r, 'dummy language') @@ -278,7 +268,7 @@ class RosettaTestCase(TestCase): def test_11_issue_80_tab_indexes(self): r = self.client.get(self.xx_form_url) - self.assertTrue('tabindex="3"' in str(r.content)) + self.assertTrue('tabindex="3"' in r.content.decode()) def test_12_issue_82_staff_user(self): self.client3 = Client() @@ -297,37 +287,37 @@ class RosettaTestCase(TestCase): self.assertTrue(r.content) self.assertEqual(r.status_code, 200) - @override_settings(ROSETTA_LANGUAGES=(('fr', 'French'), ('xx', 'Dummy Language'),)) + @override_settings(ROSETTA_LANGUAGES=(('fr', 'French'), ('xx', 'Dummy Language'))) def test_13_catalog_filters(self): r = self.client.get(self.third_party_file_list_url) self.assertTrue( - os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') in str(r.content) + os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') + in r.content.decode() ) - self.assertTrue(('contrib') not in str(r.content)) url = reverse('rosetta-file-list', kwargs={'po_filter': 'django'}) r = self.client.get(url) self.assertTrue( - os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') not in str(r.content) + os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') + not in r.content.decode() ) - self.assertTrue(('contrib') in str(r.content)) r = self.client.get(self.all_file_list_url) self.assertTrue( - os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') in str(r.content) + os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') + in r.content.decode() ) - self.assertTrue(('contrib') in str(r.content)) r = self.client.get(self.project_file_list_url) self.assertTrue( - os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') not in str(r.content) + os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') + not in r.content.decode() ) - self.assertTrue(('contrib') not in str(r.content)) def test_14_issue_99_context_and_comments(self): r = self.client.get(self.xx_form_url) - self.assertTrue('This is a text of the base template' in str(r.content)) - self.assertTrue('Context hint' in str(r.content)) + self.assertTrue('This is a text of the base template' in r.content.decode()) + self.assertTrue('Context hint' in r.content.decode()) def test_15_issue_87_entry_changed_signal(self): self.copy_po_file_from_template('./django.po.template') @@ -338,7 +328,8 @@ class RosettaTestCase(TestCase): self.test_old_msgstr = kwargs.get('old_msgstr') self.test_new_msgstr = sender.msgstr self.test_msg_id = sender.msgid - self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in str(r.content)) + + self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in r.content.decode()) # post a translation data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world'} @@ -348,7 +339,7 @@ class RosettaTestCase(TestCase): self.assertTrue(self.test_new_msgstr == 'Hello, world') self.assertTrue(self.test_msg_id == 'String 2') - del(self.test_old_msgstr, self.test_new_msgstr, self.test_msg_id) + del (self.test_old_msgstr, self.test_new_msgstr, self.test_msg_id) def test_16_issue_101_post_save_signal(self): self.copy_po_file_from_template('./django.po.template') @@ -358,14 +349,14 @@ class RosettaTestCase(TestCase): def test_receiver(sender, **kwargs): self.test_sig_lang = kwargs.get('language_code') - self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in str(r.content)) + self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in r.content.decode()) # post a translation data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world'} self.client.post(self.xx_form_url, data) self.assertTrue(self.test_sig_lang == 'xx') - del(self.test_sig_lang) + del self.test_sig_lang def test_17_issue_103_post_save_signal_has_request(self): self.copy_po_file_from_template('./django.po.template') @@ -375,31 +366,32 @@ class RosettaTestCase(TestCase): def test_receiver(sender, **kwargs): self.test_16_has_request = 'request' in kwargs - self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in str(r.content)) + self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in r.content.decode()) # post a translation data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world'} r = self.client.post(self.xx_form_url, data) self.assertTrue(self.test_16_has_request) - del(self.test_16_has_request) + del self.test_16_has_request def test_18_Test_Issue_gh24(self): self.copy_po_file_from_template('./django.po.issue24gh.template') r = self.client.get(self.xx_form_url) - self.assertTrue('m_bb9d8fe6159187b9ea494c1b313d23d4' in str(r.content)) + self.assertTrue('m_bb9d8fe6159187b9ea494c1b313d23d4' in r.content.decode()) # Post a translation, it should have properly wrapped lines - data = {'m_bb9d8fe6159187b9ea494c1b313d23d4': - 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean ' - 'commodo ligula eget dolor. Aenean massa. Cum sociis natoque ' - 'penatibus et magnis dis parturient montes, nascetur ridiculus ' - 'mus. Donec quam felis, ultricies nec, pellentesque eu, pretium ' - 'quis, sem. Nulla consequat massa quis enim. Donec pede justo, ' - 'fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, ' - 'rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum ' - 'felis eu pede mollis pretium.'} + data = { + 'm_bb9d8fe6159187b9ea494c1b313d23d4': 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean ' + 'commodo ligula eget dolor. Aenean massa. Cum sociis natoque ' + 'penatibus et magnis dis parturient montes, nascetur ridiculus ' + 'mus. Donec quam felis, ultricies nec, pellentesque eu, pretium ' + 'quis, sem. Nulla consequat massa quis enim. Donec pede justo, ' + 'fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, ' + 'rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum ' + 'felis eu pede mollis pretium.' + } r = self.client.post(self.xx_form_url, data) with open(self.dest_file, 'r') as po_file: pofile_content = po_file.read() @@ -410,7 +402,7 @@ class RosettaTestCase(TestCase): self.copy_po_file_from_template('./django.po.issue24gh.template') with self.settings(ROSETTA_POFILE_WRAP_WIDTH=0): r = self.client.get(self.xx_form_url) - self.assertTrue('m_bb9d8fe6159187b9ea494c1b313d23d4' in str(r.content)) + self.assertTrue('m_bb9d8fe6159187b9ea494c1b313d23d4' in r.content.decode()) r = self.client.post(self.xx_form_url, data) with open(self.dest_file, 'r') as po_file: pofile_content = po_file.read() @@ -419,9 +411,9 @@ class RosettaTestCase(TestCase): def test_19_Test_Issue_gh34(self): self.copy_po_file_from_template('./django.po.issue34gh.template') r = self.client.get(self.xx_form_url) - self.assertTrue('m_ff7060c1a9aae9c42af4d54ac8551f67_1' in str(r.content)) - self.assertTrue('m_ff7060c1a9aae9c42af4d54ac8551f67_0' in str(r.content)) - self.assertTrue('m_09f7e02f1290be211da707a266f153b3' in str(r.content)) + self.assertTrue('m_ff7060c1a9aae9c42af4d54ac8551f67_1' in r.content.decode()) + self.assertTrue('m_ff7060c1a9aae9c42af4d54ac8551f67_0' in r.content.decode()) + self.assertTrue('m_09f7e02f1290be211da707a266f153b3' in r.content.decode()) # post a translation, it should have properly wrapped lines data = { @@ -443,15 +435,16 @@ class RosettaTestCase(TestCase): def test_20_Test_Issue_gh38(self): # (Have to log in again, since our session engine changed) self.client.login(username='test_admin', password='test_password') - self.assertTrue('django.contrib.sessions.middleware.SessionMiddleware' - in settings.MIDDLEWARE) + self.assertTrue( + 'django.contrib.sessions.middleware.SessionMiddleware' in settings.MIDDLEWARE + ) # Only one backend to test: cache backend self.copy_po_file_from_template('./django.po.issue38gh.template') r = self.client.get(self.xx_form_url) self.assertFalse(len(str(self.client.cookies.get('sessionid'))) > 4096) - self.assertTrue('m_9efd113f7919952523f06e0d88da9c54' in str(r.content)) + self.assertTrue('m_9efd113f7919952523f06e0d88da9c54' in r.content.decode()) data = {'m_9efd113f7919952523f06e0d88da9c54': 'Testing cookie length'} r = self.client.post(self.xx_form_url, data) @@ -460,8 +453,8 @@ class RosettaTestCase(TestCase): self.assertTrue('Testing cookie length' in pofile_content) r = self.client.get(self.xx_form_url + '?filter=translated') - self.assertTrue('Testing cookie length' in str(r.content)) - self.assertTrue('m_9f6c442c6d579707440ba9dada0fb373' in str(r.content)) + self.assertTrue('Testing cookie length' in r.content.decode()) + self.assertTrue('m_9f6c442c6d579707440ba9dada0fb373' in r.content.decode()) @override_settings(ROSETTA_STORAGE_CLASS='rosetta.storage.CacheRosettaStorage') def test_21_concurrency_of_cache_backend(self): @@ -474,7 +467,7 @@ class RosettaTestCase(TestCase): self.client2.get(self.xx_form_url) self.assertNotEqual( self.client.session.get('rosetta_cache_storage_key_prefix'), - self.client2.session.get('rosetta_cache_storage_key_prefix') + self.client2.session.get('rosetta_cache_storage_key_prefix'), ) # Clean up (restore perms) @@ -485,12 +478,10 @@ class RosettaTestCase(TestCase): r = self.client.get(self.xx_form_url) # We have distinct hashes, even though the msgid and msgstr are identical - self.assertTrue('m_4765f7de94996d3de5975fa797c3451f' in str(r.content)) - self.assertTrue('m_08e4e11e2243d764fc45a5a4fba5d0f2' in str(r.content)) + self.assertTrue('m_4765f7de94996d3de5975fa797c3451f' in r.content.decode()) + self.assertTrue('m_08e4e11e2243d764fc45a5a4fba5d0f2' in r.content.decode()) - @override_settings(ROSETTA_LANGUAGES=( - ('xx', 'dummy language'), - )) + @override_settings(ROSETTA_LANGUAGES=(('xx', 'dummy language'),)) def test_23_save_header_data(self): from django.contrib.auth.models import User @@ -510,10 +501,10 @@ class RosettaTestCase(TestCase): r = self.client.get(self.xx_form_url + '?filter=untranslated') # make sure both strings are untranslated - self.assertTrue('dummy language' in str(r.content)) - self.assertTrue('String 1' in str(r.content)) - self.assertTrue('String 2' in str(r.content)) - self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in str(r.content)) + self.assertTrue('dummy language' in r.content.decode()) + self.assertTrue('String 1' in r.content.decode()) + self.assertTrue('String 2' in r.content.decode()) + self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in r.content.decode()) # post a translation data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world'} @@ -533,10 +524,10 @@ class RosettaTestCase(TestCase): # Load the template file r = self.client.get(self.xx_form_url) - self.assertTrue('Progress: 0%' in str(r.content)) + self.assertTrue('Progress: 0%' in r.content.decode()) data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world'} r = self.client.post(self.xx_form_url, data, follow=True) - self.assertTrue('Progress: 25%' in str(r.content)) + self.assertTrue('Progress: 25%' in r.content.decode()) def test_25_replace_access_control(self): # Test default access control allows access @@ -562,17 +553,18 @@ class RosettaTestCase(TestCase): def test_27_extended_urlconf_language_code_loads_file(self): url = reverse( - 'rosetta-form', - kwargs={'po_filter': 'all', 'lang_id': 'fr_FR.utf8', 'idx': 0} + 'rosetta-form', kwargs={'po_filter': 'all', 'lang_id': 'fr_FR.utf8', 'idx': 0} ) r = self.client.get(url) - self.assertTrue('French (France), UTF8' in str(r.content)) - self.assertTrue('m_03a603523bd75b00414a413657acdeb2' in str(r.content)) + self.assertTrue('French (France), UTF8' in r.content.decode()) + self.assertTrue('m_03a603523bd75b00414a413657acdeb2' in r.content.decode()) def test_28_issue_gh87(self): """Make sure that rosetta_i18n_catalog_filter is passed into the context.""" r = self.client.get(self.third_party_file_list_url) - self.assertContains(r, '