Django: switch language of message sent from admin panel - django

I have a model, Order, that has an action in the admin panel that lets an admin send information about the order to certain persons listed that order. Each person has language set and that is the language the message is supposed to be sent in.
A short version of what I'm using:
from django.utils.translation import ugettext as _
from django.core.mail import EmailMessage
lang = method_that_gets_customer_language()
body = _("Dear mister X, here is the information you requested\n")
body += some_order_information
subject = _("Order information")
email = EmailMessage(subject, body, 'customer#example.org', ['admin#example.org'])
email.send()
The customer information about the language he uses is available in lang. The default language is en-us, the translations are in french (fr) and german (de).
Is there a way to use the translation for the language specified in lang for body and subject then switch back to en-us? For example: lang is 'de'. The subject and body should get the strings specified in the 'de' translation files.
edit:
Found a solution.
from django.utils import translation
from django.utils.translation import ugettext as _
body = "Some text in English"
translation.activate('de')
print "%s" % _(body)
translation.activate('en')
What this does it take the body variable, translates it to German, prints it then returns the language to English.
Something like
body = _("Some text in English")
translation.activate('de')
print "%s" % body
prints the text in English though.

If you're using Python 2.6 (or Python 2.5 after importing with_statement from __future__) you can use the following context manager for convenience.
from contextlib import contextmanager
from django.utils import translation
#contextmanager
def language(lang):
if lang and translation.check_for_language(lang):
old_lang = translation.get_language()
translation.activate(lang)
try:
yield
finally:
if lang:
translation.activate(old_lang)
Example of usage:
message = _('English text')
with language('fr'):
print unicode(message)
This has the benefit of being safe in case something throws an exception, as well as restoring the thread's old language instead of the Django default.

Not sure if activating/deactivating translation is proper way to solve that problem(?)
If I were facing that problem I would try to build some model for storing subjects/body/language/type fields. Some code draft:
class ClientMessageTemplate(models.Model):
language = model.CharField(choices=AVAIALBLE_LANGUAGES,...)
subject = models.CharField(...)
body = models.CharField(...)
type = models.CharField(choices=AVAILABLE_MESSAGE_TYPES)
Then you can retreive easily ClientMessageTemplate you need base on type and client's language.
Advantage of this solution is that you can have all data maintainable via admin interface and do not need to recompile message files each time something changed.

Related

Django: prefix/postfix language slug in i18n_urls

