2025-01-13
A mystery with Django under Apache's mod_wsgi on Ubuntu 24.04
We have a long standing Django web application that these days runs under Python 3 and a more modern version of Django. For as long as it has existed, it's had some forms that were rendered to HTML through templates, and it has rendered errors in those forms in what I think of as the standard way:
{{ form.non_field_errors }} {% for field in form %} [...] {{ field.errors }} [...] {% endfor %}
This web application runs in Apache using mod_wsgi, and I've recently been working on moving the host this web application runs on to Ubuntu 24.04 (still using mod_wsgi). When I stood up a test virtual machine and looked at some of these HTML forms, what I saw was that when there were no errors, each place that errors would be reported was '[]' instead of blank. This did not happen if I ran the web application on the same test machine in Django's 'runserver' development testing mode.
At first I thought that this was something to do with locales, but
the underlying cause is much more bizarre and inexplicable to me.
The template operation for form.non_field_errors
results in
calling Form.non_field_errors(), which returns a
django.forms.utils.ErrorList
object (which is also what field.errors
winds up being). This
class is a multiple-inheritance subclass of UserList, list, and
django.form.utils.RenderableErrorMixin.
The latter is itself a subclass of django.forms.utils.RenderableMixin,
which defines a __str__() special method value that is
RenderableMixin.render(), which renders the error list properly,
including rendering it as a blank if the error list is empty.
In every environment except under Ubuntu 24.04's mod_wsgi,
ErrorList.__str__
is RenderableMixin.render
and everything
works right for things like 'form.non_field_errors
' and
'field.errors
'. When running under Ubuntu 24.04's mod_wsgi,
and only then, ErrorList.__str__
is actually the standard
list.__str__
, so empty lists render as '[]' (and had I tried
to render any forms with actual error reports, worse probably would
have happened, especially since list.__str__ isn't carefully
escaping special HTML characters).
I have no idea why this is happening in the 24.04 mod_wsgi. As
far as I can tell, the method resolution order (MRO) for ErrorList
is the same under mod_wsgi as outside it, and sys.path
is the
same. The RenderableErrorMixin class is getting included as a parent
of ErrorList, which I can tell because RenderableMixin also provides
a __html__
definition, and ErrorList.__html__
exists and
is correct.
The workaround for this specific situation is to explicitly render errors to some format instead of counting on the defaults; I picked .as_ul(), because this is what we've normally gotten so far. However the whole thing makes me nervous since I don't understand what's special about the Ubuntu 24.04 mod_wsgi and who knows if other parts of Django are affected by this.
(The current Django and mod_wsgi setup is running from a venv, so it should also be fully isolated from any Ubuntu 24.04 system Python packages.)
(This elaborates on a grumpy Fediverse post of mine.)