Coverage for /Users/marco/Code/django-rosetta/rosetta/views.py : 80.32%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# Python 3 except ImportError: # Python 2 from urllib import urlencode
except ImportError: from django.core.urlresolvers import reverse
"""A mixin class for Rosetta's class-based views. It provides: * security (see decorated dispatch() method) * a property for the 'po_filter' url argument """
# Handle security in our mixin # NOTE: after we drop support for Django 1.8, we can employ these decorators # more cleanly on the class itself, rather than the dispatch() method. (See # the Django docs: https://docs.djangoproject.com/en/dev/topics/class-based-views/intro/#decorating-the-class) def dispatch(self, *args, **kwargs):
def po_filter(self): """Return the filter applied to all of the .po files under consideration to determine which file is currently being translated. Options are: 'all', 'django', 'third-party', 'project'.
If the filter isn't in this list, throw a 404. """ raise Http404
"""Mixin for dealing with views that work specifically with a single .po file. In addition to what the super class brings, it adds the following properties: * language_id (e.g. 'fr'); derived from url, and validated * po_file_path (filesystem path to catalog) * po_file (pofile object) * po_file_is_writable (bool: do we have filesystem write perms to file) """ return self.request.POST.get(key)
def language_id(self): """Determine/return the language id from the url kwargs, after validating that: 1. the language is in settings.LANGUAGES, and 2. the current user is permitted to translate that language
(If either of the above fail, throw a 404.) """ # (Formerly known as "rosetta_i18n_lang_code") raise Http404
def po_file_path(self): """Based on the url kwargs, infer and return the path to the .po file to be shown/updated.
Throw a 404 if a file isn't found. """ # This was formerly referred to as 'rosetta_i18n_fn'
project_apps=project_apps, django_apps=django_apps, third_party_apps=third_party_apps, )
def po_file(self): """Return the parsed .po file that is currently being translated/viewed.
(Note that this parsing also involves marking up each entry with a hash of its contents.) """ # If we can write changes to file, then we pull it up fresh with # each request. # XXX: brittle; what if this path doesn't exist? Isn't a .po file? wrapwidth=rosetta_settings.POFILE_WRAP_WIDTH) # Entry is an object representing a single entry in the catalog. # We interate through the *entire catalog*, pasting a hashed # value of the meat of each entry on its side in an attribute # called "md5hash". six.text_type(entry.msgid) + six.text_type(entry.msgstr) + six.text_type(entry.msgctxt or '') ).encode('utf8') else: # Entry is an object representing a single entry in the # catalog. We interate through the entire catalog, pasting # a hashed value of the meat of each entry on its side in # an attribute called "md5hash". six.text_type(entry.msgid) + six.text_type(entry.msgstr) + six.text_type(entry.msgctxt or '') ).encode('utf8')
def po_file_cache_key(self): """Return the cache key used to save/access the .po file (when actually persisted in cache). """
def po_file_is_writable(self): """Return True if we're able (in terms of file system permissions) to write out changes to the .po file we're translating. """ # (This was formerly called 'rosetta_i18n_write'.)
"""Lists the languages, the gettext catalog files that can be translated, and their translation progress for a filtered list of apps/projects. """
project_apps=project_apps, django_apps=django_apps, third_party_apps=third_party_apps, )
"""Show a form with a page's worth of messages to be translated; handle its submission by updating cached pofile and, if possible, writing out changes to existing .po file.
Query strings that affect what's shown: * msg_filter: filters which messages are displayed. One of 'all', 'fuzzy', 'translated', and 'untranslated' * ref_lang: specifies which language should be shown as the source. Only applicable when REF_LANG setting is set to True * page: which page (number) should be shown of the paginated results (with msg_filter or query applied) * query: a search string, where only matches are shown. Fields that are searched include: source, translated text, "occurence" file path, or context hints. """ # Note: due to the unorthodox nature of the form itself, we're not using # Django's generic FormView as our base class.
"""Fixes submitted translations by filtering carriage returns and pairing newlines at the begging and end of the translated string with the original """ return out_
out_ = out_.replace("\r", '')
out_ = out_.lstrip() pass out_ = out_.rstrip()
"""The only circumstances when we POST is to submit the main form, both updating translations (if any changed) and advancing to the next page of messages.
There is no notion of validation of this content; as implemented, unknown fields are ignored and a generic failure message is shown.
Submitted changes are saved out to the specified .po file on the filesystem if that file is writable, otherwise the cached version of the file is updated (so it can be downloaded). Then the user is redirected to the next page of messages (if there is one; otherwise they're redirected back to the current page). """ # The message text inputs are captured as hashes of their initial # contents, preceded by "m_". Messages with plurals end with their # variation number.
# polib parses .po files into unicode strings, but # doesn't bother to convert plural indexes to int, # so we need unicode here.
# Above no longer true as of Polib 1.0.4
# If someone did a makemessage, some entries might # have been removed, so we need to check. entry.msgid_plural, new_msgstr ) else:
entry.flags.remove('fuzzy') entry.flags.append('fuzzy')
user=request.user, old_msgstr=old_msgstr, old_fuzzy=old_fuzzy, pofile=self.po_file_path, language_code=self.language_id, ) else: self.request, _("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."), )
'NFKD', u"%s %s <%s>" % ( getattr(self.request.user, 'first_name', 'Anonymous'), getattr(self.request.user, 'last_name', 'User'), getattr(self.request.user, 'email', 'anonymous@user.tld') ) ).encode('ascii', 'ignore') get_rosetta_version(False)) except UnicodeDecodeError: pass
request=self.request ) # Try auto-reloading via the WSGI daemon mode reload mechanism rosetta_settings.WSGI_AUTO_RELOAD and 'mod_wsgi.process_group' in self.request.environ and self.request.environ.get('mod_wsgi.process_group', None) and 'SCRIPT_FILENAME' in self.request.environ and int(self.request.environ.get('mod_wsgi.script_reloading', 0)) ) try: os.utime(self.request.environ.get('SCRIPT_FILENAME'), None) except OSError: pass # Try auto-reloading via uwsgi daemon reload mechanism try: import uwsgi uwsgi.reload() # pretty easy right? except: pass # we may not be running under uwsgi :P # XXX: It would be nice to add a success message here! except Exception as e: messages.error(self.request, e)
# Reconstitute url to redirect to. Start with determining whether the # page number can be incremented. except ValueError: page = 1 # fall back to page 1 else: page = 1 'msg_filter': self.msg_filter, 'query': self.query, 'ref_lang': self.ref_lang, 'page': page, } # Winnow down the query string args to non-blank ones url=reverse('rosetta-form', kwargs=self.kwargs), qs=urlencode(query_string_args), ))
# Handle REF_LANG setting; mark up our entries with the reg lang's # corresponding translations else: else: # XXX: having "MSGID" at the end of the dropdown is really odd, no? # Why not instead do this? # LANGUAGES = [('', '----')] + list(settings.LANGUAGES)
# Determine page number & how pagination links should be displayed else: else:
# Handle MAIN_LANGUAGE setting, if applicable; mark up each entry # in the pagination window with the "main language"'s string. # Translate from id to language name for language in settings.LANGUAGES: if language[0] == main_language_id: main_language = _(language[1]) break main_lang_po_path = self.po_file_path.replace( '/%s/' % self.language_id, '/%s/' % main_language_id, ) # XXX: brittle; what if this path doesn't exist? Isn't a .po file? main_lang_po = pofile(main_lang_po_path)
for message in rosetta_messages: message.main_lang = main_lang_po.find(message.msgid).msgstr
# Collect some constants for the template ADMIN_IMAGE_DIR = ADMIN_MEDIA_PREFIX + 'img/admin/' dict(settings.LANGUAGES).get(self.language_id) ) # "bidi" as in "bi-directional" # Base for pagination links; the page num itself is added in template # Base for msg filter links; it doesn't make sense to persist page # numbers in these links. We just pass in ref_lang, if it's set. {k: v for k, v in query_string_args.items() if k == 'ref_lang'} )
'version': get_rosetta_version(True), 'ADMIN_MEDIA_PREFIX': ADMIN_MEDIA_PREFIX, 'ADMIN_IMAGE_DIR': ADMIN_IMAGE_DIR, 'LANGUAGES': LANGUAGES, 'rosetta_settings': rosetta_settings, 'rosetta_i18n_lang_name': rosetta_i18n_lang_name, 'rosetta_i18n_lang_code': self.language_id, 'rosetta_i18n_lang_bidi': rosetta_i18n_lang_bidi, 'rosetta_i18n_filter': self.msg_filter, 'rosetta_i18n_write': self.po_file_is_writable, 'rosetta_messages': rosetta_messages, 'page_range': needs_pagination and page_range, 'needs_pagination': needs_pagination, 'main_language': main_language, 'rosetta_i18n_app': get_app_name(self.po_file_path), 'page': page, 'query': self.query, 'pagination_query_string_base': pagination_query_string_base, 'filter_query_string_base': filter_query_string_base, 'paginator': paginator, 'rosetta_i18n_pofile': self.po_file, 'ref_lang': self.ref_lang, })
def ref_lang(self): """Return the language id for the "reference language" (the language to be translated *from*, if not English).
Throw a 404 if it's not in settings.LANGUAGES. """ raise Http404
def ref_lang_po_file(self): """Return a parsed .po file object for the "reference language", if one exists, otherwise None. """ '/locale/[a-z]{2}/', '/locale/%s/' % (self.ref_lang), self.po_file_path, ) except IOError: # there's a syntax error in the PO file and polib can't # open it. Let's just do nothing and thus display msgids. # XXX: :-/ pass
def msg_filter(self): """Validate/return msg_filter from request (e.g. 'fuzzy', 'untranslated'), or a default.
If a query is also specified in the request, then return None. """ else: msg_filter = 'all'
def query(self): """Strip and return the query (for searching the catalog) from the request, or None. """
"""Return a list of the entries (messages) that would be part of the current "view"; that is, all of the ones from this .po file matching the current query or msg_filter. """ # Scenario #1: terms matching a search query
six.text_type(e.msgid) + six.text_type(e.comment) + u''.join([o[0] for o in e.occurrences]) )
if not e_.obsolete and rx.search(concat_entry(e_))] else: # Scenario #2: filtered list of messages entries = [e_ for e_ in self.po_file.fuzzy_entries() if not e_.obsolete] else: # ("all")
"""Download a zip file for a specific catalog including both the raw (.po) and compiled (.mo) files, either as they exist on disk, or, if what's on disk is unwritable (permissions-wise), return what's in the cache. """
else: offered_fn = self.po_file_path.split('/')[-1]
except Exception: # XXX: should add a message! return HttpResponseRedirect( reverse('rosetta-file-list', kwargs={'po_filter': 'project'}) )
def translate_text(request): language_from = request.GET.get('from', None) language_to = request.GET.get('to', None) text = request.GET.get('text', None)
if language_from == language_to: data = {'success': True, 'translation': text} else: # run the translation:
AZURE_CLIENT_ID = getattr(settings, 'AZURE_CLIENT_ID', None) AZURE_CLIENT_SECRET = getattr(settings, 'AZURE_CLIENT_SECRET', None)
translator = Translator(AZURE_CLIENT_ID, AZURE_CLIENT_SECRET)
try: translated_text = translator.translate(text, language_to, language_from) data = {'success': True, 'translation': translated_text} except TranslateApiException as e: data = { 'success': False, 'error': "Translation API Exception: {0}".format(e.message), }
return JsonResponse(data) |