#8033 closed Cleanup/optimization (fixed)
Better error handling for gettext and gettext_lazy issues
| Reported by: | Manuel Saelices | Owned by: | |
|---|---|---|---|
| Component: | Internationalization | Version: | dev |
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Ready for checkin | |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
It's very common get confused in use of gettext and gettext_lazy functions. An usual error was to use gettext at import time, for example, in any of this cases:
- In a parameter of
settings.pyfile:from django.utils.translation import ugettext as _ ... LANGUAGE_CHOICES = (('es', _('Spanish')), ('en', _('English')),) - In class or field definition of a
models.pyfile:from django.db import models from django.utils.translation import ugettext as _ class Author(models.Model): name = models.CharField(_(u'Name'), max_length=100)
In both cases, if you get an error, the error will be an ImportError usually in another part of code... This is very difficult to debug, because the real error should be "Incorrect use of gettext. You can't use gettext at import time".
The error is due to the fact of not all applications has been imported yet, and gettext resolution need to have all possible translation files loaded (django.po files), which implies loading of all applications. This causes a circular import error problem.
In this thread there is a discussion about this.
We are thinking in check in every call to gettext if django apps has been loaded (using django.db.models.loading.app_cache_ready()) and take one of this options:
- raise an representative error.
- return a dummy string.
I personally prefers first one, because gettext should be always called inside functions (at run time), and never at import time. I don't imagine a case of we need use gettext before that.
Attachments (5)
Change History (35)
comment:1 by , 17 years ago
follow-up: 6 comment:2 by , 17 years ago
I've found that previous real error was: TemplateDoesNotExist: 500.html but in DEBUG mode, fails in variables and context rendering.
by , 17 years ago
| Attachment: | better_error_handling_for_ugettext_r8138.2.diff added |
|---|
A better patch... but It's seems to have problems in apache detection of app_cache_ready
by , 17 years ago
| Attachment: | better_error_handling_for_ugettext_r8138.diff added |
|---|
A better patch... but It's seems to have problems in apache detection of app_cache_ready
comment:3 by , 17 years ago
Now, the problem I get in apache is that always raise error, even when call gettext at run time. But the problem seems to be that app_cache_ready() doesnt work in Apache. Always returns False, even inside a django view.
comment:4 by , 17 years ago
I think I find an error with apps installation. I don't find any code point that forces a django apps loading, and apps loading occurs if some things happends, like gettext call. For example, if in your first line of your django view, in a production server with mod_python, you call to django.db.models.loading.app_cache_ready() you get False.
I will attach a patch that fixes this problem, but changes Django core parts (django.core.handlers.base module). Ok, I can say that this core changes passes all tests and works in my dev and production server.
by , 17 years ago
| Attachment: | better_error_handling_for_ugettext_r8138.3.diff added |
|---|
This patch fixes apache problems, forcing apps loading just in request processing
follow-up: 8 comment:5 by , 17 years ago
| Owner: | changed from to |
|---|---|
| Triage Stage: | Unreviewed → Accepted |
This is pretty evil. :-(
We intentionally do lazy apps loading for a reason (but I cannot, for the life of me, remember why that is). I'll try to remember the reason, but I don't like forcing it like this. It's certainly not a bug that it happens lazily. I'd prefer to work around requiring it to be loaded. All that being said, your patch is a good start, so I'll have a read of it tonight.
follow-up: 7 comment:6 by , 17 years ago
Replying to msaelices:
I've found that previous real error was:
TemplateDoesNotExist: 500.htmlbut in DEBUG mode, fails in variables and context rendering.
The final problem was Django tries to translate server_time to a spanish date, for rendering 500 template, in django.core.debug module. For fix that, I deactivated all translation in 500 error handling and I checked in do_translate that translations are activated, before raise an ImproperlyImplemented exception if is the case.
comment:7 by , 17 years ago
Replying to msaelices:
Replying to msaelices:
I've found that previous real error was:
TemplateDoesNotExist: 500.htmlbut in DEBUG mode, fails in variables and context rendering.
The final problem was Django tries to translate
server_timeto a spanish date, for rendering 500 template, indjango.core.debugmodule. For fix that, I deactivated all translation in 500 error handling and I checked indo_translatethat translations are activated, before raise anImproperlyImplementedexception if is the case.
do you refer to do in do_translate something like this?:
from django.db.models.loading import get_apps def do_translate(message, translation_function): global _default, _active t = _active.get(currentThread(), None) try: get_apps() except ImportError: raise ImproperlyImplemented( "ImportError occurs, maybe it was caused because" "django.utils.translation.%(func_name)s function was called at " "import time to perform a translation of '%(message)s'. " "%(func_name)s function must be called after " "applications loading." % {'func_name': translation_function, 'message': message})
comment:8 by , 17 years ago
Replying to mtredinnick:
This is pretty evil. :-(
We intentionally do lazy apps loading for a reason (but I cannot, for the life of me, remember why that is). I'll try to remember the reason, but I don't like forcing it like this. It's certainly not a bug that it happens lazily. I'd prefer to work around requiring it to be loaded. All that being said, your patch is a good start, so I'll have a read of it tonight.
Sorry for my previous comment... was in bad thread.
About your work around... do you refer to do in do_translate something like this?:
from django.db.models.loading import get_apps
def do_translate(message, translation_function):
global _default, _active
t = _active.get(currentThread(), None)
try:
get_apps()
except ImportError:
raise ImproperlyImplemented(
"ImportError occurs, maybe it was caused because"
"django.utils.translation.%(func_name)s function was called at "
"import time to perform a translation of '%(message)s'. "
"%(func_name)s function must be called after "
"applications loading." % {'func_name': translation_function,
'message': message})
...
comment:9 by , 17 years ago
And with respect lazy apps loading. I dont mean is a bug, but the fail may be in let apps rely on app_cache_ready, because maybe never get loaded. Maybe the reason for lazy apps loading was performance. If you count time of loading templatetags, etc. When you do a simple HttpResponseRedirect, may not have to import any apps.
comment:10 by , 17 years ago
@msaelices: how are you checking things with this patch? I realise it's pretty hard to write automated tests and I'm not really worried about that. I just want to make sure I'm covering all the possibilities you are when I make changes. So I'm using ugettext() in the __init__.py file of an app and in a model field definition. Anything else?
comment:11 by , 17 years ago
It's very strange thing. I dont get to make fails django trunk, but I remember fixed several related issues by gettext_lazy. We also take a one of my company project and test it with actual django version and works. Also I try to update to a previous django revision (r8100) and works too.
What happening?
I will attach my test project, with uses gettext in forms.py and models.py, with two apps inside and one uses the other.
comment:12 by , 17 years ago
| Has patch: | set |
|---|---|
| Patch needs improvement: | set |
I haven't had as much time to look at this as I would have liked, but I just gave it another read. My feeling is that it is over-engineered. Firstly, it's not always wrong to use ugettext() in a module at import time (because of dynamic import possibilities), but it usually is.
I also feel that the patch is trying to solve too large a problem. The real problem that we want to avoid is calling the translation machinery whilst apps are being imported. We have a way to tell when the app loading is finished. So probably all that we need is a way to tell is the app loading is in progress. How about adding an extra method/flag to the AppCacheclass to indicate that loading is in progress? Then we can test that before trying to access a translation and, if it's true, raise an error. The number of lines of code should be less and it catches the really nasty problem case.
Adding lots and lots of lines of code to handle an error situation that is a developer error is adding complexity that shouldn't be necessary. A clear error message to describe the problem is good, though, but I think we've gone a bit too far in trying to hold the hand of the developer in this patch.
comment:13 by , 17 years ago
You're completely right. I'll try to make a better patch with your idea.
by , 17 years ago
| Attachment: | better_error_handling_for_ugettext_r8377.diff added |
|---|
Using a flag for no try translation while django apps is still loading
comment:14 by , 17 years ago
@mtredinnick, I added a patch with your idea. But, I think we do not solve all kind of problems. Still there is a possibility to call ugettext before first app was tried to load. In this case, ugettext was no check any problem, and a circular import error will occurs (because this translation would begin apps loading).
What is your opinion?
comment:15 by , 17 years ago
| milestone: | → 1.0 maybe |
|---|
comment:16 by , 17 years ago
| milestone: | 1.0 maybe → 1.0 |
|---|
comment:17 by , 17 years ago
| milestone: | 1.0 → post-1.0 |
|---|
This can wait until after 1.0. The patch still needs some work (we're aren't going to raise an exception saying Django isn't properly implemented, since that isn't true: this feature is about errors in the user's code). I'm also fairly sure it would be safe to use ugettext() before importing all models and that could be a genuine use-case (e.g. in cronjobs), but it needs testing and mistakes now would be stabilising.
Since this is only a diagnostic check and only triggered for code that is already broken, we can push it to later.
comment:18 by , 17 years ago
| Owner: | removed |
|---|
comment:20 by , 15 years ago
| Severity: | → Normal |
|---|---|
| Type: | → New feature |
comment:23 by , 11 years ago
| Patch needs improvement: | unset |
|---|---|
| Type: | New feature → Cleanup/optimization |
I think that after app loading refactor, the situation has improved a lot.
However, here is a patch to add a complementary error message to this sort of errors: https://github.com/django/django/pull/2838
comment:24 by , 11 years ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
comment:25 by , 11 years ago
| Triage Stage: | Ready for checkin → Accepted |
|---|
If we're creating a specific exception for the app registry not being ready, it has no reason to inherit RuntimeError.
Furthermore, this patch changes the error message without updating the docs — which are important as this is going to be one of the pain points in 1.7.
comment:26 by , 11 years ago
Upon further thought, I'm in favor of introducing AppRegistryNotReady(Exception) and updating the documentation accordingly.
comment:28 by , 11 years ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
comment:29 by , 11 years ago
| Owner: | set to |
|---|---|
| Resolution: | → fixed |
| Status: | new → closed |
Previous patch pass all tests, but if you test in an apache deployment, it fails in rendering of 500 template, with this error:
Mod_python error: "PythonHandler django.core.handlers.modpython" Traceback (most recent call last): File "/usr/lib/python2.5/site-packages/mod_python/apache.py", line 299, in HandlerDispatch result = object(req) File "/var/lib/python-support/python2.5/django/core/handlers/modpython.py", line 211, in handler return ModPythonHandler()(req) File "/var/lib/python-support/python2.5/django/core/handlers/modpython.py", line 184, in __call__ response = self.get_response(request) File "/var/lib/python-support/python2.5/django/core/handlers/base.py", line 126, in get_response return self.handle_uncaught_exception(request, resolver, exc_info) File "/var/lib/python-support/python2.5/django/core/handlers/base.py", line 146, in handle_uncaught_exception return debug.technical_500_response(request, *exc_info) File "/home/lin/django_src/django/views/debug.py", line 39, in technical_500_response html = reporter.get_traceback_html() File "/home/lin/django_src/django/views/debug.py", line 113, in get_traceback_html return t.render(c) File "/var/lib/python-support/python2.5/django/template/__init__.py", line 176, in render return self.nodelist.render(context) File "/var/lib/python-support/python2.5/django/template/__init__.py", line 751, in render bits.append(self.render_node(node, context)) File "/var/lib/python-support/python2.5/django/template/debug.py", line 81, in render_node raise wrapped TemplateSyntaxError: Caught an exception while rendering: django.utils.translation.gettext function called at import time to perform a translation of 'Tue'. gettext function must be calledcalled after applications loading Original Traceback (most recent call last): File "/var/lib/python-support/python2.5/django/template/debug.py", line 71, in render_node result = node.render(context) File "/var/lib/python-support/python2.5/django/template/debug.py", line 87, in render output = force_unicode(self.filter_expression.resolve(context)) File "/var/lib/python-support/python2.5/django/template/__init__.py", line 542, in resolve new_obj = func(obj, *arg_vals) File "/var/lib/python-support/python2.5/django/template/defaultfilters.py", line 627, in date return format(value, arg) File "/var/lib/python-support/python2.5/django/utils/dateformat.py", line 264, in format return df.format(format_string) File "/var/lib/python-support/python2.5/django/utils/dateformat.py", line 29, in format pieces.append(force_unicode(getattr(self, piece)())) File "/var/lib/python-support/python2.5/django/utils/dateformat.py", line 174, in r return self.format('D, j M Y H:i:s O') File "/var/lib/python-support/python2.5/django/utils/dateformat.py", line 29, in format pieces.append(force_unicode(getattr(self, piece)())) File "/var/lib/python-support/python2.5/django/utils/encoding.py", line 49, in force_unicode s = unicode(s) File "/var/lib/python-support/python2.5/django/utils/functional.py", line 201, in __unicode_cast return self.__func(*self.__args, **self.__kw) File "/var/lib/python-support/python2.5/django/utils/translation/__init__.py", line 62, in ugettext return real_ugettext(message) File "/var/lib/python-support/python2.5/django/utils/translation/trans_real.py", line 295, in ugettext return do_translate(message, 'ugettext') File "/var/lib/python-support/python2.5/django/utils/translation/trans_real.py", line 278, in do_translate "called after applications loading" % message) ImproperlyImplemented: django.utils.translation.gettext function called at import time to perform a translation of 'Tue'. gettext function must be calledcalled after applications loading