I have a django-cms site, that uses i18n_patterns in urls.py. This works, urls are built like /lang/here-starts-the-normal/etc/.
Now, I would like to have urls like this: /prefix-lang/here-starts.... As there will be a couple of country specific domains, this wille be like /ch-de/here-... for Switzerland/.ch domain, /us-en/here-starts.... for the states, and some more. So, when the url would be /ch-de/..., the LANGUAGE would still be de. Hope this is clear?
As the content is filled with existing LANGUAGES=(('de', 'DE'), ('en', 'EN'), ...), I cannot change LANGUAGES for every domain - no content would be found in the cms, modeltranslation, only to mention those two.
How can I prefix the language slug in i18n_patterns? Is it possible at all?
I think a way without hacking Django too much would be to use URL rewrite facility provided by the webserver you run, for example, for mod_wsgi you can use mod_rewrite, similar facility exists also for uWSGI.
You may need to also post-process the output from Django to make sure that any links are also correctly re-written to follow the new schema. Not the cleanest approach but seems doable.
Working example, though the country/language order is reversed (en-ch instead of ch-en), to have it as django expects it when trying to find a language (ie, setting language to "en-ch", it will find "en", if available).
This solution involves a modified LocaleMiddleware, i18n_patterns, LocaleRegexResolver. It supports no country, or a 2 char country code, setup with settings.SITE_COUNTRY. It works by changing urls to a lang-country mode, but the found language code in middleware will still be language only, 2 chars, and work perfectly with existing LANGUAGES, that contain 2 chars language codes.
custom_i18n_patterns.py - this just uses our new resolver, see below
from django.conf import settings
from ceco.resolvers import CountryLocaleRegexURLResolver
def country_i18n_patterns(*urls, **kwargs):
"""
Adds the language code prefix to every URL pattern within this
function. This may only be used in the root URLconf, not in an included
URLconf.
"""
if not settings.USE_I18N:
return list(urls)
prefix_default_language = kwargs.pop('prefix_default_language', True)
assert not kwargs, 'Unexpected kwargs for i18n_patterns(): %s' % kwargs
return [CountryLocaleRegexURLResolver(list(urls), prefix_default_language=prefix_default_language)]
resolvers.py
import re
from django.conf import settings
from django.urls import LocaleRegexURLResolver
from modeltranslation.utils import get_language
class CountryLocaleRegexURLResolver(LocaleRegexURLResolver):
"""
A URL resolver that always matches the active language code as URL prefix.
extended, to support custom country postfixes as well.
"""
#property
def regex(self):
language_code = get_language() or settings.LANGUAGE_CODE
if language_code not in self._regex_dict:
if language_code == settings.LANGUAGE_CODE and not self.prefix_default_language:
regex_string = ''
else:
# start country changes
country_postfix = ''
if getattr(settings, 'SITE_COUNTRY', None):
country_postfix = '-{}'.format(settings.SITE_COUNTRY)
regex_string = '^%s%s/' % (language_code, country_postfix)
# end country changes
self._regex_dict[language_code] = re.compile(regex_string, re.UNICODE)
return self._regex_dict[language_code]
middleware.py - only very few lines changed, but had to replace the complete process_response.
from django.middleware.locale import LocaleMiddleware
from django.conf import settings
from django.conf.urls.i18n import is_language_prefix_patterns_used
from django.http import HttpResponseRedirect
from django.urls import get_script_prefix, is_valid_path
from django.utils import translation
from django.utils.cache import patch_vary_headers
class CountryLocaleMiddleware(LocaleMiddleware):
"""
This is a very simple middleware that parses a request
and decides what translation object to install in the current
thread context. This allows pages to be dynamically
translated to the language the user desires (if the language
is available, of course).
"""
response_redirect_class = HttpResponseRedirect
def process_response(self, request, response):
language = translation.get_language()
language_from_path = translation.get_language_from_path(request.path_info)
urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF)
i18n_patterns_used, prefixed_default_language = is_language_prefix_patterns_used(urlconf)
if (response.status_code == 404 and not language_from_path and
i18n_patterns_used and prefixed_default_language):
# Maybe the language code is missing in the URL? Try adding the
# language prefix and redirecting to that URL.
# start country changes
language_country = language
if getattr(settings, 'SITE_COUNTRY', None):
language_country = '{}-{}'.format(language, settings.SITE_COUNTRY)
language_path = '/%s%s' % (language_country, request.path_info)
# end country changes!
path_valid = is_valid_path(language_path, urlconf)
path_needs_slash = (
not path_valid and (
settings.APPEND_SLASH and not language_path.endswith('/') and
is_valid_path('%s/' % language_path, urlconf)
)
)
if path_valid or path_needs_slash:
script_prefix = get_script_prefix()
# Insert language after the script prefix and before the
# rest of the URL
language_url = request.get_full_path(force_append_slash=path_needs_slash).replace(
script_prefix,
'%s%s/' % (script_prefix, language_country),
1
)
return self.response_redirect_class(language_url)
if not (i18n_patterns_used and language_from_path):
patch_vary_headers(response, ('Accept-Language',))
if 'Content-Language' not in response:
response['Content-Language'] = language
return response

Flask-Babel convert Flask-WTF SelectField

