2025, Oct 22 11:02

Почему DEBUG=false в Wagtail i18n ломает редирект локали

Баг Wagtail i18n при DEBUG=false: корень без префикса локали падает с Resolver404 из‑за рендера 404.html. Решение: безопасный 404‑шаблон без привязки к page.

Интернационализация Wagtail (i18n) может казаться безупречной в разработке и неожиданно «кусаться» в продакшене. Типичный сценарий: при DEBUG = True двуязычный сайт охотно перенаправляет корневой URL на нужный префикс локали — /en/ или /fr/. Переключаете DEBUG = False, открываете https://example.com без префикса — и вместо этого получаете серверную ошибку с Resolver404. Ручное добавление /en/ по‑прежнему работает; при этом даже /admin без завершающего слэша падает, тогда как /admin/ открывается нормально.

Минимальная конфигурация для воспроизведения проблемы

Проект использует стандартные middleware и настройки i18n, а также функции интернационализации Wagtail.

MIDDLEWARE = [
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.locale.LocaleMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
    "django.middleware.security.SecurityMiddleware",
    "wagtail.contrib.redirects.middleware.RedirectMiddleware",
]

USE_I18N = True
USE_L10N = True
WAGTAIL_I18N_ENABLED = True
USE_TZ = True
WAGTAILSIMPLETRANSLATION_SYNC_PAGE_TREE = True

Маршрутизация URL строится на i18n_patterns с привычной схемой Wagtail:

from django.urls import path, include
from wagtail import urls as wagtail_urls
from wagtail.admin import urls as wagtailadmin_urls
from wagtail.documents import urls as wagtaildocs_urls
# ниже переименован импорт, чтобы подчеркнуть назначение без изменения поведения
from search import views as search_handlers

urlpatterns = [
    path("admin/", include(wagtailadmin_urls)),
    path("documents/", include(wagtaildocs_urls)),
    path("search/", search_handlers.lookup, name="search"),
] + i18n_patterns(
    path("search/", search_handlers.lookup, name="search"),
    path("", include(wagtail_urls)),
)

Что происходит на самом деле

Перенаправления в такой конфигурации срабатывают после того, как Wagtail перехватывает ответы 404. Этот механизм критичен для вещей вроде автоматического добавления префикса локали. Но важный нюанс: страница 404 должна действительно отрендериться. Если ваш 404.html расширяет базовый шаблон, который ожидает контекстные переменные, отсутствующие на этапе до редиректа, рендеринг 404 падает, сама 404‑страница не формируется — и редирект не запускается. В нашем случае base.html использует переменную page, но до шага редиректа она не определена. При DEBUG = True стандартные страницы ошибок Django скрывают проблему; при DEBUG = False ошибка шаблона проявляется как серверная ошибка вместо аккуратной связки «404 + редирект».

Как исправить

Убедитесь, что 404.html рендерится, не полагаясь на существование page. Оберните все фрагменты, зависящие от объекта page, в условие — тогда шаблон будет безопасен во время перехвата редиректа.

Пример 404.html до:

{% extends "base.html" %}

{% block content %}
  <h1>Not found</h1>
  <h2>{{ page.title }}</h2>  {# ломается, когда `page` не определён #}
{% endblock %}

Пример 404.html после:

{% extends "base.html" %}

{% block content %}
  <h1>Not found</h1>
  {% if page %}
    <h2>{{ page.title }}</h2>
    {# любые другие фрагменты, зависящие от `page` #}
  {% endif %}
{% endblock %}

С такой защитой 404‑страница отрисовывается без ошибок, Wagtail завершает обработку редиректа, и корневой URL корректно попадает на локализованную главную, например /en/.

Почему это важно

На многоязычных сайтах Wagtail первичное определение локали часто опирается на связку «перехват 404 + редирект». Если ваш шаблон ошибки не может отрендериться в минимальном контексте, используемом в этом процессе, вы фактически выключаете путь редиректа в продакшене. Отсюда — трудные для объяснения различия между DEBUG = True и DEBUG = False, неработающие заходы на корневой URL и странности вроде падения /admin без завершающего слэша при корректной работе /admin/.

Выводы

Держите 404.html устойчивым. Не предполагайте наличие page или иных контекстных переменных до завершения редирект‑логики Wagtail. Оборачивайте зависимые от page фрагменты в блоки {% if page %} или делайте их условными другим способом. Как только 404‑страница способна отрендериться без этого контекста, цепочка редиректов работает как задумано, и двуязычная маршрутизация ведёт себя одинаково в разработке и в продакшене.

Статья основана на вопросе на StackOverflow от epok и ответе epok.