Примечания к выпуску Django 4.2

3 апреля 2023 г.

Добро пожаловать в Django 4.2!

Эти заметки о выпуске охватывают new features, а также некоторые backwards incompatible changes, на которые следует обратить внимание при переходе с версии Django 4.1 или более ранней. Мы begun the deprecation process for some features.

См. руководство Как обновить Django до более новой версии, если вы обновляете существующий проект.

Django 4.2 обозначен как long-term support release. Она будет получать обновления безопасности в течение как минимум трех лет после выпуска. Поддержка предыдущего LTS, Django 3.2, завершится в апреле 2024 года.

Совместимость с Python

Django 4.2 поддерживает Python 3.8, 3.9, 3.10 и 3.11. Мы настоятельно рекомендуем и официально поддерживаем только последний релиз каждой серии.

Что нового в Django 4.2

Поддержка Psycopg 3

Django теперь поддерживает psycopg версии 3.1.8 и выше. Для обновления кода установите psycopg library, ENGINE менять не нужно, так как django.db.backends.postgresql поддерживает обе библиотеки.

Поддержка psycopg2, скорее всего, будет устаревшей и удалена в будущем.

Следует иметь в виду, что в psycopg 3 внесены некоторые принципиальные изменения по сравнению с psycopg2. В связи с этим может потребоваться внести некоторые изменения для учета differences from psycopg2.

Комментарии к столбцам и таблицам

Новые опции Field.db_comment и Meta.db_table_comment позволяют создавать комментарии к столбцам и таблицам соответственно. Например:

from django.db import models


class Question(models.Model):
    text = models.TextField(db_comment="Poll question")
    pub_date = models.DateTimeField(
        db_comment="Date and time when the question was published",
    )

    class Meta:
        db_table_comment = "Poll questions"


class Answer(models.Model):
    question = models.ForeignKey(
        Question,
        on_delete=models.CASCADE,
        db_comment="Reference to a question",
    )
    answer = models.TextField(db_comment="Question answer")

    class Meta:
        db_table_comment = "Question answers"

Кроме того, новая операция AlterModelTableComment позволяет изменять комментарии к таблице, заданные в Meta.db_table_comment.

Защита от атаки BREACH

GZipMiddleware теперь включает в себя защиту от атаки BREACH. Оно добавляет до 100 случайных байтов в gzip-ответы, чтобы затруднить атаки BREACH. Подробнее о методе защиты от атак читайте в Heal The Breach (HTB) paper.

Файловое хранилище в памяти

Новый класс django.core.files.storage.InMemoryStorage предоставляет непостоянное хранилище, полезное для ускорения тестирования за счет отказа от обращения к диску.

Пользовательские файловые хранилища

Новый параметр STORAGES позволяет настраивать несколько пользовательских бэкендов хранения файлов. Она также управляет механизмами хранения для управления files (ключ "default") и static files (ключ "staticfiles").

Старые настройки DEFAULT_FILE_STORAGE и STATICFILES_STORAGE в этом выпуске устарели.

Незначительные особенности

django.contrib.admin

  • Светлая или темная цветовая тема администратора теперь может быть выбрана в пользовательском интерфейсе, а также может быть установлена в соответствии с системными настройками.
  • Стек шрифтов администратора теперь предпочитает системные шрифты пользовательского интерфейса и больше не требует загрузки шрифтов. Кроме того, доступны переменные CSS для более удобного переопределения семейств шрифтов по умолчанию.
  • Шаблон admin/delete_confirmation.html теперь имеет несколько дополнительных блоков и скриптовых крючков, облегчающих его настройку.
  • Выбранные опции виджетов filter_horizontal и filter_vertical теперь можно фильтровать.
  • В шаблоне admin/base.html теперь появился новый блок nav-breadcrumbs, содержащий навигационный ориентир и блок breadcrumbs.
  • ModelAdmin.list_editable теперь использует атомарные транзакции при выполнении правок.
  • jQuery обновлен с версии 3.6.0 до 3.6.4.

django.contrib.auth

  • Количество итераций по умолчанию для хешера паролей PBKDF2 увеличено с 390 000 до 600 000.
  • UserCreationForm теперь сохраняет поля формы «многие-ко-многим» для пользовательской модели пользователя.
  • Новый класс BaseUserCreationForm теперь является рекомендуемым базовым классом для настройки формы создания пользователя.

