============================================ Django 6.1 release notes - UNDER DEVELOPMENT ============================================ *Expected August 2026* Welcome to Django 6.1! These release notes cover the :ref:`new features `, as well as some :ref:`backwards incompatible changes ` 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 `. 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 `. 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 ` 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 ````\s to preserve :ref:`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() ` database function are now supported on SpatiaLite. * The new :lookup:`num_dimensions` lookup and :class:`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 ` and :attr:`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 ` 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 ` 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 ` lookup on :class:`~django.db.models.JSONField` key transforms now matches JSON ``null``, to match the behavior of :lookup:`exact=None ` 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. * The :class:`~django.core.files.File` class now always evaluates to ``True`` in boolean contexts, rather than relying on the ``name`` attribute. The built-in subclasses ``FieldFile``, ``UploadedFile``, ``TemporaryUploadedFile``, ``InMemoryUploadedFile``, and ``SimpleUploadedFile`` retain the previous behavior of evaluating based on the ``name`` attribute. .. _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 ` 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 `, 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.