============================================ Django 6.2 release notes - UNDER DEVELOPMENT ============================================ *Expected April 2027* Welcome to Django 6.2! These release notes cover the :ref:`new features `, as well as some :ref:`backwards incompatible changes ` you should be aware of when upgrading from Django 6.1 or earlier. We've :ref:`begun the deprecation process for some features `. See the :doc:`/howto/upgrade-version` guide if you're updating an existing project. Django 6.2 is designated as a :term:`long-term support release `. It will receive security updates for at least three years after its release. Support for the previous LTS, Django 5.2, will end in April 2028. Python compatibility ==================== Django 6.2 supports Python 3.12, 3.13, 3.14, and 3.15. We **highly recommend** and only officially support the latest release of each series. .. _whats-new-6.2: What's new in Django 6.2 ======================== Support for the HTTP ``QUERY`` method ------------------------------------- Django now recognizes the :rfc:`QUERY <10008>` method, which performs a safe, idempotent request with the query content in the request body rather than in the URL's query string. Use ``QUERY`` for read-only operations whose input is more naturally expressed in a request body, such as searches with complex, structured filters. ``QUERY`` is treated as a safe method throughout Django: it is exempt from CSRF protection, it participates in conditional request processing via :class:`~django.middleware.http.ConditionalGetMiddleware` and :func:`~django.views.decorators.http.condition`, and it is accepted by :func:`~django.views.decorators.http.require_safe`. :attr:`View.http_method_names ` now includes ``"query"``, so a class-based view handles ``QUERY`` requests by defining a ``query()`` method:: import json from django import forms from django.http import JsonResponse from django.views import View from books.models import Book class BookSearchView(View): def query(self, request): criteria = json.loads(request.body) formfield = forms.CharField() names = criteria["authors"][:100] authors = [formfield.to_python(name) for name in names] books = Book.objects.filter(author__name__in=authors) return JsonResponse({"books": list(books.values("title"))}) The query content is available as :attr:`HttpRequest.body `; you must parse it according to the request's ``Content-Type``. Django does not automatically parse it into :attr:`HttpRequest.POST `. :class:`~django.middleware.cache.UpdateCacheMiddleware`, :class:`~django.middleware.cache.FetchFromCacheMiddleware` and :func:`~django.views.decorators.cache.cache_page` do not cache ``QUERY`` responses. Conditional request processing with ``ETag`` or ``Last-Modified`` validators is supported. Multiline template tags ----------------------- The :class:`~django.template.backends.django.DjangoTemplates` backend now supports template tags spanning multiple lines. This can make tags with several arguments easier to read: .. code-block:: html+django {% include "blog/includes/article_card.html" with article=article show_author=True extra_classes="featured-article" only %} This change to template parsing may cause template errors or unexpected rendering in some existing templates. See :ref:`multiline-template-tags-compatibility` for details. Adopting calendar versioning ---------------------------- Following `DEP 20`_, the releases previously planned as Django 7.0 and 7.1 are now Django 2028 and 2029. Accordingly, the deprecation warning classes are renamed to match the calendar version in which the features they mark are removed: ``RemovedInDjango70Warning`` becomes ``RemovedInDjango2028Warning``, and ``RemovedInDjango71Warning`` becomes ``RemovedInDjango2029Warning``. .. _DEP 20: https://github.com/django/deps/blob/main/accepted/0020-annual-release-cycle.rst Minor features -------------- :mod:`django.contrib.admin` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ... :mod:`django.contrib.admindocs` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ... :mod:`django.contrib.auth` ~~~~~~~~~~~~~~~~~~~~~~~~~~ * The default iteration count for the PBKDF2 password hasher is increased from 1,500,000 to 1,800,000. :mod:`django.contrib.contenttypes` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ... :mod:`django.contrib.gis` ~~~~~~~~~~~~~~~~~~~~~~~~~ * :class:`~django.contrib.gis.gdal.DataSource` now preserves millisecond precision when reading time and datetime fields. :mod:`django.contrib.messages` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ... :mod:`django.contrib.postgres` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ... :mod:`django.contrib.redirects` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ... :mod:`django.contrib.sessions` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ... :mod:`django.contrib.sitemaps` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ... :mod:`django.contrib.sites` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ... :mod:`django.contrib.staticfiles` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ... :mod:`django.contrib.syndication` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ... Asynchronous views ~~~~~~~~~~~~~~~~~~ * ... Cache ~~~~~ * Subclasses of ``BaseDatabaseCache`` now support :ref:`culling ` on a percentage of writes as an optimization. The default is 10%, and may be configured using the ``CULL_PROBABILITY`` option. CSP ~~~ * ... CSRF ~~~~ * ... Database backends ~~~~~~~~~~~~~~~~~ * ... Decorators ~~~~~~~~~~ * ... Email ~~~~~ * The :setting:`ADMINS`, :setting:`DEFAULT_FROM_EMAIL`, :setting:`MANAGERS`, and :setting:`SERVER_EMAIL` settings now support :class:`~email.headerregistry.Address` objects. Error Reporting ~~~~~~~~~~~~~~~ * ... File Storage ~~~~~~~~~~~~ * ... File Uploads ~~~~~~~~~~~~ * ... Forms ~~~~~ * ... Generic Views ~~~~~~~~~~~~~ * ... Internationalization ~~~~~~~~~~~~~~~~~~~~ * ... Logging ~~~~~~~ * The warning logged when a database error occurs while validating model constraints now escapes the error message and trims it to 4096 characters. Management Commands ~~~~~~~~~~~~~~~~~~~ * The new :djadmin:`listurls` command lists the URLs from the project's root URLconf, including the view class or function (and name, if present). * The :djadmin:`makemigrations` command now tracks all changes to unmanaged models, including field additions, removals, alterations, constraints, and model renames. After upgrading, you will see new migrations detected for unmanaged models that have changed since their creation. * Whether to suppress an :exc:`ImportError` escaping from a settings module is configurable by the new :attr:`.BaseCommand.requires_settings` attribute (default ``True``). In previous versions, such errors were always suppressed. Migrations ~~~~~~~~~~ * ... Models ~~~~~~ * ... Requests and Responses ~~~~~~~~~~~~~~~~~~~~~~ * ... Security ~~~~~~~~ * ... Serialization ~~~~~~~~~~~~~ * ... Signals ~~~~~~~ * ... Tasks ~~~~~ * ... Templates ~~~~~~~~~ * The :ttag:`now` template tag now supports passing a template variable as the format string. Tests ~~~~~ * :meth:`~django.test.Client.force_login` now skips members of :setting:`AUTHENTICATION_BACKENDS` not implementing ``(a)get_user()``, e.g. permission-only backends. * The new :meth:`.Client.query` method makes an HTTP ``QUERY`` request. It is also available on ``AsyncClient``, ``RequestFactory``, and ``AsyncRequestFactory``. URLs ~~~~ * ... Utilities ~~~~~~~~~ * :func:`.utils.module_loading.import_string` now supports modules. Previously, top-level modules did not work, and submodules only worked if already imported. * The new :func:`django.utils.asyncio.maybe_aclosing` returns a context manager that calls ``aclose()`` on a caller-provided iterator only if it defines one. * ... Validators ~~~~~~~~~~ * :class:`~django.core.validators.EmailValidator` now includes the invalid ``username`` or ``domain`` in the ``params`` dict of the :exc:`~django.core.exceptions.ValidationError` it raises. The new :meth:`~django.core.validators.EmailValidator.parse_address`, :meth:`~django.core.validators.EmailValidator.validate`, :meth:`~django.core.validators.EmailValidator.validate_domain`, and :meth:`~django.core.validators.EmailValidator.validate_username` methods can be overridden to customize how the address is parsed and validated. .. _backwards-incompatible-6.2: Backwards incompatible changes in 6.2 ===================================== .. _multiline-template-tags-compatibility: Multiline template tag parsing ------------------------------ Previously, ``{%`` and ``%}`` sequences on separate lines were treated as text. These may now be interpreted as a template tag. This can change the rendered output or raise a :exc:`~django.template.TemplateSyntaxError`. Use the :ttag:`templatetag` tag with ``openblock`` and ``closeblock`` to output these sequences as text. For example, previously the following was rendered as text: .. code-block:: text This is the opening delimiter {%. This is the closing delimiter %}. To preserve this output, use: .. code-block:: html+django This is the opening delimiter {% templatetag openblock %}. This is the closing delimiter {% templatetag closeblock %}. Whitespace within the names of named :ttag:`verbatim` tags is now treated as equivalent when matching closing tags. Previously, ``{% verbatim a b %}`` required ``{% endverbatim a b %}`` to close it. It now also matches ``{% endverbatim a b %}``, which may cause a verbatim block to end earlier than intended. You can temporarily restore the previous parsing behavior by setting ``"allow_multiline_tags": False`` in the :class:`~django.template.backends.django.DjangoTemplates` backend's :setting:`OPTIONS `. Remove the option once your templates are updated. This transitional option is deprecated and will be removed in Django 2029. Database backend API -------------------- This section describes changes that may be needed in third-party database backends. * ... :mod:`django.contrib.admin` --------------------------- * The admin ``view_on_site`` URL now consistently returns an HTTP 403 response when a staff user lacks view or change permission for the target model. * The admin history view now checks permissions before object existence, consistently returning an HTTP 403 response for staff users without the view or change permission regardless of whether the object exists. * The undocumented ``django.contrib.admin.tests`` module was removed. :mod:`django.contrib.gis` ------------------------- * The seconds value returned by :meth:`~django.contrib.gis.gdal.Field.as_datetime` is now a ``c_float`` rather than a ``c_int``. * Support for PostGIS 3.2 is removed. * Support for GDAL 3.3 and 3.4 is removed. * Support for GEOS 3.10 is removed. CSP --- * A CSP directive whose only source is :attr:`~django.utils.csp.CSP.NONCE` is now emitted as ``'none'`` when the nonce is unused, instead of being dropped from the response header. Models ------ * Unsaved instances with a composite primary key or a ``db_default`` primary key no longer compare equal to other instances. * :class:`django.db.models.GenericIPAddressField` now validates IPv6 input strictly when saving and querying. Invalid IPv6 addresses now raise ``ValidationError`` instead of being silently accepted, and surrounding whitespace is stripped. Requests and responses ---------------------- * Most Django-provided middleware now set ``async_capable = False`` to avoid repetitive context switching in ``MiddlewareMixin.__acall__()`` under ASGI. For atypical use cases, e.g. high in-process concurrency over non-ORM I/O, where no thread is otherwise held, the repetitive context switching may be preferable to pinning a thread per request (see :ref:`async_performance`). Such deployments can restore the prior behavior by setting ``async_capable = True``; see :ref:`MiddlewareMixin `. :class:`~django.contrib.auth.middleware.RemoteUserMiddleware` is unaffected, because it does not use ``MiddlewareMixin``. Neither is :class:`~django.contrib.auth.middleware.LoginRequiredMiddleware` nor ``django.contrib.admindocs.middleware.XViewMiddleware`` affected, as they did not implement ``process_request()`` or ``process_response()``. * Streaming responses now include configured CSP nonces in their headers, even if those nonces are unused in the response body. Previously allowed inline scripts or styles without a matching nonce may be blocked or trigger violation reports. See :ref:`csp-nonce-generation` for details. Tests ----- * The undocumented ``django.test.selenium`` module was removed. * The undocumented ``django.test.ignore_warnings()`` now matches its ``message`` argument as a literal, case-sensitive substring of the warning message. Use the new ``message_re`` argument for the previous behavior. Dropped support for PostgreSQL 15 --------------------------------- Upstream support for PostgreSQL 15 ends in November 2027. Django 6.2 supports PostgreSQL 16 and higher. Miscellaneous ------------- * Views decorated with :func:`~django.views.decorators.http.require_safe` now also accept the ``QUERY`` method, which :rfc:`RFC 10008 <10008#section-2>` defines as safe and idempotent. Use ``require_http_methods(["GET", "HEAD"])`` to keep the previous behavior. * :class:`~django.middleware.http.ConditionalGetMiddleware` now computes ``ETag`` headers for, and returns HTTP 304 responses to, conditional ``QUERY`` requests. * With :setting:`DEBUG` and :setting:`APPEND_SLASH` enabled, :class:`~django.middleware.common.CommonMiddleware` now raises a ``RuntimeError`` for ``QUERY`` requests to a URL without a trailing slash, as it already did for ``DELETE``, ``POST``, ``PUT``, and ``PATCH``. * To facilitate the deprecation of the ``safe`` parameter of :class:`~django.http.JsonResponse`, it now defaults to ``False``, because the pollution vulnerability in the ``Array`` prototype was fixed in `ES5 `_. * :class:`~django.core.serializers.json.DjangoJSONEncoder` now omits the millisecond component of serialized ``datetime.datetime`` and ``datetime.time`` objects if they have zero milliseconds. For example, ``datetime.datetime(2000, 1, 1, 0, 0, 0, 1)`` now serializes to ``"2000-01-01T00:00:00"`` rather than ``"2000-01-01T00:00:00.000"``. * :func:`.utils.module_loading.import_string` now deterministically favors submodules in ambiguous cases where depending on prior import state, a same-named attribute of the parent module might have been returned instead. * Directories created in the process of uploading files when :setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS` (or :attr:`~django.core.files.storage.FileSystemStorage.directory_permissions_mode`) is set, and by the file-based cache backend, now have the requested permissions mode combined with the process's umask value, matching the semantics of Python's :func:`os.mkdir`. For example, a typical ``0o022`` umask leaves ``0o755`` unchanged but turns ``0o775`` into ``0o755``. Also, the set-group-ID bit is no longer guaranteed to be set, even if specified in the mode. To keep it on new subdirectories, set it on the parent directory. :setting:`FILE_UPLOAD_PERMISSIONS` is unaffected. * The minimum supported version of ``asgiref`` is increased from 3.9.1 to 3.13. * The minimum supported version of ``oracledb`` is increased from 2.3.0 to 4.0.2. * In the asynchronous request path, error responses (such as those rendered by ``handler404`` and ``handler500``) are now rendered on the request's thread-sensitive thread, rather than on a shared thread pool, so that database connections used during error handling are managed by ``close_old_connections()``. * Django's built-in cached properties now use Python's :func:`functools.cached_property`. Subclasses that override one with a ``@property`` and call ``super()`` now get the cached value instead of recomputing it, and accessing one on an instance without a ``__dict__`` raises ``TypeError`` instead of ``AttributeError``. .. _deprecated-features-6.2: Features deprecated in 6.2 ========================== Validators ---------- * The undocumented ``EmailValidator.validate_domain_part()`` method is deprecated in favor of the new :meth:`~django.core.validators.EmailValidator.validate_domain` method. Miscellaneous ------------- * Indexing ``django.VERSION`` from its third component on, reading it in a way which depends on its length, such as unpacking it or calling ``len()`` on it, and comparing it against three or more components are all deprecated. Under the calendar versioning adopted in `DEP 20`_, versions are ``YYYY[.N]`` and have no minor component, so from Django 2028 ``django.VERSION`` has four components, ``(year, patch, status, iteration)``. Use the ``.feature``, ``.patch``, ``.status``, and ``.iteration`` attributes, which give the same answers under both versioning schemes:: django.VERSION.feature # (6, 2) for 6.2.1, and (2028,) for 2028.1 django.VERSION.patch # 1 for both 6.2.1 and 2028.1 Comparisons against one or two components, such as ``django.VERSION >= (6, 2)``, are unaffected. * :class:`django.utils.functional.cached_property` is deprecated in favor of :func:`functools.cached_property`. * The :class:`~django.middleware.MiddlewareMixin` class moved from ``django.utils.deprecation`` to ``django.middleware``. The old import path is deprecated. * The ``safe`` parameter is deprecated from :class:`~django.http.JsonResponse`. Omitting the argument is equivalent to the prior ``safe=False`` usage. * Calling :meth:`.QuerySet.aiterator` after ``prefetch_related()`` without providing a ``chunk_size`` is deprecated. It currently falls back to a ``chunk_size`` of 2000, but a ``ValueError`` will be raised in Django 2029. * Omitting the ``tzinfo`` argument of :class:`~django.db.models.functions.Extract` and :class:`~django.db.models.functions.Trunc` database functions when used in migrations (for example as a :attr:`db_default `) and :setting:`USE_TZ` is ``True`` is deprecated. The timezone information is not captured during migration serialization, which can lead to inconsistent behavior if :setting:`TIME_ZONE` changes. Pass the ``tzinfo`` argument explicitly to suppress this warning. In Django 2029, the current timezone will be captured automatically when ``tzinfo`` is omitted.