============================================
Django 6.1 release notes - UNDER DEVELOPMENT
============================================

*Expected August 2026*

Welcome to Django 6.1!

These release notes cover the :ref:`new features <whats-new-6.1>`, as well as
some :ref:`backwards incompatible changes <backwards-incompatible-6.1>` you'll
want to be aware of when upgrading from Django 6.0 or earlier. We've
:ref:`begun the deprecation process for some features
<deprecated-features-6.1>`.

See the :doc:`/howto/upgrade-version` guide if you're updating an existing
project.

Python compatibility
====================

Django 6.1 supports Python 3.12, 3.13, and 3.14. We **highly recommend**, and
only officially support, the latest release of each series.

.. _whats-new-6.1:

What's new in Django 6.1
========================

Model field fetch modes
-----------------------

The on-demand fetching behavior of model fields is now configurable with
:doc:`fetch modes </topics/db/fetch-modes>`. These modes allow you to control
how Django fetches data from the database when an unfetched field is accessed.

Django provides three fetch modes:

1. ``FETCH_ONE``, the default, fetches the missing field for the current
   instance only. This mode represents Django's existing behavior.

2. ``FETCH_PEERS`` fetches a missing field for all instances that came from
   the same :class:`~django.db.models.query.QuerySet`.

   This mode works like an on-demand ``prefetch_related()``. It can reduce most
   cases of the "N+1 queries problem" to two queries without any work to
   maintain a list of fields to prefetch.

3. ``RAISE`` raises a :exc:`~django.core.exceptions.FieldFetchBlocked`
   exception.

   This mode can prevent unintentional queries in performance-critical
   sections of code.

Use the new method :meth:`.QuerySet.fetch_mode` to set the fetch mode for model
instances fetched by the ``QuerySet``:

.. code-block:: python

    from django.db import models

    books = Book.objects.fetch_mode(models.FETCH_PEERS)
    for book in books:
        print(book.author.name)

Despite the loop accessing the ``author`` foreign key on each instance, the
``FETCH_PEERS`` fetch mode will make the above example perform only two
queries:

1. Fetch all books.
2. Fetch associated authors.

See :doc:`fetch modes </topics/db/fetch-modes>` for more details.

Database-level delete options for ``ForeignKey.on_delete``
----------------------------------------------------------

:attr:`.ForeignKey.on_delete` now supports database-level delete options:

* :attr:`~django.db.models.DB_CASCADE`
* :attr:`~django.db.models.DB_SET_NULL`
* :attr:`~django.db.models.DB_SET_DEFAULT`

These options handle deletion logic entirely within the database, using the SQL
``ON DELETE`` clause. They are thus more efficient than the existing
Python-level options, as Django does not need to load objects before deleting
them. As a consequence, the :attr:`~django.db.models.DB_CASCADE` option does
not trigger the ``pre_delete`` or ``post_delete`` signals.

Minor features
--------------

:mod:`django.contrib.admin`
~~~~~~~~~~~~~~~~~~~~~~~~~~~

* The admin site login view now redirects authenticated users to the next URL,
  if available, instead of always redirecting to the admin index page.

* The admin's ``FilteredSelectMultiple`` widget now uses ``<optgroup>``\s to
  preserve :ref:`named groups <field-choices-named-groups>` (e.g.
  ``choices=[("Group", [("1", "Item")]), ...]``).

* In order to improve accessibility of the admin change forms:

  * Form fields are now shown below their respective labels instead of next to
    them.

  * Help text is now shown after the field label and before the field input.

  * Validation errors are now shown after the help text and before the field
    input.

  * Checkboxes are an exception to the above changes and continue to be
    displayed in their original layout.

* :attr:`~django.contrib.admin.ModelAdmin.list_display` now uses boolean icons
  for boolean fields on related models.

:mod:`django.contrib.admindocs`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* ...

:mod:`django.contrib.auth`
~~~~~~~~~~~~~~~~~~~~~~~~~~

* The default iteration count for the PBKDF2 password hasher is increased from
  1,200,000 to 1,500,000.

* :attr:`.Permission.name` and :attr:`.Permission.codename` values are now
  renamed when renaming models via a migration.

* The new :attr:`.Permission.user_perm_str` property returns the string
  suitable to use with :meth:`.User.has_perm`.