django.contrib.gis

  • Теперь GeoJSON serializer выводит ключ id для сериализованных функций, который по умолчанию равен первичному ключу объектов.
  • Класс GDALRaster теперь поддерживает pathlib.Path.
  • Класс GeoIP2 теперь поддерживает файлы .mmdb, загружаемые с DB-IP.
  • Виджет шаблона OpenLayers больше не включает встроенный CSS (при этом также удаляется прежний блок map_css), чтобы лучше соответствовать строгой политике безопасности содержимого.
  • OpenLayersWidget теперь базируется на OpenLayers 7.2.2 (ранее 4.6.5).
  • Новый поиск isempty и выражение IsEmpty() позволяют фильтровать пустые геометрии в PostGIS.
  • Новые функции FromWKB() и FromWKT() позволяют создавать геометрии из общеизвестного двоичного (WKB) и общеизвестного текстового (WKT) представлений.

django.contrib.postgres

django.contrib.sitemaps

  • Новый метод Sitemap.get_languages_for_item() позволяет настраивать список языков, для которых отображается элемент.

django.contrib.staticfiles

  • В операторе ManifestStaticFilesStorage появилась экспериментальная поддержка замены путей к JavaScript-модулям в операторах import и export на их хэшированные аналоги. Если вы хотите попробовать, создайте подкласс ManifestStaticFilesStorage и установите атрибут support_js_module_import_aggregation в значение True.
  • Новый атрибут ManifestStaticFilesStorage.manifest_hash содержит хэш всех файлов в манифесте и изменяется при изменении одного из файлов.

Бэкенды баз данных

  • Новая опция "assume_role" теперь поддерживается в OPTIONS на PostgreSQL, позволяя указывать session role.
  • Новая опция "server_side_binding" теперь поддерживается в OPTIONS на PostgreSQL с версией psycopg 3.1.8+, что позволяет использовать server-side binding cursors.

Отчеты об ошибках

  • На странице отладки теперь отображаются exception notes и fine-grained error locations на Python 3.11+.
  • Сессионные cookies теперь рассматриваются как учетные данные и поэтому скрываются, а в отчетах об ошибках заменяются звездочками (**********).

Формы

  • ModelForm теперь принимает новую опцию Meta formfield_callback для настройки полей формы.
  • modelform_factory() теперь уважает атрибут formfield_callback у form в Meta.

Интернационализация

  • Добавлена поддержка и переводы для центральнокурдского языка (сорани).

Ведение журнала

  • Регистратор django.db.backends теперь регистрирует запросы управления транзакциями (BEGIN, COMMIT и ROLLBACK) на уровне DEBUG.

Команды управления

  • <<<Команда makemessages теперь поддерживает локали с приватными подтегами, такими как nl_NL-x-informal.
  • Новая опция makemigrations --update объединяет изменения модели в последнюю миграцию и оптимизирует результирующие операции.

Миграции

  • Миграции теперь поддерживают сериализацию объектов enum.Flag.

Модели

  • Теперь QuerySet широко поддерживает фильтрацию по Функции окна, за исключением дизъюнктивного поиска фильтра по оконным функциям при выполнении агрегации.
  • prefetch_related() теперь поддерживает объекты Prefetch с нарезанными кверисетами.
  • Registering lookups на экземплярах Field теперь поддерживается.
  • Новый аргумент robust для on_commit() позволяет выполнять действия, которые могут завершиться неудачей после успешной фиксации транзакции базы данных.
  • Новое выражение KT() представляет собой текстовое значение ключа, индекса или преобразования пути JSONField.
  • Now теперь поддерживает микросекундную точность в MySQL и миллисекундную в SQLite.
  • Выражения F(), выводящие BooleanField, теперь можно отрицать с помощью ~F() (оператор инверсии).
  • Model теперь предоставляет асинхронные версии некоторых методов, использующих базу данных, с использованием префикса a: adelete(), arefresh_from_db() и asave().
  • Менеджеры Related теперь предоставляют асинхронные версии методов, изменяющих набор связанных объектов, с использованием префикса a: aadd(), aclear(), aremove() и aset().
  • <<<Установка CharField.max_length больше не требуется в PostgreSQL, который поддерживает неограниченное количество столбцов VARCHAR.

Запросы и ответы

  • StreamingHttpResponse теперь поддерживает асинхронные итераторы при обслуживании Django через ASGI.

Тесты

  • Опция test --debug-sql теперь форматирует SQL-запросы с помощью sqlparse.

  • Классы RequestFactory, AsyncRequestFactory, Client и AsyncClient теперь поддерживают параметр headers, который принимает словарь имен и значений заголовков. Это позволяет использовать более естественный синтаксис для объявления заголовков.

    # Before:
    self.client.get("/home/", HTTP_ACCEPT_LANGUAGE="fr")
    await self.async_client.get("/home/", ACCEPT_LANGUAGE="fr")
    
    # After:
    self.client.get("/home/", headers={"accept-language": "fr"})
    await self.async_client.get("/home/", headers={"accept-language": "fr"})
    

