# Django 6.0.3 documentation

# Django 6.1 release notes - UNDER DEVELOPMENT

Expected August 2026

Welcome to Django 6.1!

These release notes cover the new features, as well as some backwards incompatible changes you’ll want to be aware of when upgrading from Django 6.0 or earlier. We’ve begun the deprecation process for some features.

See the How to upgrade Django to a newer 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.

## What’s new in Django 6.1

### Model field fetch modes

The on-demand fetching behavior of model fields is now configurable with 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 `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 `FieldFetchBlocked` exception.

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

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

    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 fetch modes for more details.

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

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

- `DB_CASCADE`

- `DB_SET_NULL`

- `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 `DB_CASCADE` option does not trigger the `pre_delete` or `post_delete` signals.

### Minor features

#### `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 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.

- `list_display` now uses boolean icons for boolean fields on related models.

#### `django.contrib.admindocs`

- …

#### `django.contrib.auth`

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

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

- The new `Permission.user_perm_str` property returns the string suitable to use with `User.has_perm()`.

#### `django.contrib.contenttypes`

- …

#### `django.contrib.gis`

- The `isempty` lookup and `IsEmpty()` database function are now supported on SpatiaLite.

- The new `num_dimensions` lookup and `NumDimensions()` database function allow filtering geometries by the number of dimensions on PostGIS and SpatiaLite.

#### `django.contrib.messages`

- …

#### `django.contrib.postgres`

- `inspectdb` now introspects `HStoreField` when `psycopg` 3.2+ is installed and `django.contrib.postgres` is in `INSTALLED_APPS`.

- `ExclusionConstraint` now supports the Hash index type.

#### `django.contrib.redirects`

- …

#### `django.contrib.sessions`

- `SessionBase` now supports boolean evaluation via `__bool__()`.

#### `django.contrib.sitemaps`

- …

#### `django.contrib.sites`

- …

#### `django.contrib.staticfiles`

- …

#### `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 `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 `loaddata` command now calls `m2m_changed` signals with `raw=True` when loading fixtures.

#### Migrations

- …

#### Models

- `QuerySet.in_bulk()` now supports chaining after `QuerySet.values()` and `QuerySet.values_list()`.

- The new `JSONNull` expression provides an explicit way to represent the JSON scalar `null`. It can be used when saving a top-level `JSONField` value, or querying for top-level or nested JSON `null` values. See Storing and querying for None for usage examples and some caveats.

- `DecimalField.max_digits` and `DecimalField.decimal_places` are no longer required to be set on Oracle, PostgreSQL, and SQLite.

- `JSONField` now supports negative array indexing on Oracle 21c+.

- The new `UUID4` and `UUID7` database functions were added.

- `GeneratedField` now supports stored columns (`db_persist` set to `True`) on Oracle 23ai/26ai (23.7+).

- The `m2m_changed` signal now receives a `raw` argument.

- `StringAgg` now supports `distinct=True` on SQLite when using the default delimiter `Value(",")` only.

- The new `QuerySet.totally_ordered` property returns `True` if the `QuerySet` is ordered and the ordering is deterministic.

#### Pagination

- …

#### Requests and Responses

- `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 `dumpdata --natural-primary`.

- The XML deserializer now raises `SuspiciousOperation` when it encounters unexpected nested tags.

#### Signals

- …

#### Tasks

- The `task()` decorator now accepts `**kwargs`, which are forwarded to the backend’s `task_class`.

#### Templates

- …

#### Tests

- `assertContains()` and `assertNotContains()` can now be called multiple times on the same `StreamingHttpResponse`. Previously, they would consume the streaming response’s content, causing subsequent calls to fail.

#### URLs

- …

#### Utilities

- `parse_duration()` now supports ISO 8601 time periods expressed in weeks (`PnW`).

#### Validators

- …

## 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. `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.

### `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.

### `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.

### `django.contrib.postgres`

- Top-level elements set to `None` in an `ArrayField` with a `JSONField` base field are now saved as SQL `NULL` instead of the JSON `null` primitive. This matches the behavior of a standalone `JSONField` when storing `None` values.

### Models

- SQL `SELECT` aliases originating from `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 `RawSQL`, might have to be adjusted to also make use of quoting.

### System checks

- The `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 `send_mail()`) while also providing a `connection` now raises a `TypeError`.

- `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 `iexact=None` lookup on `JSONField` key transforms now matches JSON `null`, to match the behavior of `exact=None` on key transforms. Previously, it was interpreted as an `isnull` lookup.

- `first()` and `last()` no longer order by the primary key when a `QuerySet`’s ordering has been forcibly cleared by calling `order_by()` with no arguments.

## Features deprecated in 6.1

### Miscellaneous

- Calling `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 `JSONField` is now deprecated in favor of the new `JSONNull` expression. At the end of the deprecation period, `None` values compile to SQL `IS NULL` when used as the top-level value. `Key and index lookups` are unaffected by this deprecation.

- The undocumented `get_placeholder` method of `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 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 Features deprecated in 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.