Переход с Django 3 на Django 5: что ломается первым
Django 5.x убрал десятки deprecated-функций, которые в Django 3 работали без единого предупреждения. Разбираем конкретные точки поломок — от url() и force_text до изменений в полях форм и middleware — и показываем, как найти их до того, как это сделает продакшен.

Почему нельзя перепрыгнуть через два мажорных релиза и не сломаться
Django придерживается жёсткого deprecation-цикла: функция объявляется устаревшей в версии N, выбрасывает DeprecationWarning в N+1 и удаляется в N+2. Между Django 3.2 LTS и Django 5.0 прошли два таких полных цикла — 4.x и 5.x. Всё, что было deprecated в 3.2, гарантированно удалено в 5.0. Если проект жил на 3.2 с подавленными предупреждениями, при попытке запуска на 5.x он упадёт с ImportError или AttributeError — не в редком edge case, а на старте.
Ниже — конкретные точки, которые ломаются чаще всего, с текстом ошибки и минимальным исправлением. Порядок — от самых частых к менее очевидным.
Удалённые импорты: первое, на чём всё падает
Большинство проектов не доходят даже до runserver — процесс умирает на этапе импорта модулей. Вот главные жертвы.
django.conf.urls.url()
Функция url() с регулярными выражениями была deprecated в Django 3.1 и удалена в 4.0. Если вы как-то дожили с ней до попытки перехода на 5.x, вы увидите:
ImportError: cannot import name 'url' from 'django.conf.urls'
Замена — re_path() из django.urls для регулярных выражений или path() для простых маршрутов. re_path() принимает тот же первый аргумент, так что в большинстве случаев достаточно заменить имя функции и импорт.
force_text и smart_text
django.utils.encoding.force_text и smart_text были псевдонимами force_str и smart_str, оставшимися со времён поддержки Python 2. Удалены в Django 4.0.
ImportError: cannot import name 'force_text' from 'django.utils.encoding'
Замена — force_str и smart_str из того же модуля. Опасность в том, что эти функции часто используют сторонние пакеты: django-rest-framework старых версий, django-filter, различные платёжные интеграции. Если ошибка прилетает из зависимости — обновлять нужно зависимость, а не патчить Django.
ugettext и ugettext_lazy
Ещё один артефакт Python 2. django.utils.translation.ugettext и ugettext_lazy удалены в Django 4.0. Замена — gettext и gettext_lazy.
Прочие удалённые модули
Полный список длинный, но часто встречаются:
django.utils.decorators.ContextDecorator— используйтеcontextlib.ContextDecoratorиз стандартной библиотеки;django.utils.encoding.python_2_unicode_compatible— просто удалите декоратор;django.contrib.admin.utils.lookup_needs_distinct— заменён наlookup_spawns_duplicates(с Django 4.0).
Сторонние пакеты — главный источник скрытых поломок
Собственный код вы контролируете. Зависимости — нет. Вот типичный сценарий: вы исправили все свои импорты, запускаете проект, а он падает внутри django-allauth или django-mptt, потому что установлена версия двухлетней давности.
Правило: прежде чем обновлять Django, обновите все сторонние пакеты до последних версий, совместимых с текущей версией Django. Потом — поднимайте Django. Два изменения одновременно превращают отладку в лотерею.
Проверьте совместимость зависимостей до перехода. Полезная команда — pip list --outdated — покажет, что отстало. Но не вся правда в номерах версий: у некоторых пакетов совместимость с Django 5 появилась только в последнем минорном релизе. Читайте changelog каждого пакета — да, это скучно, но дешевле, чем отладка.
Формы и поля: тихие изменения в Django 5.0
Эти поломки коварнее, потому что проект запускается — но ведёт себя не так.
Новые field groups и изменённый рендеринг
Django 5.0 ввёл концепцию field groups и шаблоны для рендеринга полей форм. Если вы использовали кастомные виджеты или переопределяли шаблоны полей через FORM_RENDERER, они могут отображаться не так, как ожидалось — не из-за ошибки, а потому что изменился контекст шаблона.
Проверяйте рендеринг форм визуально, а не только по тестам. Автотесты редко проверяют вёрстку формы — они смотрят на HTTP-коды и данные в базе.
Обязательные аргументы у Field
Ряд полей модели стал строже к аргументам. Например, в Django 5.0 у GeneratedField (новое поле) свои правила, а у существующих полей некоторые ранее игнорируемые параметры теперь вызывают ошибки системы проверок (check).
Middleware и ASGI: что поменялось под капотом
В Django 3.x можно было использовать старый стиль middleware (класс с process_request/process_response без __init__(get_response)). Поддержка этого стиля через MiddlewareMixin формально осталась, но если вы писали middleware без наследования от MiddlewareMixin и без принятия get_response — в Django 5.x оно не заработает.
Отдельная боль — ASGI. Django 5.x улучшил асинхронную поддержку, но если ваш проект использует WSGI-only middleware в ASGI-контексте, поведение может отличаться от 3.x. Тема обширная и заслуживает отдельного разбора; здесь важно знать: если вы переходите на ASGI одновременно с обновлением — разделите эти два изменения.
Таблица ключевых поломок
| Что сломано | Текст ошибки (суть) | Удалено в | Замена |
|---|---|---|---|
django.conf.urls.url() | ImportError: cannot import name 'url' | 4.0 | django.urls.re_path() или path() |
force_text, smart_text | ImportError: cannot import name 'force_text' | 4.0 | force_str, smart_str |
ugettext, ugettext_lazy | ImportError: cannot import name 'ugettext' | 4.0 | gettext, gettext_lazy |
default_app_config в __init__.py | Нет ошибки, просто игнорируется | Не используется с 5.0 | Удалить строку; AppConfig определяется автоматически |
Старый стиль middleware без get_response | TypeError: __init__() takes 1 positional argument | Не работает с 4.x+ | Наследуйтесь от MiddlewareMixin или примите get_response |
USE_L10N = False | Нет ошибки; значение по умолчанию стало True в 4.0 | Настройка удалена в 5.0 | Локализация включена всегда; форматы настраивайте через FORMAT_MODULE_PATH |
HttpRequest.is_ajax() | AttributeError: 'WSGIRequest' object has no attribute 'is_ajax' | 4.0 | Проверяйте заголовок X-Requested-With вручную |
Сноска: deprecation-цикл в Django — это два мажорных релиза. Функция помечается deprecated в версии N, продолжает работать с предупреждением в N+1 и удаляется в N+2. Это значит, что между «работает без нареканий» и «удалено навсегда» проходит примерно 2–3 года.
Стратегия перехода: не прыгайте, шагайте
Перескочить с 3.2 на 5.x одним коммитом — плохая идея. Django-разработчики прямо рекомендуют поэтапное обновление: 3.2 → 4.0 → 4.1 → 4.2 LTS → 5.0 → 5.1. На каждом шаге:
- Обновите зависимости до совместимых версий.
- Запустите тесты с включёнными
DeprecationWarning:python -Wd manage.py test. - Исправьте все предупреждения — это будущие ошибки.
- Поднимите версию Django на один мажорный релиз.
- Запустите тесты снова. Если падают — исправляйте; если проходят — переходите к следующему шагу.
Да, это пять переходов вместо одного. Но каждый из них — маленький и понятный. Один большой прыжок порождает десятки ошибок одновременно, и непонятно, какая из них первична.
Что делать с покрытием тестами
Если покрытия мало (а на проектах, застрявших на Django 3, оно обычно скромное), имеет смысл перед обновлением написать хотя бы smoke-тесты: каждый URL из urls.py отдаёт не 500; каждая модель создаётся; каждая форма рендерится. Это не заменяет полноценного покрытия, но ловит именно те поломки, о которых эта статья.
Граничные случаи и неочевидности
- Кастомные менеджеры моделей. Если менеджер использовал
get_queryset()с вызовами, которые зависели от удалённых внутренних API QuerySet — ломается молча: запросы возвращают неожиданные результаты, а не ошибки. - Шаблонные теги. Ваши кастомные теги, наследующие от внутренних классов Django, могут потерять аргументы или получить новые обязательные. Это не документировано как breaking change — внутренние классы не входят в public API.
- Базы данных. Django 5.0 поднял минимальные версии: PostgreSQL 12+, MySQL 8.0.11+, SQLite 3.27+. Если сервер БД старше — Django не запустится, и ошибка будет неочевидной:
NotSupportedErrorпри миграции, а не при старте. - Python. Django 5.0 требует Python 3.10+, Django 5.1 — Python 3.10+. Если на сервере Python 3.8, вы не сможете даже установить пакет.
Что делать дальше
После успешного перехода на Django 5.x включите в CI прогон тестов с флагом -Wd, чтобы ловить новые DeprecationWarning до того, как они превратятся в ошибки при следующем обновлении. Изучите новые возможности Django 5 — GeneratedField, улучшенные field groups для форм, встроенные декораторы для асинхронных вьюх — но уже после того, как стабилизируете существующий код. Следующий LTS — Django 5.2; если вы хотите стабильности на 2–3 года, ориентируйтесь на него как на целевую версию.
Редактор изображений: Убрать лишнее, заменить фон, добавить объект, расширить кадр — на вашем изображении.
Спросить за 20 ₽Источники: Django 5.0 release notes — official documentation, Django deprecation timeline — official documentation
Частые вопросы
Можно ли перейти с Django 3.2 сразу на 5.x, минуя 4.x?
Сторонние пакеты не обновляются — что делать?
Как узнать все deprecated-функции в моём проекте до обновления?
Проект упал с ImportError в пакете, который я не писал. Это баг Django?
У меня другая версия — Django 2.x. Инструкция подходит?
Что ломается не с ошибкой, а молча — работает, но неправильно?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.