Merge remote-tracking branch 'mbi/develop' into reflang

Conflicts:
	README.rst
	rosetta/views.py
This commit is contained in:
Virgil Dupras 2013-04-19 09:09:21 -04:00
commit 1ff1e04460
12 changed files with 273 additions and 19 deletions

View file

@ -1,6 +1,10 @@
Version 0.7.2
-------------
* Fix for when settings imports unicode_literals for some reason (Issue #67)
* Fixed mess with app_id between pages (Issue #68, thanks @tsouvarev)
* Added Farsi translation. Thanks, @amiraliakbari
* Improved the permission system, allowing for more advanced permission mechanisms. Thanks, @codeinthehole and @tangentlabs
* Fixed the ordering of apps in the language selection screen. (Issue #73, thanks @tsouvarev, @kanu and everyone else involved in tracking this one down)
Version 0.7.1
-------------

View file

@ -62,6 +62,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_ENABLE_REFLANG``: See the note below on Reference Language. Defaults to ``False``.
* ``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.
********
Storages

36
rosetta/access.py Normal file
View file

@ -0,0 +1,36 @@
from django.conf import settings
from django.utils import importlib
def can_translate(user):
return get_access_control_function()(user)
def get_access_control_function():
"""
Return a predicate for determining if a user can access the Rosetta views
"""
fn_path = getattr(settings, 'ROSETTA_ACCESS_CONTROL_FUNCTION', None)
if fn_path is None:
return is_superuser_staff_or_in_translators_group
# Dynamically load a permissions function
perm_module, perm_func = fn_path.rsplit('.', 1)
perm_module = importlib.import_module(perm_module)
return getattr(perm_module, perm_func)
# Default access control test
def is_superuser_staff_or_in_translators_group(user):
if not getattr(settings, 'ROSETTA_REQUIRES_AUTH', True):
return True
if not user.is_authenticated():
return False
elif user.is_superuser and user.is_staff:
return True
else:
try:
from django.contrib.auth.models import Group
translators = Group.objects.get(name='translators')
return translators in user.groups.all()
except Group.DoesNotExist:
return False

View file

@ -16,6 +16,9 @@ MAIN_LANGUAGE = getattr(settings, 'ROSETTA_MAIN_LANGUAGE', None)
MESSAGES_SOURCE_LANGUAGE_CODE = getattr(settings, 'ROSETTA_MESSAGES_SOURCE_LANGUAGE_CODE', 'en')
MESSAGES_SOURCE_LANGUAGE_NAME = getattr(settings, 'ROSETTA_MESSAGES_SOURCE_LANGUAGE_NAME', 'English')
ACCESS_CONTROL_FUNCTION = getattr(
settings, 'ROSETTA_ACCESS_CONTROL_FUNCTION', None)
"""
When running WSGI daemon mode, using mod_wsgi 2.0c5 or later, this setting

Binary file not shown.

View file

@ -0,0 +1,206 @@
# 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: 2013-04-01 18:36+0430\n"
"PO-Revision-Date: 2013-04-01 19:03+0430\n"
"Last-Translator: <admin@arsh.co>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Plural-Forms: nplurals=1; plural=0\n"
"X-Translated-Using: django-rosetta 0.7.1\n"
#: templates/rosetta/languages.html:5 templates/rosetta/languages.html.py:8
msgid "Language selection"
msgstr "انتخاب زبان"
#: templates/rosetta/languages.html:8 templates/rosetta/pofile.html:22
msgid "Home"
msgstr "خانه"
#: templates/rosetta/languages.html:9
msgid ""
"Couldn't load the specified language file. This usually happens when using "
"the Encrypted Cookies Session Storage backend on Django 1.4 or "
"higher.<br/>Setting ROSETTA_STORAGE_CLASS = "
"'rosetta.storage.CacheRosettaStorage' in your settings file should fix this."
msgstr ""
"بارگزاری کاتالوگ زبان مورد نظر ممکن نبود. این مشکل معمولا به خاطر استفاده از"
" Encrypted Cookies Session Storage backend در جنگو ۱.۴ یا بالاتر رخ "
"می‌دهد.<br/>با افزودن خط زیر به فایل تنظیمات پروژه معمولا این مشکل را حل "
"می‌کند:<br/>ROSETTA_STORAGE_CLASS = 'rosetta.storage.CacheRosettaStorage'"
#: templates/rosetta/languages.html:15
msgid "Filter"
msgstr "فیلتر"
#: templates/rosetta/languages.html:16
msgid "Project"
msgstr "پروژه"
#: templates/rosetta/languages.html:17
msgid "Third party"
msgstr "شخص ثالث"
#: templates/rosetta/languages.html:19 templates/rosetta/pofile.html:39
msgid "All"
msgstr "همه"
#: templates/rosetta/languages.html:32
msgid "Application"
msgstr "کارکرد"
#: templates/rosetta/languages.html:33
msgid "Progress"
msgstr "پیشرفت"
#: templates/rosetta/languages.html:34
msgid "Messages"
msgstr "پیام‌ها"
#: templates/rosetta/languages.html:35
msgid "Translated"
msgstr "ترجمه شده"
#: templates/rosetta/languages.html:36 templates/rosetta/pofile.html:71
msgid "Fuzzy"
msgstr "نیاز به بازبینی"
#: templates/rosetta/languages.html:37
msgid "Obsolete"
msgstr "کهنه"
#: templates/rosetta/languages.html:38
msgid "File"
msgstr "پرونده"
#: templates/rosetta/languages.html:61
msgid "Nothing to translate!"
msgstr "موردی برای ترجمه وجود ندارد!"
#: templates/rosetta/languages.html:62
msgid ""
"You haven't specified any languages in your settings file, or haven't yet "
"generated a batch of translation catalogs."
msgstr ""
"شما زبانی را در فایل تنظیمات تعیین نکردید، یا هنوز کاتالوگ‌های ترجمه را "
"ایجاد نکردید."
#: templates/rosetta/languages.html:63
#, python-format
msgid ""
"Please refer to <a href=\"%(i18n_doc_link)s\">Django's I18N "
"documentation</a> for a guide on how to set up internationalization for your"
" project."
msgstr ""
"لطفا به <a href=\"%(i18n_doc_link)s\">مستندات جهانی‌سازی جنگو "
"documentation</a> برای راهنما در مورد روش راه‌اندازی امکانات جهانی‌سازی برای"
" پروژه‌تان مراجعه کنید."
#: templates/rosetta/pofile.html:9
msgid "Pick another file"
msgstr "انتخاب یک فایل دیگر"
#: templates/rosetta/pofile.html:10
msgid "Download this catalog"
msgstr "دانلود این کاتالوگ"
#: templates/rosetta/pofile.html:25
#, python-format
msgid "Progress: %(percent_translated)s%%"
msgstr "درصد پیشرفت: %(percent_translated)s%%"
#: templates/rosetta/pofile.html:27
msgid "File is read-only: download the file when done editing!"
msgstr ""
"فایل ترجمه غیر قابل نوشتن است، پس از اتمام ترجمه فایل حاصل را دانلود نمایید!"
#: templates/rosetta/pofile.html:28
msgid ""
"Some items in your last translation block couldn't be saved: this usually "
"happens when the catalog file changes on disk after you last loaded it."
msgstr ""
"برخی موارد در آخرین دسته‌ی ترجمه قابل ذخیره نبود: این مشکل معمولا زمانی رخ "
"می‌دهد که فایل کاتالوگ روی دیسک پس از آخرین دفعه‌ی بارگزاری آن، تغییر کرده "
"باشد."
#: templates/rosetta/pofile.html:32
#, python-format
msgid "Translate into %(rosetta_i18n_lang_name)s"
msgstr "در حال ترجمه به %(rosetta_i18n_lang_name)s"
#: templates/rosetta/pofile.html:35
msgid "Display:"
msgstr "نمایش:"
#: templates/rosetta/pofile.html:36
msgid "Untranslated only"
msgstr "فقط ترجمه نشده‌ها"
#: templates/rosetta/pofile.html:37
msgid "Translated only"
msgstr "فقط ترجمه شده‌ها"
#: templates/rosetta/pofile.html:38
msgid "Fuzzy only"
msgstr "فقط موارد برای بازبینی"
#: templates/rosetta/pofile.html:46
msgid "Search"
msgstr "جستجو"
#: templates/rosetta/pofile.html:48
msgid "Go"
msgstr "برو"
#: templates/rosetta/pofile.html:68
msgid "Original"
msgstr "اصلی"
#: templates/rosetta/pofile.html:72
msgid "Occurrences(s)"
msgstr "موارد رخداد"
#: templates/rosetta/pofile.html:86 templates/rosetta/pofile.html.py:89
#: templates/rosetta/pofile.html:104 templates/rosetta/pofile.html.py:107
msgid "Context hint"
msgstr "راهنمای محلی"
#: templates/rosetta/pofile.html:114
msgid "suggest"
msgstr "پیشنهاد"
#: templates/rosetta/pofile.html:125
#, python-format
msgid "%(more_count)s more"
msgid_plural "%(more_count)s more"
msgstr[0] "%(more_count)s مورد بیش‌تر"
msgstr[1] "%(more_count)s مورد بیش‌تر"
#: templates/rosetta/pofile.html:137
msgid "Save and translate next block"
msgstr "ذخیره و ترجمه‌ی دسته‌ی بعدی"
#: templates/rosetta/pofile.html:141
msgid "Skip to page:"
msgstr "پرش به صفحه:"
#: templates/rosetta/pofile.html:154
msgid "Displaying:"
msgstr "در حال نمایش:"
#: templates/rosetta/pofile.html:158
#, python-format
msgid "%(hits)s/%(message_number)s message"
msgid_plural "%(hits)s/%(message_number)s messages"
msgstr[0] "%(hits)s از %(message_number)s پیام"
msgstr[1] "%(hits)s از %(message_number)s پیام"

View file

@ -113,7 +113,7 @@ def find_pos(lang, project_apps=True, django_apps=False, third_party_apps=False)
filename = os.path.join(dirname, fn)
if os.path.isfile(filename):
ret.add(os.path.abspath(filename))
return list(ret)
return list(sorted(ret))
def pagination_range(first, last, current):

View file

@ -537,3 +537,22 @@ class RosettaTestCase(TestCase):
r = self.client.post(reverse('rosetta-home'), dict(m_e48f149a8b2e8baa81b816c0edf93890='Hello, world', _next='_next'))
r = self.client.get(reverse('rosetta-home'))
self.assertTrue('Progress: 25.00%' in str(r.content))
def test_24_replace_access_control(self):
# Test default access control allows access
url = reverse('rosetta-home')
response = self.client.get(url)
self.assertEqual(200, response.status_code)
# Now replace access control, and check we get redirected
settings.ROSETTA_ACCESS_CONTROL_FUNCTION = 'rosetta.tests.no_access'
response = self.client.get(url)
self.assertEqual(302, response.status_code)
# Restore setting to default
settings.ROSETTA_ACCESS_CONTROL_FUNCTION = None
# Stubbed access control function
def no_access(user):
return False

View file

@ -13,6 +13,7 @@ from rosetta.polib import pofile
from rosetta.poutil import find_pos, pagination_range, timestamp_with_timezone
from rosetta.signals import entry_changed, post_save
from rosetta.storage import get_storage
from rosetta.access import can_translate
import re
import rosetta
import unicodedata
@ -269,6 +270,7 @@ def home(request):
ADMIN_MEDIA_PREFIX=ADMIN_MEDIA_PREFIX,
ADMIN_IMAGE_DIR=ADMIN_IMAGE_DIR,
ENABLE_REFLANG=rosetta_settings.ENABLE_REFLANG,
LANGUAGES=LANGUAGES,
rosetta_settings=rosetta_settings,
rosetta_i18n_lang_name=_(storage.get('rosetta_i18n_lang_name')),
rosetta_i18n_lang_code=rosetta_i18n_lang_code,
@ -395,7 +397,7 @@ def lang_sel(request, langid, idx):
third_party_apps = rosetta_i18n_catalog_filter in ('all', 'third-party')
django_apps = rosetta_i18n_catalog_filter in ('all', 'django')
project_apps = rosetta_i18n_catalog_filter in ('all', 'project')
file_ = find_pos(langid, project_apps=project_apps, django_apps=django_apps, third_party_apps=third_party_apps)[int(idx)]
file_ = sorted(find_pos(langid, project_apps=project_apps, django_apps=django_apps, third_party_apps=third_party_apps), key=get_app_name)[int(idx)]
storage.set('rosetta_i18n_lang_code', langid)
storage.set('rosetta_i18n_lang_name', six.text_type([l[1] for l in settings.LANGUAGES if l[0] == langid][0]))
@ -429,20 +431,3 @@ def ref_sel(request, langid):
return HttpResponseRedirect(reverse('rosetta-home'))
ref_sel = never_cache(ref_sel)
ref_sel = user_passes_test(lambda user: can_translate(user), settings.LOGIN_URL)(ref_sel)
def can_translate(user):
if not getattr(settings, 'ROSETTA_REQUIRES_AUTH', True):
return True
if not user.is_authenticated():
return False
elif user.is_superuser and user.is_staff:
return True
else:
try:
from django.contrib.auth.models import Group
translators = Group.objects.get(name='translators')
return translators in user.groups.all()
except Group.DoesNotExist:
return False

0
runtests.sh Normal file → Executable file
View file

0
runtests_coverage.sh Normal file → Executable file
View file

0
runtests_multi_venv.sh Normal file → Executable file
View file