- Model field fetch modes allow configurable on-demand fetching behavior with FETCH_ONE, FETCH_PEERS, and FETCH_RAISE modes via QuerySet.fetch_mode()
- ForeignKey.on_delete now supports database-level delete options DB_CASCADE, DB_SET_NULL, and DB_SET_DEFAULT using SQL ON DELETE clause
- New MAILERS setting supports configuring multiple email backends with different options
- Admin site login view now redirects authenticated users to the next URL if available
- Admin FilteredSelectMultiple widget now uses optgroups to preserve named groups
- Admin change list now selects only foreign key fields specified in list_display instead of all foreign key fields when list_select_related is False
- New delete_confirmation_max_display option allows customizing how many objects are displayed on admin delete confirmation pages before truncation
- Admin change forms now display form fields below their labels, help text after labels, and validation errors after help text for improved accessibility
- Admin list_display now uses boolean icons for boolean fields on related models
- New location keyword argument of action() decorator specifies which admin views the action is available on
- New description_plural keyword argument of action() decorator specifies human-readable description for actions on admin change list page
- Permission.name and Permission.codename values are now renamed when renaming models via migration
- New Permission.user_perm_str property returns string suitable for use with User.has_perm()
- isempty lookup and IsEmpty() database function are now supported on SpatiaLite
- Default iteration count for PBKDF2 password hasher increased from 1,200,000 to 1,500,000
- EMAIL_BACKEND and related EMAIL_* settings will be replaced by MAILERS in Django 7.0 and now issue deprecation warnings
Django 6.1 release notes
August 5, 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. Mainstream support is expected to end in April 2027. Extended support is expected to end in December 2027.
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_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.
- FETCH_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.
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.
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 FilteredSelectMultiple widget now uses 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 and inline protected deletion errors 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.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.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.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.sessions
- SessionBase now supports boolean evaluation via bool().
CSP
- The new csp_nonce_attr template tag renders the CSP nonce attribute on and 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.
- CSP nonce attributes are now added on , , and elements in admin templates and all built-in templates when the csp() context processor is configured. See Nonce config for setup instructions.
- A new mail.E001 deployment-only system check prevents using one of Django’s email backends that is not intended for production use in the 'default' MAILERS entry.
- A new mail.W001 system check warns when MAILERS is defined but does not include a 'default' entry.
Forms
- 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().
Generic Views
- The new RedirectView.preserve_request attribute 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'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.
- The sendtestemail command now supports a --using option to specify the MAILERS alias.
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 virtual columns (db_persist set to False) on Postgres 18+ and 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.
Requests and Responses
- 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.
Security
- Signed cookies now use an unambiguous salt derivation by default. Set SIGNED_COOKIE_LEGACY_SALT_FALLBACK to True to 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 using dumpdata --natural-primary.
- The XML deserializer now raises SuspiciousOperation when it encounters unexpected nested tags.
Tasks
- 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.
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.
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 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. …