Утилиты

  • Новый параметр encoder для функции django.utils.html.json_script() позволяет настраивать класс JSON-кодировщика.
  • Частная внутренняя вендорная копия urllib.parse.urlsplit() теперь зачеркивает '\r', '\n' и '\t' (см. CVE-2022-0391 и bpo-43882). Это сделано для защиты проектов, которые могут некорректно использовать внутреннюю функцию url_has_allowed_host_and_scheme(), вместо того, чтобы использовать одну из документированных функций для работы с URL-перенаправлениями. Функции Django не были затронуты.
  • Новая функция django.utils.http.content_disposition_header() возвращает значение HTTP-заголовка Content-Disposition, указанное в RFC 6266.

Валидаторы

  • Список распространенных паролей, используемых программой CommonPasswordValidator, обновляется до последней версии.

Обратные несовместимые изменения в 4.2

API бэкенда базы данных

В этом разделе описаны изменения, которые могут потребоваться в бэкендах баз данных сторонних производителей.

  • DatabaseFeatures.allows_group_by_pk удален, поскольку он оставался только для того, чтобы соответствовать расширению MySQL, которое в MySQL 5.7.15 было вытеснено корректным определением функциональных зависимостей. Отметим, что DatabaseFeatures.allows_group_by_selected_pks по-прежнему поддерживается и должен быть включен, если ваш бэкэнд поддерживает обнаружение функциональных зависимостей в предложениях GROUP BY, как это предусмотрено стандартом SQL:1999.
  • inspectdb теперь использует display_size из DatabaseIntrospection.get_table_description(), а не internal_size для CharField.

Прекращена поддержка MariaDB 10.3

Поддержка MariaDB 10.3 заканчивается в мае 2023 года. Django 4.2 поддерживает MariaDB 10.4 и выше.

Отказано в поддержке MySQL 5.7

Поддержка MySQL 5.7 заканчивается в октябре 2023 года. Django 4.2 поддерживает MySQL 8 и выше.

Отказ от поддержки PostgreSQL 11

Текущая поддержка PostgreSQL 11 заканчивается в ноябре 2023 года. Django 4.2 поддерживает PostgreSQL 12 и выше.

Теперь может потребоваться установка update_fields в Model.save()

Чтобы избежать обновления ненужных столбцов, QuerySet.update_or_create() теперь передает update_fields в вызовы Model.save(). Как следствие, любые поля, модифицированные в пользовательских методах save(), должны быть добавлены в аргумент ключевого слова update_fields перед вызовом super(). Более подробная информация приведена в разделе Переопределение методов модели.

Разное

  • Недокументированная функция django.http.multipartparser.parse_header() удалена. Вместо нее используйте django.utils.http.parse_header_parameters().
  • Результат {% blocktranslate asvar %} теперь помечен как безопасный для (HTML) вывода.
  • HTML-атрибут autofocus в окне поиска администратора удален, так как он может сбивать с толку пользователей, читающих с экрана.
  • Опция makemigrations --check больше не создает отсутствующие файлы миграции.
  • Аргумент alias для Expression.get_group_by_cols() удален.
  • Минимальная поддерживаемая версия sqlparse увеличена с 0.2.2 до 0.3.1.
  • Недокументированный параметр negated выражения Exists удален.
  • Аргумент is_summary недокументированного метода Query.add_annotation() удален.
  • Минимальная поддерживаемая версия SQLite увеличена с 3.9.0 до 3.21.0.
  • Минимальная поддерживаемая версия asgiref увеличена с 3.5.2 до 3.6.0.
  • UserCreationForm теперь отвергает имена пользователей, отличающиеся только регистром. Если вам нужно прежнее поведение, используйте вместо него BaseUserCreationForm.
  • Минимальная поддерживаемая версия mysqlclient увеличена с 1.4.0 до 1.4.3.
  • Минимальная поддерживаемая версия argon2-cffi увеличена с 19.1.0 до 19.2.0.
  • Минимальная поддерживаемая версия Pillow увеличена с 6.2.0 до 6.2.1.
  • Минимальная поддерживаемая версия jinja2 увеличена с 2.9.2 до 2.11.0.
  • Минимальная поддерживаемая версия redis-py увеличена с 3.0.0 до 3.4.0.
  • Для объектов WSGIRequest, инстанцируемых вручную, должен быть предоставлен файлоподобный объект wsgi.input. Ранее Django был более мягким по сравнению с ожидаемым поведением, указанным в спецификации WSGI.
  • Устранена поддержка PROJ < 5.
  • EmailBackend теперь проверяет наличие hostname и certificates. Если вам нужно прежнее поведение, которое является менее ограничительным и не рекомендуется, подкласс EmailBackend и переопределите свойство ssl_context.