I want to convert Flask-WTF SelectField value with Flask-Babel.
Here is the snippet of my code:
from flask_babel import _, lazy_gettext as _l
class PaymentStatus(enum.Enum):
REJECTED = 'REJECTED'
COMPLETED = 'COMPLETED'
EXPIRED = 'EXPIRED'
def __str__(self):
return self.value
payment_status = [(str(y), y) for y in (PaymentStatus)]
def course_list():
return Course.query.all()
class PaymentForm(FlaskForm):
course_name = QuerySelectField(_l('Course name'), validators=[required()], query_factory=course_list)
status_of_payment = SelectField(_l('Payment Status'), choices=payment_status)
# ...
# ...
There, I want to localization the SelectField choices value and QuerySelectField query_factory value with Flask-Babel.
Is it possible..?, if so, any example or refer tutorial would be appreciated :)
The SelectField choices could be handled by lazy_gettext().
Quote from The Flask Mega-Tutorial Part XIII: I18n and L10n
Some string literals are assigned outside of a request, usually when the application is starting up, so at the time these texts are evaluated there is no way to know what language to use.
Flask-Babel provides a lazy evaluation version of _() that is called lazy_gettext().
from flask_babel import lazy_gettext as _l
class LoginForm(FlaskForm):
username = StringField(_l('Username'), validators=[DataRequired()])
# ...
For choices
from flask_babel import _, lazy_gettext as _l
class PaymentStatus(enum.Enum):
REJECTED = _l('REJECTED')
COMPLETED = _l('COMPLETED')
EXPIRED = _l('EXPIRED')
def __str__(self):
return self.value
QuerySelectField query_factory accepts values queried from the database. These values should not be handled by Flask-Babel/babel. Cause the database stores data outside the Python source code.
Possible solutions:
Add a translation field in the database table and update the translation manually. Or
Using a Third-Party Translation Service on the webpage and handle it by AJAX
BTW, The Flask Mega-Tutorial made by Miguel Grinberg is a very famous Flask tutorial. All these situations are included in it.

Django - Syntax highlighting with pygments

I'm currently working on my blog site. One of the most important thing in appearance is multi language syntax highlighter. So I decided to use Pygments library and I wrote some code:
from django import template
from pygments import highlight
from pygments.formatters.html import HtmlFormatter
from pygments.lexers import get_lexer_by_name, guess_lexer
from django.utils.safestring import mark_safe
from bs4 import BeautifulSoup
register = template.Library()
#register.filter(is_safe=True)
def highlighter(content):
soup = BeautifulSoup(unicode(content))
codeBlocks = soup.findAll(u'code')
for i,block in enumerate(codeBlocks):
if block.has_attr(u'class'):
language = block[u'class']
else:
language = u'text'
try:
lexer = get_lexer_by_name(language[0])
except ValueError:
try:
lexer = guess_lexer(unicode(block))
except ValueError:
lexer = get_lexer_by_name(language[0])
highlighting = highlight(unicode(block), lexer, HtmlFormatter())
block.replaceWith(highlighting)
return mark_safe(unicode(soup))
In my template i use somethink like this:
<p>{{ post.en_post_content|highlighter|safe|linebreaks}}</p>
Highlighting works well but I can't make it safe because this is what I receive:
http://i.gyazo.com/a2557c861e20a826b28cb5c261e6020f.png
I'm also worried about strange characters like "&#39"
I need advices on how to deal with it. Thanks in advance for reply.

Django how to mark_safe a link but not the arguments in a gettext translation

Let say we have a message like this :
messages.add_message(request, messages.SUCCESS,
_('Document %(doc_type_name)s %(name)s (%(fname)s) created.') % {
'doc_type_name': conditional_escape(document.doc_type.name),
'name': conditional_escape(document.title),
'fname': conditional_escape(document.name),
'url': document.get_absolute_url()
})
Here it will work only if we display the message with {{ message|safe }} but we don't want that since if there is some code in %(name) it will be executed too.
If I use:
messages.add_message(request, messages.SUCCESS,
mark_safe(_('Document %(doc_type_name)s %(name)s (%(fname)s) created.') % {
'doc_type_name': conditional_escape(document.doc_type.name),
'name': conditional_escape(document.title),
'fname': conditional_escape(document.name),
'url': document.get_absolute_url()
}))
The mark_safe doesn't work.
I read a solution over there : https://stackoverflow.com/a/12600388/186202
But it is the reverse that I need here:
_('Document %s created.') % mark_safe('')
And as soon as it goes through the ugettext function it is not safe anymore.
How should I do?
You are trying to mix view and logic by placing HTML inside Python code. Well, sometimes you just have to do this but it is not the case.
mark_safe() returns SafeString object which is treated by Django templates specially. If SafeString evaluated by ugettext or % you will get string again, it is an expected behaviour. You can not mark safe only formatting string, either complete output with doc name/title etc or everything is not safe. Ie, it will not work this way.
You can put HTML into template and use render_to_string(), and probably it is the best option.
Are document title, name and doc_type.name set by user? If not, you can skip mark_safe and document using HTML in document properties as feature.
Previous response are correct: you should avoid mixing python and html as much as possible.
To solve your issue:
from django.utils import six # Python 3 compatibility
from django.utils.functional import lazy
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
mark_safe_lazy = lazy(mark_safe, six.text_type)
then:
lazy_string = mark_safe_lazy(_("<p>My <strong>string!</strong></p>"))
Found in django documentation:
https://docs.djangoproject.com/en/1.9/topics/i18n/translation/#s-other-uses-of-lazy-in-delayed-translations