:mod:`django.contrib.contenttypes`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* ...

:mod:`django.contrib.gis`
~~~~~~~~~~~~~~~~~~~~~~~~~

* The :lookup:`isempty` lookup and
  :class:`IsEmpty() <django.contrib.gis.db.models.functions.IsEmpty>`
  database function are now supported on SpatiaLite.

* The new :lookup:`num_dimensions` lookup and :class:`NumDimensions()
  <django.contrib.gis.db.models.functions.NumDimensions>` database function
  allow filtering geometries by the number of dimensions on PostGIS and
  SpatiaLite.

:mod:`django.contrib.messages`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* ...

:mod:`django.contrib.postgres`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* :djadmin:`inspectdb` now introspects
  :class:`~django.contrib.postgres.fields.HStoreField` when ``psycopg`` 3.2+ is
  installed and ``django.contrib.postgres`` is in :setting:`INSTALLED_APPS`.

* :class:`~django.contrib.postgres.constraints.ExclusionConstraint` now
  supports the Hash index type.

:mod:`django.contrib.redirects`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* ...

:mod:`django.contrib.sessions`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* :class:`~django.contrib.sessions.backends.base.SessionBase` now supports
  boolean evaluation via
  :meth:`~django.contrib.sessions.backends.base.SessionBase.__bool__`.

:mod:`django.contrib.sitemaps`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* ...

:mod:`django.contrib.sites`
~~~~~~~~~~~~~~~~~~~~~~~~~~~

* ...

:mod:`django.contrib.staticfiles`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* ...

:mod:`django.contrib.syndication`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* ...

Asynchronous views
~~~~~~~~~~~~~~~~~~

* ...

Cache
~~~~~

* ...

CSP
~~~

* ...

CSRF
~~~~

* ...

Decorators
~~~~~~~~~~

* ...

Email
~~~~~

* ...

Error Reporting
~~~~~~~~~~~~~~~

* ...

File Storage
~~~~~~~~~~~~

* ...

File Uploads
~~~~~~~~~~~~

* ...

Forms
~~~~~

* ...

Generic Views
~~~~~~~~~~~~~

* ...

Internationalization
~~~~~~~~~~~~~~~~~~~~

* ...

Logging
~~~~~~~

* ...

Management Commands
~~~~~~~~~~~~~~~~~~~

* Management commands now set :class:`~argparse.ArgumentParser`\'s
  ``suggest_on_error`` argument to ``True`` by default on Python 3.14, enabling
  suggestions for incorrectly typed subcommand names and argument choices.

* The :djadmin:`loaddata` command now calls
  :data:`~django.db.models.signals.m2m_changed` signals with ``raw=True`` when
  loading fixtures.

Migrations
~~~~~~~~~~

* ...

Models
~~~~~~

* :meth:`.QuerySet.in_bulk` now supports chaining after
  :meth:`.QuerySet.values` and :meth:`.QuerySet.values_list`.

* The new :class:`~django.db.models.JSONNull` expression provides an explicit
  way to represent the JSON scalar ``null``. It can be used when saving a
  top-level :class:`~django.db.models.JSONField` value, or querying for
  top-level or nested JSON ``null`` values. See
  :ref:`storing-and-querying-for-none` for usage examples and some caveats.

* :attr:`DecimalField.max_digits <django.db.models.DecimalField.max_digits>`
  and :attr:`DecimalField.decimal_places
  <django.db.models.DecimalField.decimal_places>` are no longer required to be
  set on Oracle, PostgreSQL, and SQLite.

* :class:`~django.db.models.JSONField` now supports
  :ref:`negative array indexing <key-index-and-path-transforms>` on Oracle
  21c+.

* The new :class:`~django.db.models.functions.UUID4` and
  :class:`~django.db.models.functions.UUID7` database functions were added.

* :class:`~django.db.models.GeneratedField` now supports stored columns
  (:attr:`~django.db.models.GeneratedField.db_persist` set to ``True``) on
  Oracle 23ai/26ai (23.7+).

* The :data:`~django.db.models.signals.m2m_changed` signal now receives a
  ``raw`` argument.

* :class:`~django.db.models.StringAgg` now supports ``distinct=True`` on SQLite
  when using the default delimiter ``Value(",")`` only.

