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:
FETCH_ONE, the default, fetches the missing field for the current instance only. This mode represents Django’s existing behavior.FETCH_PEERSfetches a missing field for all instances that came from the sameQuerySet.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.RAISEraises aFieldFetchBlockedexception.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.
Database-level delete options for 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.
Mailers¶
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.third.party.EmailBackend",
"OPTIONS": {"region": "africa-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.
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
FilteredSelectMultiplewidget now uses<optgroup>s to preserve named groups (e.g.choices=[("Group", [("1", "Item")]), ...]).When
ModelAdmin.list_select_relatedisFalse(the default), the change list now selects only the foreign key fields specified inModelAdmin.list_display, rather than all foreign key fields. This should improve performance for models with many foreign key fields.The
delete_confirmation_max_displayoption allows customizing how many objects are displayed on admin delete confirmation pages before the remainder is truncated. The default isNone(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_displaynow uses boolean icons for boolean fields on related models.The new
locationkeyword argument of theaction()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_pluralkeyword argument of theaction()decorator specifies a human-readable description for actions on the admin change list page. Defaults to thedescriptionvalue. This is useful when the action is available on both the admin change list and admin change form.
django.contrib.auth¶
The default iteration count for the PBKDF2 password hasher is increased from 1,200,000 to 1,500,000.
Permission.nameandPermission.codenamevalues are now renamed when renaming models via a migration.The new
Permission.user_perm_strproperty returns the string suitable to use withUser.has_perm().
django.contrib.gis¶
The
isemptylookup andIsEmpty()database function are now supported on SpatiaLite.The new
num_dimensionslookup andNumDimensions()database function allow filtering geometries by the number of dimensions on PostGIS and SpatiaLite.OpenLayersWidgetis now based on OpenLayers 10.9.0 (previously 7.2.2).
django.contrib.postgres¶
inspectdbnow introspectsHStoreFieldwhenpsycopg3.2+ is installed anddjango.contrib.postgresis inINSTALLED_APPS.ExclusionConstraintnow supports the Hash index type.
django.contrib.sessions¶
SessionBasenow supports boolean evaluation via__bool__().
CSP¶
The new
csp_nonce_attrtemplate tag renders the CSP nonce attribute on<script>and<link>elements, or renders aMediaobject’s assets with the nonce applied, when thecsp()context processor is configured. See Nonce usage for details.A new
security.W027system check warns whenContentSecurityPolicyMiddlewareis enabled withCSP.NONCEin a CSP policy butdjango.template.context_processors.cspis not configured.CSP nonce attributes are now added on
<script>,<style>, and<link>elements in admin templates and all built-in templates when thecsp()context processor is configured. See Nonce config for setup instructions.
Forms¶
The new asset object
Stylesheetis 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_LABELdefines a more accessible and translatable default label for the blank choice in forms, which is appended to mostchoiceslists. The transitional settingUSE_BLANK_CHOICE_DASHallows you to revert back to the old default label.FilePathFieldnow provides aset_choices()method to scan the directory atpathand refresh the field’s choices. This allows per-request refreshing when called in a form’s__init__().
Generic Views¶
The new
RedirectView.preserve_requestattribute allows preserving the HTTP method and body during redirects, using 307/308 status codes instead of 302/301.
Management Commands¶
Management commands now set
ArgumentParser'ssuggest_on_errorargument toTrueby default on Python 3.14, enabling suggestions for incorrectly typed subcommand names and argument choices.The
loaddatacommand now callsm2m_changedsignals withraw=Truewhen loading fixtures.
Models¶
QuerySet.in_bulk()now supports chaining afterQuerySet.values()andQuerySet.values_list().The new
JSONNullexpression provides an explicit way to represent the JSON scalarnull. It can be used when saving a top-levelJSONFieldvalue, or querying for top-level or nested JSONnullvalues. See Storing and querying for None for usage examples and some caveats.DecimalField.max_digitsandDecimalField.decimal_placesare no longer required to be set on Oracle, PostgreSQL, and SQLite.JSONFieldnow supports negative array indexing on Oracle 21c+.GeneratedFieldnow supports stored columns (db_persistset toTrue) on Oracle 23ai/26ai (23.7+).The
m2m_changedsignal now receives arawargument.StringAggnow supportsdistinct=Trueon SQLite when using the default delimiterValue(",")only.The new
QuerySet.totally_orderedproperty returnsTrueif theQuerySetis ordered and the ordering is deterministic.The new
BitAnd,BitOr, andBitXoraggregates return the bitwiseAND,OR,XOR, respectively. These aggregates were previously included only incontrib.postgres.django.db.models.BinaryFieldnow validates Base64 input strictly. Invalid Base64 strings now raiseValidationErrorinstead of being silently accepted.
Requests and Responses¶
HttpRequest.multipart_parser_classcan now be customized to use a different multipart parser class.HttpResponseRedirect(and its subclasses), as well as theredirect()shortcut, now accept amax_lengthparameter to override the default maximum URL length limit.
Security¶
Signed cookies now use an unambiguous salt derivation by default. Set
SIGNED_COOKIE_LEGACY_SALT_FALLBACKtoTrueto continue accepting legacy signed cookies.
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 usingdumpdata --natural-primary.The XML deserializer now raises
SuspiciousOperationwhen it encounters unexpected nested tags.
Tasks¶
The
task()decorator now accepts**kwargs, which are forwarded to the backend’stask_class.TaskandTaskResultinstances can now be pickled and unpickled.
Tests¶
assertContains()andassertNotContains()can now be called multiple times on the sameStreamingHttpResponse. Previously, they would consume the streaming response’s content, causing subsequent calls to fail.
Utilities¶
parse_duration()now supports ISO 8601 time periods expressed in weeks (PnW).
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 forDurationField, 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_deleteis one of the database-level delete options e.g.DB_CASCADE.Set the new
DatabaseFeatures.supports_inspectdbattribute toFalseif the management command isn’t supported.The
DatabaseFeatures.prohibits_dollar_signs_in_column_aliasesfeature 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 toget_geom_placeholder_sqland 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_aggregationsattribute toFalseif the database doesn’t support bitwise aggregations.
django.contrib.admin¶
The
wideclass is removed, as it was made obsolete by the new layout.The
object-toolsblock is hoisted out of thecontentblock in forms.The undocumented
InclusionAdminNode.__init__()now takes the template tagnameas the first positional argument.The undocumented
ChangeList.has_related_field_in_list_display()method has been replaced withChangeList.get_select_related_fields().
django.contrib.auth¶
Under ASGI,
RemoteUserMiddlewareno longer prefixesHTTP_when looking up custom values inrequest.META. For example, to send-H "AuthUser: ...", theheaderattribute should beHTTP_AUTHUSER. This restores the behavior prior to Django 5.2. (The default value ofREMOTE_USERis 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
Nonein anArrayFieldwith aJSONFieldbase field are now saved as SQLNULLinstead of the JSONnullprimitive. This matches the behavior of a standaloneJSONFieldwhen storingNonevalues.
Email¶
Providing
fail_silently=True,auth_user, orauth_passwordto mail sending functions (such assend_mail()) while also providing aconnectionnow raises aTypeError.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 theconnectionproperty on theEmailMessage. (This behavior was never documented. Thesend()method will still use aconnectionthat is set on the message before sending.)
Models¶
The
iexact=Nonelookup onJSONFieldkey transforms now matches JSONnull, to match the behavior ofexact=Noneon key transforms. Previously, it was interpreted as anisnulllookup.first()andlast()no longer order by the primary key when aQuerySet’s ordering has been forcibly cleared by callingorder_by()with no arguments.SQL
SELECTaliases originating fromQuerySet.annotate()calls as well as table andJOINaliases 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 usingRawSQL, might have to be adjusted to also make use of quoting.
System checks¶
The
checkmanagement command now supplies alldatabasesif 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¶
The minimum supported version of SQLite is increased from 3.31.0 to 3.37.0.
The default value of the transitional setting
SIGNED_COOKIE_LEGACY_SALT_FALLBACKis nowFalse.GenericForeignKeynow uses a separate descriptor class: the privateGenericForeignKeyDescriptor.The undocumented
django.template.library.parse_bits()function no longer accepts thetakes_contextargument.The
Fileclass now always evaluates toTruein boolean contexts, rather than relying on thenameattribute. The built-in subclassesFieldFile,UploadedFile,TemporaryUploadedFile,InMemoryUploadedFile, andSimpleUploadedFileretain the previous behavior of evaluating based on thenameattribute.The undocumented
connection()method oflog.AdminEmailHandlerhas been removed and is no longer called. Subclasses overridingAdminEmailHandler.send_mail()should avoid callingconnection(). See Replacing get_connection() and connection arguments if specific connection configuration is needed.The internal implementation of
BrokenLinkEmailsMiddlewarehas 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 baseBrokenLinkEmailsMiddlewareclass.django.http.multipartparser.MultiPartParsernow uses strict Base64 validation when decoding encoded request data. Previously, invalid data could be silently ignored or result in empty values. Invalid data now raisesMultiPartParserError.django.core.cache.backends.db.DatabaseCachenow 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.
Features deprecated in 6.1¶
Email¶
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, andEMAIL_TIMEOUTsettings are deprecated. Replace them with aMAILERSconfiguration dictionary as described in Migrating email to mailers.mail.get_connection()is deprecated. See Replacing get_connection() and connection arguments for replacement options.The
connectionargument tosend_mail(),send_mass_mail(),mail_admins(),mail_managers(), andEmailMessageis deprecated. TheEmailMessage.connectionattribute is also deprecated. Switch to theusingargument with aMAILERSalias.The
fail_silentlyargument tosend_mail(),send_mass_mail(),mail_admins(),mail_managers(), andEmailMessage.send()is deprecated. See Replacing fail_silently for alternatives.The
auth_userandauth_passwordarguments tosend_mail()andsend_mass_mail()are deprecated. Replace them with"username"and"password"OPTIONSinMAILERS. See Replacing auth_user and auth_password.Directly constructing and using instances of the smtp.EmailBackend class is deprecated. Use
mail.mailersto 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**kwargsbefore 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_silentlyin theBaseEmailBackendis deprecated. A custom email backend that wants to supportfail_silentlyshould manage its own local attribute, not pass it to the base backend constructor. See Migrating custom email backends.
Miscellaneous¶
Calling
select_related()with no arguments to select all non-nullable related fields is deprecated. Specify the related fields to fetch instead, or use theFETCH_PEERSfetch mode.Setting
ModelAdmin.list_select_relatedtoTrueand returningTruefromModelAdmin.get_list_select_related()are deprecated. Specify the related fields to fetch instead.Calling
QuerySet.values_list()withflat=Trueand no field name is deprecated. Pass an explicit field name, likevalues_list("pk", flat=True).The use of
Noneto represent a top-level JSON scalarnullwhen queryingJSONFieldis now deprecated in favor of the newJSONNullexpression. At the end of the deprecation period,Nonevalues compile to SQLIS NULLwhen used as the top-level value.Key and index lookupsare unaffected by this deprecation.The
django.db.models.fields.BLANK_CHOICE_DASHconstant is deprecated in favor of the new constantdjango.db.models.fields.BLANK_CHOICE_LABEL.The
USE_BLANK_CHOICE_DASHtransitional setting is deprecated.The
SIGNED_COOKIE_LEGACY_SALT_FALLBACKtransitional setting is deprecated.The undocumented
get_placeholdermethod ofFieldis deprecated in favor of the newly introducedget_placeholder_sqlmethod, 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 providedcompilerargument.The
quote_name_unless_alias()method ofSQLCompiler, the type of object passed as thecompilerargument to theas_sql()method of expressions, is deprecated in favor of the newly introducedquote_name()method.The
email_backendargument oflog.AdminEmailHandleris deprecated in favor of the newly introducedusingargument.The
BitAnd,BitOr, andBitXorclasses indjango.contrib.postgres.aggregatesare deprecated in favor of the generally availableBitAnd,BitOr, andBitXorclasses.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
algorithmargument fordjango.utils.crypto.salted_hmac()anddjango.core.signing.base64_hmac()is deprecated and will change from"sha1"to"sha256"in Django 7.0. Pass an explicitalgorithmto silence the deprecation warning.Overriding
ModelAdmin.get_actions()without the newaction_locationparameter is deprecated.Unpacking or indexing the dictionary values of the
ModelAdmin.get_actions()return value is deprecated. UseActionattributes instead.Overriding
ModelAdmin.get_action_choices()without the newaction_locationparameter is deprecated.django.db.transaction.savepoint()is deprecated in favor ofsavepoint_create().
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
allparameter for thedjango.contrib.staticfiles.finders.find()function is removed in favor of thefind_allparameter.Fallbacks to
request.userandrequest.auser()whenuserisNoneindjango.contrib.auth.login()anddjango.contrib.auth.alogin(), respectively, are removed.The
orderingkeyword parameter of the PostgreSQL specific aggregation functionsdjango.contrib.postgres.aggregates.ArrayAgg,django.contrib.postgres.aggregates.JSONBAgg, anddjango.contrib.postgres.aggregates.StringAggare removed in favor of theorder_byparameter.Support for subclasses of
RemoteUserMiddlewarethat overrideprocess_request()without overridingaprocess_request()is removed.