Pass a lazy translation string including variable to function in Django

Inside a Django view, I create a subject like that:
subject = _(u"%(user)s has posted a comment") % { 'user': user }
Then I pass this subject to a function, which handles email notifications:
send_notifications(request, subject, url)
In send_notifications, I iterate over all subscriptions and send emails. However, each user can have a different language, so I activate the user's language dynamically via Django's activate:
def send_notifications(request, subject, url):
from django.utils.translation import activate
for s in Subscription.objects.filter(url=url):
activate(s.user.userprofile.lang)
send_mail(subject, render_to_string('notification_email.txt', locals()), settings.SERVER_EMAIL, [s.user.email])
The template gets rendered in the correct language of each user. However, the subject is passed as an evaluated and translated string to send_notifications and thus, is not translated.
I played around with lazy translations and lambda functions as parameters, but without success. Any help appreciated :)
Instead of passing the translated subject, just pass it non translated:
subject = '%(user)s has posted a comment'
context = {'user': user}
def send_notifications(request, subject, url, context):
from django.utils.translation import activate
for s in Subscription.objects.filter(url=url):
activate(s.user.userprofile.lang)
send_mail(_(subject) % context, render_to_string('notification_email.txt', locals()), settings.SERVER_EMAIL, [s.user.email])
If you're not going to personalize the contents per user, then you might as well limit the number of renderings because that's a little confusing:
# also do your imports at the top to catch import issues early
from django.utils.translation import activate
from django.utils.translation import ugettext as _
def send_notifications(request, url,
translatable_subject, context,
body_template='notification_template.txt'):
previous_lang = None
for s in Subscription.objects.filter(url=url).order_by('user__userprofile__lang'):
if s.user.userprofile.lang != previous_lang:
activate(s.user.userprofile.lang)
subject = _(translatable_subject) % context
body = render_to_string(body_template, locals())
send_mail(subject, body, settings.SERVER_EMAIL, [s.user.email])
previous_lang = s.user.userprofile.lang
As such, it is much more obvious that you're not going to render emails per usage.
This slight rewrite should make you doubt about the original choice of a couple of names (locals, notification_template).
The above sample code is barely an "educated guess" and you should double check it and make sure you understand everything before you paste it.
Ok, found a solution myself. In case anybody runs into a similar problem:
from django.utils.translation import ugettext as _
# create subject as raw string in Django view
raw_subject = r"%(username)s has posted a comment"
# for the sake of generic variables, create a dictionary to pass to function
extra_context = { 'user': user }
# call function with raw string and dictionary as params
send_notifications(request, raw_subject, url, extra_context)
# translate the raw string inside send_notifications into the temporarily activated language
translated_subject = _(raw_subject) % extra_context
Appears to be working as desired :) Since we are working with several different notifications, I tried to avoid an extra template for each kind. However, calling a template with extra_context is also a possible solution.
I ended up creating a class that will handle the translation, passing the context of the string to this class.
from django.utils.translation import gettext_lazy as _
class Translatable(object):
def __init__(self, text, context):
self.text = text
self.context = context
def __str__(self):
return _(self.text).format(**self.context)
In your function, just call str(subject):
def send_notifications(request, subject, url):
from django.utils.translation import activate
for s in Subscription.objects.filter(url=url):
activate(s.user.userprofile.lang)
send_mail(str(subject), render_to_string('notification_email.txt', locals()), settings.SERVER_EMAIL, [s.user.email])
And passes subject to your function as following:
subject = Translatable(_("{user} has posted a comment"), context={'user': user})
send_notifications(request, subject, url)