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.
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.
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:
FETCH_ONE, the default, fetches the missing field for the current
instance only. This mode represents Django’s existing behavior.
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.
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:
Fetch all books.
Fetch associated authors.
See fetch modes for more details.
ForeignKey.on_delete¶ForeignKey.on_delete now supports database-level delete options:
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.
The new MAILERS setting supports configuring multiple email backends
with different options, similar to existing mechanisms for CACHES,
DATABASES, STORAGES, and TASKS:
MAILERS = {
"default": {
"BACKEND": "django.core.mail.backends.smtp.EmailBackend",
"OPTIONS": {"host": "smtp.example.com", "use_tls": True},
},
"marketing": {
"BACKEND": "example_ses.EmailBackend",
"OPTIONS": {"region": "us-east-1"},
},
}
You can select a mailer with the new using argument to email sending functions, or obtain an email backend instance with
mail.mailers[alias]. See
Sending email for more details.
MAILERS is not yet enabled by default in existing projects. It will
replace EMAIL_BACKEND and related EMAIL_* settings in Django
7.0. Until then, the older settings will continue to work but will issue
deprecation warnings: see the list of email deprecations below.
You can opt into the new feature at any time before Django 7.0; see
Migrating email to mailers. To ease the transition,
mail.mailers["default"] works with
either MAILERS or the deprecated EMAIL_BACKEND setting
defined. The deprecated get_connection() function will
also return an instance of the default mailer when MAILERS is
defined.
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")]), ...]).
When ModelAdmin.list_select_related is False (the default),
the change list now selects only the foreign key fields specified in
ModelAdmin.list_display, rather than all foreign key fields. This
should improve performance for models with many foreign key fields.
The delete_confirmation_max_display
option allows customizing how many objects are displayed on admin delete
confirmation pages before the remainder is truncated. The default is
None (no truncation).
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.
The new location keyword argument of the
action() decorator specifies which admin views
the action is available on. The action is available on the admin change list
page by default. It can also be available on the admin change form. See
Controlling where actions are available for details.
The new description_plural keyword argument of the
action() decorator specifies a human-readable
description for actions on the admin change list page. Defaults to the
description value. This is useful when the action is available on both
the admin change list and admin change form.
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.
OpenLayersWidget is now based on
OpenLayers 10.9.0 (previously 7.2.2).
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¶…
…
…
The new csp_nonce_attr template tag renders the CSP nonce attribute
on <script> and <link> elements, or renders a
Media object’s assets with the nonce applied, when the
csp() context processor is
configured. See Nonce usage for details.
A new security.W027 system check warns when
ContentSecurityPolicyMiddleware is enabled
with CSP.NONCE in a CSP policy but
django.template.context_processors.csp is not configured.
…
…
…
…
…
…
The new asset object Stylesheet is available for
adding custom HTML-attributes to stylesheet links in form media. See
paths as objects for more details.
The new constant django.db.models.fields.BLANK_CHOICE_LABEL defines a
more accessible and translatable default label for the blank choice in
forms, which is appended to most choices lists. The transitional setting
USE_BLANK_CHOICE_DASH allows you to revert back to the old
default label.
FilePathField now provides a
set_choices() method to scan the
directory at path and refresh the
field’s choices. This allows per-request refreshing when called in a form’s
__init__().
…
…
…
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.
…
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+.
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.
The new BitAnd, BitOr,
and BitXor aggregates return the bitwise AND,
OR, XOR, respectively. These aggregates were previously included only
in contrib.postgres.
django.db.models.BinaryField now validates Base64 input strictly.
Invalid Base64 strings now raise ValidationError instead of being
silently accepted.
…
HttpRequest.multipart_parser_class
can now be customized to use a different multipart parser class.
HttpResponseRedirect (and its subclasses), as well as
the redirect() shortcut, now accept a max_length
parameter to override the default maximum URL length limit.
…
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.
…
The task() decorator now accepts **kwargs, which are
forwarded to the backend’s
task_class.
Task and TaskResult instances
can now be pickled and unpickled.
…
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.
…
parse_duration() now supports ISO 8601
time periods expressed in weeks (PnW).
…
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.
Set the new DatabaseFeatures.supports_bit_aggregations attribute to
False if the database doesn’t support bitwise aggregations.
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.
The undocumented InclusionAdminNode.__init__() now takes the template tag
name as the first positional argument.
The undocumented ChangeList.has_related_field_in_list_display() method
has been replaced with ChangeList.get_select_related_fields().
django.contrib.auth¶Under ASGI, RemoteUserMiddleware no
longer prefixes HTTP_ when looking up custom values in request.META.
For example, to send -H "AuthUser": ..., the header attribute should
be HTTP_AUTHUSER. This restores the behavior prior to Django 5.2. (The
default value of REMOTE_USER is not affected.)
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.
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.
The check management command now supplies all databases if not
specified. Callers should be prepared for databases to be accessed.
Upstream support for PostgreSQL 14 ends in November 2026. Django 6.1 supports PostgreSQL 15 and higher.
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.
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.
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.
The undocumented EmailMessage.get_connection() method is no longer used.
Defining it in a subclass or trying to call it now causes an error.
EmailMessage.send() no longer sets the connection property on the
EmailMessage. (This behavior was never documented. The send() method
will still use a connection that is set on the message before sending.)
GenericForeignKey now uses a
separate descriptor class: the private GenericForeignKeyDescriptor.
The undocumented django.template.library.parse_bits() function no longer
accepts the takes_context argument.
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.
The 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.
The undocumented connection() method of log.AdminEmailHandler
has been removed and is no longer called. Subclasses overriding
AdminEmailHandler.send_mail() should avoid calling connection().
See Replacing get_connection() and connection arguments if specific connection
configuration is needed.
The internal implementation of BrokenLinkEmailsMiddleware has been
updated for mailers. If you have subclassed it to customize email sending
behavior (as suggested in How to manage error reporting), you may want to
review the updates in the base BrokenLinkEmailsMiddleware class.
django.http.multipartparser.MultiPartParser now uses strict Base64
validation when decoding encoded request data. Previously, invalid data could
be silently ignored or result in empty values. Invalid data now raises
MultiPartParserError.
django.core.cache.backends.db.DatabaseCache now uses strict Base64
validation when decoding cached values. Invalid Base64 data will raise an
exception instead of being silently ignored. Cache values generated by Django
are unaffected, as they are always valid Base64. However, existing cache
entries containing non-standard or corrupted Base64 data may no longer be
readable.
The EMAIL_BACKEND, EMAIL_FILE_PATH,
EMAIL_HOST, EMAIL_HOST_PASSWORD,
EMAIL_HOST_USER, EMAIL_PORT,
EMAIL_USE_TLS, EMAIL_USE_SSL,
EMAIL_SSL_CERTFILE, EMAIL_SSL_KEYFILE, and
EMAIL_TIMEOUT settings are deprecated. Replace them with an
MAILERS configuration dictionary as described in
Migrating email to mailers.
mail.get_connection() is deprecated. See
Replacing get_connection() and connection arguments for replacement options.
The connection argument to send_mail(), send_mass_mail(),
mail_admins(), mail_managers(), and EmailMessage is
deprecated. The EmailMessage.connection attribute is also deprecated.
Switch to the using argument with a MAILERS alias.
The fail_silently argument to send_mail(),
send_mass_mail(), mail_admins(), mail_managers(), and
EmailMessage.send() is deprecated. See
Replacing fail_silently for alternatives.
The auth_user and auth_password arguments to send_mail() and
send_mass_mail() are deprecated. Replace them with "username" and
"password" OPTIONS in MAILERS.
See Replacing auth_user and auth_password.
Directly constructing and using instances of the
smtp.EmailBackend class is deprecated. Use
mail.mailers to obtain email backend instances.
The BaseEmailBackend.__init__() constructor no longer silently ignores
unknown keyword arguments. Custom email backend subclasses should ensure they
have consumed all supported **kwargs before forwarding the remainder to
superclass init. The base class now issues a deprecation warning for unknown
arguments, and it will treat them as errors starting in Django 7.0. See
Migrating custom email backends.
Support for fail_silently in the BaseEmailBackend is deprecated.
A custom email backend that wants to support fail_silently should manage
its own local attribute, not pass it to the base backend constructor. See
Migrating custom email backends.
Calling select_related() with no
arguments to select all related fields, is deprecated. Specify the related
fields to fetch instead.
Setting ModelAdmin.list_select_related to True and returning
True from ModelAdmin.get_list_select_related() are deprecated.
Specify the related fields to fetch instead.
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 django.db.models.fields.BLANK_CHOICE_DASH constant is deprecated
in favor of the new constant django.db.models.fields.BLANK_CHOICE_LABEL.
The USE_BLANK_CHOICE_DASH transitional setting is deprecated.
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.
The email_backend argument of log.AdminEmailHandler is
deprecated in favor of the newly introduced using argument.
The BitAnd, BitOr, and BitXor classes in
django.contrib.postgres.aggregates are deprecated in favor of the
generally available BitAnd,
BitOr, and BitXor
classes.
Support for double-dot variable lookups, like {{ book..title }}, is
deprecated. This syntax maps to a lookup of the empty string, which is
normally a mistake.
The default value of the algorithm argument for
django.utils.crypto.salted_hmac() and
django.core.signing.base64_hmac() is deprecated and will change from
"sha1" to "sha256" in Django 7.0. Pass an explicit algorithm
to silence the deprecation warning.
Overriding ModelAdmin.get_actions() without the new action_location
parameter is deprecated.
Unpacking or indexing the dictionary values of the
ModelAdmin.get_actions() return value is deprecated. Use
Action attributes instead.
Overriding ModelAdmin.get_action_choices() without the new
action_location parameter is deprecated.
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.
May 13, 2026