Look at this snippet of Django code in
models.py, and in particular
from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils.safestring import mark_safe class MyModel(models.Model): my_field = models.CharField(max_length=123, help_text=mark_safe(_('Some <b>help</b> text.')))
For those unfamiliar with Django. A quick run-down:
- The definition of
MyModelcreates a mapping between the MyModel class and a underlying
app_mymodeltable in a database.
- That table will consist of two columns:
id, an automatic integer as primary key (created by default), and
my_field, a varchar/text field of at most 123 characters.
- With minimal effort a HTML form can be generated from this. That
form will show
my_fieldas a text input box and near it the text we defined in
_()-function has the gettext functions run over it so the text can be served in different languages with minimal effort.
mark_safe()-function tells the template rendererer that this output is already safe to use in HTML. Without it, the user would see:
Some <b>help</b> text.
Unfortunately this doesn't do what you would expect.
Let's examine why.
There is a reason why we use the
ugettext_lazy wrapper in
This code is executed once at startup / first run, and the language that
was selected at that time would be substituted if we used the
ugettext. The lazy variant makes sure the substitution takes
place at the last possible time.
mark_safe forces the translation to happen immediately.
In the best case that means someone else can get the help text served in
the wrong language. In the worst case, you get a recursive import when
the translation routines attempt to import all
looking for locale files. Your
MyModel might be referenced from one of
those apps. The result: recursion and a resulting
... File "someapp/models.py", line 5, in <module> class MyModel(models.Model): File "someapp/models.py", line 6, in <module> my_field = models.CharField(max_length=123, File "django/utils/safestring.py", line 101, in mark_safe return SafeUnicode(s) ... File "django/utils/translation/trans_real.py", line 180, in _fetch app = import_module(appname) File "django/utils/importlib.py", line 35, in import_module __import__(name) ... ImportError: cannot import name MyModel
Lessons learnt: if you're using translations then don't call
on anything until it's view time.
In this case, we would fix it by adding the
mark_safe call to the
Form constructor. We know that that is run for every form
instantiation, so that's late enough.
class MyModelForm(forms.ModelForm): class Meta: model = MyModel def __init__(self, *args, **kwargs): super(MyModelForm, self).__init__(*args, **kwargs) self.fields['my_field'].help_text = mark_safe(self.fields['my_field'].help_text)
But suggestions for prettier solutions are welcome.
The Django 1.4 docs provide the better solution:
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)