* The new :attr:`.QuerySet.totally_ordered` property returns ``True`` if the
  :class:`~django.db.models.query.QuerySet` is ordered and the ordering is
  deterministic.

Pagination
~~~~~~~~~~

* ...

Requests and Responses
~~~~~~~~~~~~~~~~~~~~~~

* :attr:`HttpRequest.multipart_parser_class <django.http.HttpRequest.multipart_parser_class>`
  can now be customized to use a different multipart parser class.

Security
~~~~~~~~

* ...

Serialization
~~~~~~~~~~~~~

* Subclasses of models defining the ``natural_key()`` method can now opt out of
  natural key serialization by overriding the method to return an empty tuple:
  ``()``. This ensures primary keys are serialized when using
  :option:`dumpdata --natural-primary`.

* The XML deserializer now raises
  :exc:`~django.core.exceptions.SuspiciousOperation` when it encounters
  unexpected nested tags.

Signals
~~~~~~~

* ...

Tasks
~~~~~

* The :func:`~django.tasks.task` decorator now accepts ``**kwargs``, which are
  forwarded to the backend's
  :attr:`~django.tasks.backends.base.BaseTaskBackend.task_class`.

Templates
~~~~~~~~~

* ...

Tests
~~~~~

* :meth:`~django.test.SimpleTestCase.assertContains` and
  :meth:`~django.test.SimpleTestCase.assertNotContains` can now be called
  multiple times on the same :class:`~django.http.StreamingHttpResponse`.
  Previously, they would consume the streaming response's content, causing
  subsequent calls to fail.

URLs
~~~~

* ...

Utilities
~~~~~~~~~

* :func:`~django.utils.dateparse.parse_duration` now supports ISO 8601
  time periods expressed in weeks (``PnW``).

Validators
~~~~~~~~~~

* ...

.. _backwards-incompatible-6.1:

Backwards incompatible changes in 6.1
=====================================

Database backend API
--------------------

This section describes changes that may be needed in third-party database
backends.

* The ``DatabaseOperations.adapt_durationfield_value()`` hook is added. If the
  database has native support for ``DurationField``, override this method to
  simply return the value.

* The ``DatabaseIntrospection.get_relations()`` should now return a dictionary
  with 3-tuples containing (``field_name_other_table``, ``other_table``,
  ``db_on_delete``) as values. ``db_on_delete`` is one of the database-level
  delete options e.g. :attr:`~django.db.models.DB_CASCADE`.

* Set the new ``DatabaseFeatures.supports_inspectdb`` attribute to ``False``
  if the management command isn't supported.

* The ``DatabaseFeatures.prohibits_dollar_signs_in_column_aliases`` feature
  flag is removed.

* The ``DatabaseOperations.binary_placeholder_sql()`` method now expects a
  query compiler as an extra positional argument and should return a
  two-elements tuple composed of an SQL format string and a tuple of associated
  parameters.

* The ``BaseSpatialOperations.get_geom_placeholder()`` method is renamed to
  ``get_geom_placeholder_sql`` and is expected to return a two-elements tuple
  composed of an SQL format string and a tuple of associated parameters.

:mod:`django.contrib.admin`
---------------------------

* The ``wide`` class is removed, as it was made obsolete by the new layout.

* The ``object-tools`` block is hoisted out of the ``content`` block in forms.

:mod:`django.contrib.gis`
-------------------------

* Support for PostGIS 3.1 is removed.

* Support for GEOS 3.8 and 3.9 is removed.

* Support for GDAL 3.1 and 3.2 is removed.

:mod:`django.contrib.postgres`
------------------------------

* Top-level elements set to ``None`` in an
  :class:`~django.contrib.postgres.fields.ArrayField` with a
  :class:`~django.db.models.JSONField` base field are now saved as SQL ``NULL``
  instead of the JSON ``null`` primitive. This matches the behavior of a
  standalone :class:`~django.db.models.JSONField` when storing ``None`` values.

Models
------

* SQL ``SELECT`` aliases originating from :meth:`.QuerySet.annotate`
  calls as well as table and ``JOIN`` aliases are now systematically quoted to
  prevent special character collisions. Because quoted aliases are
  case-sensitive, *raw* SQL references to aliases mixing case, such as when
  using :class:`.RawSQL`, might have to be adjusted to also make use of
  quoting.

