diff --git a/CHANGES b/CHANGES index d13e3ad7..70796909 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ Version 0.7.5 * Fixed external JavaScript import to be url scheme independent (PR #101, thanks @tsouvarev) * Fixed a test * Added support for excluding certain locale paths from the list of PO catalogs (PR #102, thanks @elpaso) +* Added support for translator groups (PR #103, thanks @barklund) Version 0.7.4 diff --git a/README.rst b/README.rst index d791e0c6..da59ae6d 100644 --- a/README.rst +++ b/README.rst @@ -63,7 +63,7 @@ Rosetta can be configured via the following parameters, to be defined in your pr * ``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_LANGUAGE_GROUPS``: Set to ``True`` to enable language-specific groups, which can be used to give different translators access to different languages. +* ``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 filenames are exposed in the web interface. Defaults to ``('django.po', 'djangojs.po')`` * ``ROSETTA_EXCLUDE_PATHS``: Exclude paths defined in this list from being searched (usually ends with "locale"). Defaults to ``()`` diff --git a/rosetta/access.py b/rosetta/access.py index 8e916cd5..9d155f99 100644 --- a/rosetta/access.py +++ b/rosetta/access.py @@ -1,4 +1,6 @@ from django.conf import settings +from rosetta.conf import settings as rosetta_settings + from django.utils import importlib @@ -6,21 +8,6 @@ def can_translate(user): return get_access_control_function()(user) -def can_translate_language(user, langid): - - use_language_groups = getattr(settings, 'ROSETTA_LANGUAGE_GROUPS', False) - - if not use_language_groups: - return can_translate(user) - elif not user.is_authenticated(): - return False - elif user.is_superuser and user.is_staff: - return True - else: - return user.groups.filter(name='translators-%s' % langid).exists() - - - def get_access_control_function(): """ Return a predicate for determining if a user can access the Rosetta views @@ -44,3 +31,14 @@ def is_superuser_staff_or_in_translators_group(user): return True else: return user.groups.filter(name='translators').exists() + + +def can_translate_language(user, langid): + if not rosetta_settings.ROSETTA_LANGUAGE_GROUPS: + return can_translate(user) + elif not user.is_authenticated(): + return False + elif user.is_superuser and user.is_staff: + return True + else: + return user.groups.filter(name='translators-%s' % langid).exists() diff --git a/rosetta/conf/settings.py b/rosetta/conf/settings.py index a2cf85f7..27fc8686 100644 --- a/rosetta/conf/settings.py +++ b/rosetta/conf/settings.py @@ -75,3 +75,9 @@ # Exclude paths defined in this list from being searched (usually ends with "locale") ROSETTA_EXCLUDED_PATHS = getattr(settings, 'ROSETTA_EXCLUDED_PATHS', ()) + +# Set to True to enable language-specific groups, which can be used to give +# different translators access to different languages. Instead of creating a +# 'translators` group, create individual per-language groups, e.g. +# 'translators-de', 'translators-fr', ... +ROSETTA_LANGUAGE_GROUPS = getattr(settings, 'ROSETTA_LANGUAGE_GROUPS', False) diff --git a/rosetta/tests/tests.py b/rosetta/tests/tests.py index d9688556..201de475 100644 --- a/rosetta/tests/tests.py +++ b/rosetta/tests/tests.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from django.conf import settings -from django.contrib.auth.models import User +from django.contrib.auth.models import User, Group from django.core.urlresolvers import reverse, resolve from django.core.exceptions import ImproperlyConfigured from django.core.cache import cache @@ -633,6 +633,42 @@ def test_31_pr_102__exclude_paths(self): rosetta_settings.ROSETTA_EXCLUDED_PATHS = ROSETTA_EXCLUDED_PATHS + def test_32_pr_103__language_groups(self): + ROSETTA_LANGUAGE_GROUPS = rosetta_settings.ROSETTA_LANGUAGE_GROUPS + rosetta_settings.ROSETTA_LANGUAGE_GROUPS = False + + # Default behavior: non admins need to be in a translators group, they see + # all catalogs + translators = Group.objects.create(name='translators') + translators_xx = Group.objects.create(name='translators-xx') + + user4 = User.objects.create_user('test_admin4', 'test@test3.com', 'test_password') + user4.groups.add(translators) + user4.is_superuser = False + user4.is_staff = True + user4.save() + self.client.login(username='test_admin4', password='test_password') + + r = self.client.get(reverse('rosetta-pick-file') + '?filter=third-party') + r = self.client.get(reverse('rosetta-pick-file')) + self.assertTrue(os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') in str(r.content)) + + # Activate the option, user doesn't see the XX catalog + rosetta_settings.ROSETTA_LANGUAGE_GROUPS = True + + r = self.client.get(reverse('rosetta-pick-file') + '?filter=third-party') + r = self.client.get(reverse('rosetta-pick-file')) + self.assertFalse(os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') in str(r.content)) + + # Now add them to the custom group + user4.groups.add(translators_xx) + + r = self.client.get(reverse('rosetta-pick-file') + '?filter=third-party') + r = self.client.get(reverse('rosetta-pick-file')) + self.assertTrue(os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') in str(r.content)) + + rosetta_settings.ROSETTA_LANGUAGE_GROUPS = ROSETTA_LANGUAGE_GROUPS + # Stubbed access control function def no_access(user):