Функции, устаревшие в 4.2

<<<Опция index_together устарела в пользу indexes.

Опция Meta.index_together устарела в пользу опции indexes.

Миграция существующих index_together должна обрабатываться как миграция. Например:

class Author(models.Model):
    rank = models.IntegerField()
    name = models.CharField(max_length=30)

    class Meta:
        index_together = [["rank", "name"]]

Должно стать:

class Author(models.Model):
    rank = models.IntegerField()
    name = models.CharField(max_length=30)

    class Meta:
        indexes = [models.Index(fields=["rank", "name"])]

Выполнение команды makemigrations приведет к генерации миграции, содержащей операцию RenameIndex, которая переименует существующий индекс. Далее следует рассмотреть возможность сминания миграций, чтобы удалить index_together из исторических миграций.

Операция миграции AlterIndexTogether теперь официально поддерживается только для файлов миграции, созданных до версии Django 4.2. По соображениям обратной совместимости она по-прежнему является частью общедоступного API, и ее отказ от использования или удаление не планируется, но ее не следует использовать для новых миграций. Вместо него используйте операции AddIndex и RemoveIndex.

Передача закодированных строковых литералов JSON в JSONField устарела

JSONField и связанные с ним lookups и aggregates раньше позволяли передавать строковые литералы в кодировке JSON, что вызывало неоднозначность в вопросе о том, закодированы ли уже строковые литералы с точки зрения бэкенда базы данных.

В течение периода обесценивания строковые литералы будут пытаться декодироваться в JSON, а при успехе будет выдаваться предупреждение, указывающее на передачу вместо них некодированных форм.

Код, используемый для передачи строковых литералов в кодировке JSON:

Document.objects.bulk_create(
    Document(data=Value("null")),
    Document(data=Value("[]")),
    Document(data=Value('"foo-bar"')),
)
Document.objects.annotate(
    JSONBAgg("field", default=Value("[]")),
)

Должно стать:

Document.objects.bulk_create(
    Document(data=Value(None, JSONField())),
    Document(data=[]),
    Document(data="foo-bar"),
)
Document.objects.annotate(
    JSONBAgg("field", default=[]),
)

Начиная с версии Django 5.1+ строковые литералы будут неявно интерпретироваться как строковые литералы JSON.

Разное

  • Метод BaseUserManager.make_random_password() является устаревшим. См. recipes and best practices об использовании модуля Python secrets для генерации паролей.

  • Шаблонный фильтр length_is устарел в пользу length и оператора == внутри тега {% if %}. Например

    {% if value|length == 4 %}{% endif %}
    {% if value|length == 4 %}True{% else %}False{% endif %}
    

    вместо:

    {% if value|length_is:4 %}{% endif %}
    {{ value|length_is:4 }}
    
  • django.contrib.auth.hashers.SHA1PasswordHasher, django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher и django.contrib.auth.hashers.UnsaltedMD5PasswordHasher являются устаревшими.

  • django.contrib.postgres.fields.CICharField устарел в пользу CharField(db_collation="…") с нечувствительной к регистру недетерминированной сверткой.

  • django.contrib.postgres.fields.CIEmailField устарел в пользу EmailField(db_collation="…") с нечувствительной к регистру недетерминированной сверткой.

  • django.contrib.postgres.fields.CITextField устарел в пользу TextField(db_collation="…") с нечувствительной к регистру недетерминированной сверткой.

  • Миксин django.contrib.postgres.fields.CIText устарел.

  • Атрибуты map_height и map_width в BaseGeometryWidget устарели, вместо них используйте CSS для определения размеров виджетов карт.

  • SimpleTestCase.assertFormsetError() устарел в пользу assertFormSetError().

  • TransactionTestCase.assertQuerysetEqual() устарел в пользу assertQuerySetEqual().

  • Передача позиционных аргументов в Signer и TimestampSigner устарела в пользу аргументов, состоящих только из ключевых слов.

  • Настройка DEFAULT_FILE_STORAGE устарела в пользу STORAGES["default"].

  • Настройка STATICFILES_STORAGE устарела в пользу STORAGES["staticfiles"].

  • Функция django.core.files.storage.get_storage_class() является устаревшей.

Вернуться на верх