System checks
-------------

* The :djadmin:`check` management command now supplies all ``databases`` if not
  specified. Callers should be prepared for databases to be accessed.

Dropped support for PostgreSQL 14
---------------------------------

Upstream support for PostgreSQL 14 ends in November 2026. Django 6.1 supports
PostgreSQL 15 and higher.

Dropped support for MySQL < 8.4
-------------------------------

Upstream support for MySQL 8.0 ends in April 2026, and MySQL 8.1-8.3 are
short-term innovation releases. Django 6.1 supports MySQL 8.4 and higher.

Dropped support for MariaDB < 10.11
-----------------------------------

Upstream support for MariaDB 10.6 ends in July 2026, and MariaDB 10.7-10.10 are
short-term maintenance releases. Django 6.1 supports MariaDB 10.11 and higher.

Miscellaneous
-------------

* Providing ``fail_silently=True``, ``auth_user``, or ``auth_password`` to mail
  sending functions (such as :func:`~django.core.mail.send_mail`) while also
  providing a ``connection`` now raises a ``TypeError``.

* :class:`~django.contrib.contenttypes.fields.GenericForeignKey` now uses a
  separate descriptor class: the private ``GenericForeignKeyDescriptor``.

* The minimum supported version of SQLite is increased from 3.31.0 to 3.37.0.

* The :lookup:`iexact=None <iexact>` lookup on
  :class:`~django.db.models.JSONField` key transforms now matches JSON
  ``null``, to match the behavior of :lookup:`exact=None <exact>` on key
  transforms. Previously, it was interpreted as an :lookup:`isnull` lookup.

* :meth:`~.QuerySet.first` and :meth:`~.QuerySet.last` no longer order by the
  primary key when a ``QuerySet``'s ordering has been forcibly cleared by
  calling :meth:`~.QuerySet.order_by` with no arguments.

.. _deprecated-features-6.1:

Features deprecated in 6.1
==========================

Miscellaneous
-------------

* Calling :meth:`.QuerySet.values_list` with ``flat=True`` and no field name
  is deprecated. Pass an explicit field name, like
  ``values_list("pk", flat=True)``.

* The use of ``None`` to represent a top-level JSON scalar ``null`` when
  querying :class:`~django.db.models.JSONField` is now deprecated in favor of
  the new :class:`~django.db.models.JSONNull` expression. At the end
  of the deprecation period, ``None`` values compile to SQL ``IS NULL`` when
  used as the top-level value. :lookup:`Key and index lookups <jsonfield.key>`
  are unaffected by this deprecation.

* The undocumented ``get_placeholder`` method of
  :class:`~django.db.models.Field` is deprecated in favor of the newly
  introduced ``get_placeholder_sql`` method, which has the same input signature
  but is expected to return a two-elements tuple composed of an SQL format
  string and a tuple of associated parameters. This method should now expect
  to be provided expressions meant to be compiled via the provided ``compiler``
  argument.

* The ``quote_name_unless_alias()`` method of ``SQLCompiler``, the type of
  object passed as the ``compiler`` argument to the ``as_sql()`` method of
  :ref:`expressions <writing-your-own-query-expressions>`, is deprecated in
  favor of the newly introduced ``quote_name()`` method.

Features removed in 6.1
=======================

These features have reached the end of their deprecation cycle and are removed
in Django 6.1.

See :ref:`deprecated-features-5.2` for details on these changes, including how
to remove usage of these features.

* The ``all`` parameter for the ``django.contrib.staticfiles.finders.find()``
  function is removed in favor of the ``find_all`` parameter.

* Fallbacks to ``request.user`` and ``request.auser()`` when ``user`` is
  ``None`` in ``django.contrib.auth.login()`` and
  ``django.contrib.auth.alogin()``, respectively, are removed.

* The ``ordering`` keyword parameter of the PostgreSQL specific aggregation
  functions ``django.contrib.postgres.aggregates.ArrayAgg``,
  ``django.contrib.postgres.aggregates.JSONBAgg``, and
  ``django.contrib.postgres.aggregates.StringAgg`` are removed in favor
  of the ``order_by`` parameter.

* Support for subclasses of ``RemoteUserMiddleware`` that override
  ``process_request()`` without overriding ``aprocess_request()`` is
  removed.
