NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #465 most downloaded on PyPI
A high-level Python web framework that encourages rapid development and clean, pragmatic design.
Last release 1 months ago
02 Sep 2026
Ships fairly regularly
a new release about every 3 weeks
Nearly every release is documented
notes for 54 of the last 60 stable releases
2 versions withdrawn
withdrawn after publishing
16 years old
442 releases · first in 2010
Fixed a regression in Django 6.1 where the deprecation of double-dot variable lookups incorrectly applied to string and translated template literals c…
September 2, 2026
Django 6.1.1 fixes one crash in Django 5.2 and several bugs in 6.1.
Fixed a crash in Django 5.2 when combining distinct(*fields) with order_by() and values() and using two lookup paths resolving to the same column (37222).
Fixed a regression in Django 6.1 where the deprecation of double-dot variable lookups incorrectly applied to string and translated template literals containing two consecutive dots, such as {{ "a..b" }} (37257).
Fixed a regression in Django 6.1 where .ModelAdmin.list_display entries that traverse multiple relations using __ could crash or display incorrect values (37270).
Fixed a bug in Django 6.1 where the fields.E323 system check did not detect mixed on_delete variants for auto-created intermediate models for ManyToManyFields (37254).
Fixed a regression in Django 6.1 where custom querysets used with ~django.db.models.Prefetch for forward foreign key or reverse one-to-one relationships were not routed using the parent queryset's database (37300).
Fixed a regression in Django 6.1 where HTML-safe strings, such as those created with ~django.utils.safestring.mark_safe, used as form media assets were treated as asset paths rather than being rendered verbatim (37262).
Fixed a regression in Django 6.1 that caused AlterField operations that changed only the Python-level on_delete option of ForeignKey or OneToOneField fields to perform unnecessary schema changes (37260).
Fixed a bug in Django 6.1 where ~django.db.models.DecimalField without max_digits and decimal_places caused a crash when retrieving values on SQLite (37275).
Fixed a regression in Django 6.1 that caused a crash when iterating a QuerySet of a model overriding Model.from_db() without the new fetch_mode keyword argument. Such overrides now work again, but are deprecated and should be updated to accept fetch_mode (37259).
Fixed a regression in Django 6.1 where an admin changelist search crashed when a search_fields entry used an __exact lookup on a field with choices, and where any search term matched all rows with a True value when an __exact lookup was used on a BooleanField (37263).
Fixed a regression in Django 6.1 that caused __in lookups on annotations to erroneously return empty querysets and __range lookups to crash when passed an iterator (37311).
Fixed a bug in Django 6.1 where .QuerySet.in_bulk chained after .QuerySet.values or .QuerySet.values_list could drop selected annotations or produce incorrect mapping keys (37312).
One column per quarter.
- Django 6.1.2 release notes - Django 6.1.1 release notes - Django 6.1 release notes
Django 6.1.2 release notes
Django 6.1.1 release notes
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 /howto/upgrade-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.
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 ~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.
FETCH_RAISE raises a ~django.core.exceptions.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 now supports database-level delete options:
~django.db.models.DB_CASCADE
~django.db.models.DB_SET_NULL
~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 ~django.db.models.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.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 /topics/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 2028; see migrating-to-mailers. To ease the transition, mail.mailers["default"] works with either MAILERS or the deprecated EMAIL_BACKEND setting defined. The deprecated ~django.core.mail.get_connection function will also return an instance of the default mailer when MAILERS is defined.
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 ~django.contrib.admin.ModelAdmin.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.
~django.contrib.admin.ModelAdmin.list_display now uses boolean icons for boolean fields on related models.
The new location keyword argument of the ~django.contrib.admin.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 admin-action-availability for details.
The new description_plural keyword argument of the ~django.contrib.admin.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.
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.
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.gis.forms.widgets.OpenLayersWidget is now based on OpenLayers 10.9.0 (previously 7.2.2).
inspectdb now introspects ~django.contrib.postgres.fields.HStoreField when psycopg 3.2+ is installed and django.contrib.postgres is in INSTALLED_APPS.
~django.contrib.postgres.constraints.ExclusionConstraint now supports the Hash index type.
~django.contrib.sessions.backends.base.SessionBase now supports boolean evaluation via ~django.contrib.sessions.backends.base.SessionBase.__bool__.
The new csp_nonce_attr template tag renders the CSP nonce attribute on <script> and <link> elements, or renders a ~django.forms.Media object's assets with the nonce applied, when the ~django.template.context_processors.csp context processor is configured. See csp-nonce for details.
A new security.W027 system check warns when ~django.middleware.csp.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 <script>, <style>, and <link> elements in admin templates and all built-in templates when the ~django.template.context_processors.csp context processor is configured. See csp-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.
The new asset object ~django.forms.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.
~django.forms.FilePathField now provides a ~django.forms.FilePathField.set_choices method to scan the directory at ~django.forms.FilePathField.path and refresh the field's choices. This allows per-request refreshing when called in a form's __init__().
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 now set ~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 loaddata command now calls ~django.db.models.signals.m2m_changed signals with raw=True when loading fixtures.
The sendtestemail command now supports a --using option to specify the MAILERS alias.
.QuerySet.in_bulk now supports chaining after .QuerySet.values and .QuerySet.values_list.
The new ~django.db.models.JSONNull expression provides an explicit way to represent the JSON scalar null. It can be used when saving a top-level ~django.db.models.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.
~django.db.models.JSONField now supports negative array indexing on Oracle 21c+.
The new ~django.db.models.functions.UUID4 and ~django.db.models.functions.UUID7 database functions were added.
~django.db.models.GeneratedField now supports virtual columns (~django.db.models.GeneratedField.db_persist set to False) on Postgres 18+ and stored columns (~django.db.models.GeneratedField.db_persist set to True) on Oracle 23ai/26ai (23.7+).
The ~django.db.models.signals.m2m_changed signal now receives a raw argument.
~django.db.models.StringAgg now supports distinct=True on SQLite when using the default delimiter Value(",") only.
The new .QuerySet.totally_ordered property returns True if the ~django.db.models.query.QuerySet is ordered and the ordering is deterministic.
The new ~django.db.models.BitAnd, ~django.db.models.BitOr, and ~django.db.models.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.
~django.http.HttpResponseRedirect (and its subclasses), as well as the ~django.shortcuts.redirect shortcut, now accept a max_length parameter to override the default maximum URL length limit.
Signed cookies now use an unambiguous salt derivation by default. Set SIGNED_COOKIE_LEGACY_SALT_FALLBACK to True to continue accepting legacy signed cookies.
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 ~django.core.exceptions.SuspiciousOperation when it encounters unexpected nested tags.
The ~django.tasks.task decorator now accepts **kwargs, which are forwarded to the backend's ~django.tasks.backends.base.BaseTaskBackend.task_class.
~django.tasks.Task and ~django.tasks.TaskResult instances can now be pickled and unpickled.
~django.test.SimpleTestCase.assertContains and ~django.test.SimpleTestCase.assertNotContains can now be called multiple times on the same ~django.http.StreamingHttpResponse. Previously, they would consume the streaming response's content, causing subsequent calls to fail.
~django.utils.dateparse.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. ~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.
Set the new DatabaseFeatures.supports_bit_aggregations attribute to False if the database doesn't support bitwise aggregations.
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().
Under ASGI, ~django.contrib.auth.middleware.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.)
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.
Top-level elements set to None in an ~django.contrib.postgres.fields.ArrayField with a ~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 ~django.db.models.JSONField when storing None values.
Providing fail_silently=True, auth_user, or auth_password to mail sending functions (such as ~django.core.mail.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.)
.EmailMessage.message now raises a ValueError if Bcc is included in the headers argument or extra_headers attribute. Use the bcc argument instead.
The iexact=None lookup on ~django.db.models.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.
~.QuerySet.first and ~.QuerySet.last no longer order by the primary key when a QuerySet's ordering has been forcibly cleared by calling ~.QuerySet.order_by with no arguments.
As default model ordering is now applied to combined querysets, ~.QuerySet.union, ~.QuerySet.difference, and ~.QuerySet.intersection raise DatabaseError when a field in .Options.ordering isn't selected by ~.QuerySet.values or ~.QuerySet.values_list. Call ~.QuerySet.order_by without arguments after combining to clear the default ordering.
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.
~django.db.models.Model._is_pk_set now returns False for DatabaseDefault values on unsaved instances.
fetch_mode=None is added to the signature of ~django.db.models.Model.from_db.
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.
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_FALLBACK is now False.
In cases where cached pages or template fragments varied on arguments, e.g. vary headers for ~django.views.decorators.cache.cache_page and ~django.middleware.cache.UpdateCacheMiddleware, or the vary_on arguments to the cache template tag (generated by ~django.core.cache.utils.make_template_fragment_key), the cache keys are different from the keys generated by older versions of Django. After upgrading to Django 6.1, the first request to any previously cached page or template fragment that varies on additional information will be a cache miss.
~django.contrib.contenttypes.fields.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 ~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.
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 migrating-to-mailers-get-connection 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 /howto/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 a MAILERS configuration dictionary as described in migrating-to-mailers.
.mail.get_connection is deprecated. See migrating-to-mailers-get-connection 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 migrating-to-mailers-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 migrating-to-mailers-auth.
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 2028. See migrating-to-mailers-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-to-mailers-email-backends.
Calling ~django.db.models.query.QuerySet.select_related with no arguments to select all non-nullable related fields is deprecated. Specify the related fields to fetch instead, or use the ~django.db.models.FETCH_PEERS fetch mode.
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).
Support for Model.from_db() methods that do not accept the fetch_mode keyword argument is deprecated.
The use of None to represent a top-level JSON scalar null when querying ~django.db.models.JSONField is now deprecated in favor of the new ~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. Key and index lookups are unaffected by this deprecation.
The undocumented django.db.models.fields.BLANK_CHOICE_DASH constant is deprecated. See the USE_BLANK_CHOICE_DASH transitional setting for migration advice.
The USE_BLANK_CHOICE_DASH transitional setting is deprecated.
The SIGNED_COOKIE_LEGACY_SALT_FALLBACK transitional setting is deprecated.
The undocumented get_placeholder method of ~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 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. See migrating-to-mailers-adminemailhandler for details.
The BitAnd, BitOr, and BitXor classes in django.contrib.postgres.aggregates are deprecated in favor of the generally available ~django.db.models.BitAnd, ~django.db.models.BitOr, and ~django.db.models.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 2028. 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 ~django.contrib.admin.Action attributes instead.
Overriding ModelAdmin.get_action_choices() without the new action_location parameter is deprecated.
django.db.transaction.savepoint is deprecated in favor of ~django.db.transaction.savepoint_create.
These features have reached the end of their deprecation cycle and are removed in Django 6.1.
See 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.
Nothing published for this version
Nothing published for this version
Nothing published for this version
CVE-2026-15307: Server-side file-write and request forgery via spatial lookups
August 4, 2026
Django 6.0.8 fixes one security issue with severity "high", two security issues with severity "moderate", one security issue with severity "low", and several bugs in 6.0.7.
Spatial lookups allowed str and dict lookup values to be passed to ~django.contrib.gis.gdal.GDALRaster when they represented rasters. Depending on the raster driver, this could write a file to disk (in some cases enabling remote code execution) or issue a network request as the Django process user. Because the admin changelist permits filtering via ~django.contrib.admin.ModelAdmin.lookup_allowed, the flaw was reachable by staff users with view permission on any registered model containing a spatial field.
The following types are now disallowed by spatial lookups:
dict
A str that is not a valid ~django.contrib.gis.geos.GEOSGeometry, e.g. a serialized dictionary
This is a backward incompatible change. As a reminder, all untrusted user input should be validated before use. For that reason, assignments to model fields are unaffected and still accept these input types.
For guidance on how to keep using these types in spatial lookups, on validating untrusted input, and on further security considerations, see raster security considerations.
This issue has severity "high" according to the Django security policy.
~django.utils.translation.check_for_language was subject to a potential denial-of-service attack when checking many distinct, very long language codes. Each code was used as a key in an in-memory cache, consuming process memory.
The language value reaches this function through the django.views.i18n.set_language view (not active by default) from POST data. Since request data is limited by DATA_UPLOAD_MAX_MEMORY_SIZE and the cache is configured to store a maximum number of entries, the memory that could be consumed was bounded.
To mitigate this vulnerability, language codes longer than 500 characters are now rejected before the cached lookup.
This issue has severity "low" according to the Django security policy.
~django.contrib.gis.geos.GEOSGeometry was subject to a potential denial-of-service attack when provided deeply nested GEOMETRYCOLLECTION objects, leading to a segmentation fault in GEOS. A maximum depth of 198 GEOMETRYCOLLECTIONs is now enforced for the well-known text (WKT) format, and a maximum number of 198 GEOMETRYCOLLECTIONs in total (breadth and depth) is enforced for well-known binary (WKB).
Lookups against spatial fields and the ~django.contrib.gis.forms.GeometryField form field were also affected.
The limit can be customized through the new max_geom_collections argument, available on ~django.contrib.gis.geos.GEOSGeometry, the form field, and the model field. The limit is not applied to GeoJSON inputs, as they were parsed by GDAL and are not affected.
This issue has severity "moderate" according to the Django security policy.
The admin renders ~django.db.models.URLField values as clickable links on changelist views and read-only fields. The link was generated without validating the value as a safe URL, so a stored value using a potentially dangerous scheme was rendered as a link.
URLField values shown via display_for_field are now validated using ~django.core.validators.URLValidator before a link is rendered, and displayed as plain text if validation is failed.
This issue has severity "moderate" according to the Django security policy.
Fixed a regression in Django 6.0 that caused ~django.db.models.query.QuerySet.bulk_create to crash on databases that support returning rows from bulk inserts when a related object providing the primary key was saved after assignment (37234).
Added compatibility for sqlparse 0.5.5 (37235).
CVE-2026-48588: Potential exposure of private data via cached Set-Cookie response
July 7, 2026
Django 6.0.7 fixes three security issues with severity "low" and one bug in 6.0.6.
~django.middleware.cache.UpdateCacheMiddleware and ~django.views.decorators.cache.cache_page avoided caching responses that set a cookie while varying on Cookie only when the incoming request contained no cookies at all. When the request already carried an unrelated cookie (such as a language or theme preference cookie), the protection did not apply, allowing a response that sets a session or other sensitive cookie to be stored in Django's shared cache.
This issue has severity "low" according to the Django security policy.
When ~django.contrib.gis.gdal.GDALRaster was instantiated with a bytes object representing a raster file, the ~django.contrib.gis.gdal.GDALRaster.vsi_buffer property could over-read the allocated buffer by approximately 32 bytes. This could result in information disclosure of adjacent heap memory or, in rare cases, a segmentation fault. Only rasters stored in GDAL's virtual filesystem were affected.
This issue has severity "low" according to the Django security policy.
~django.core.validators.DomainNameValidator accepted newlines in domain names. If such values were included in HTTP responses, header injection attacks were possible. Django itself wasn't vulnerable because ~django.http.HttpResponse prohibits newlines in HTTP headers.
The vulnerability only affected uses of DomainNameValidator outside Django form fields, as CharField strips newlines by default.
This issue has severity "low" according to the Django security policy.
Fixed a regression in Django 6.0 where the PBKDF2 and MD5 password hashers raised UnicodeDecodeError for bytes passwords that were not valid UTF-8. Passwords supplied as str or as UTF-8 bytes are unaffected (37184).
CVE-2026-6873: Signed cookie salt namespace collision
June 3, 2026
Django 6.0.6 fixes five security issues with severity "low" and one bug in 6.0.5. Also, the latest string translations from Transifex are incorporated.
~django.http.HttpRequest.get_signed_cookie derived the signing salt by concatenating the cookie name (key) and salt arguments. When distinct name and salt pairs produced the same concatenation, cookies could be accepted in a context different from the one where they were signed.
Cookies are now signed with an unambiguous salt derivation. For backwards compatibility, cookies signed by older Django versions are accepted until Django 2028. Projects affected by the above ambiguity should set SIGNED_COOKIE_LEGACY_SALT_FALLBACK to False to reject older cookies immediately.
This issue has severity "low" according to the Django security policy.
When using EMAIL_USE_TLS, a failed STARTTLS handshake could leave a partially-initialized connection that would subsequently be reused for sending email without encryption. This can occur with fail_silently=True, as used by ~django.core.mail.send_mail and ~django.middleware.common.BrokenLinkEmailsMiddleware, among others. Connections configured with EMAIL_USE_SSL are not affected.
This issue has severity "low" according to the Django security policy.
~django.middleware.cache.UpdateCacheMiddleware and ~django.views.decorators.cache.cache_page incorrectly cached responses marked with private Cache-Control directives when using mixed or uppercase values (e.g. Private).
The ~django.views.decorators.cache.cache_control decorator and ~django.utils.cache.patch_cache_control function were not affected, since they normalize directives to lowercase. This issue only affects responses where Cache-Control is set manually.
This issue has severity "low" according to the Django security policy.
~django.middleware.cache.UpdateCacheMiddleware and ~django.views.decorators.cache.cache_page decorator allowed responses to requests bearing an Authorization header (and without Cache-Control: public) to be cached. To conform with the existing mechanism for constructing cache keys, responses to these requests will now vary on Authorization.
This issue has severity "low" according to the Django security policy.
~django.middleware.cache.UpdateCacheMiddleware incorrectly cached responses whose Vary header values contained leading or trailing whitespace. Because has_vary_header() failed to strip that, a Vary: * header value with surrounding whitespace was not recognized as containing the wildcard, causing it to be stored and potentially served from the cache when it should not have been.
This issue has severity "low" according to the Django security policy.
Fixed a bug in Django 6.0 where an alert message on an admin changelist with ModelAdmin.list_editable referred to the "Run" button by its previous name (37094).
CVE-2026-5766: Potential denial-of-service vulnerability in ASGI requests via file upload limit bypass
May 5, 2026
Django 6.0.5 fixes three security issues with severity "low" and several bugs in 6.0.4.
ASGI requests with a missing or understated Content-Length header could bypass the FILE_UPLOAD_MAX_MEMORY_SIZE limit, potentially loading large files into memory and causing service degradation.
As a reminder, Django expects a limit to be configured at the web server level rather than solely relying on FILE_UPLOAD_MAX_MEMORY_SIZE.
This issue has severity "low" according to the Django security policy.
Response headers did not vary on cookies if a session was not modified, but SESSION_SAVE_EVERY_REQUEST was True. A remote attacker could steal a user's session after that user visits a cached public page.
This issue has severity "low" according to the Django security policy.
Previously, ~django.middleware.cache.UpdateCacheMiddleware would erroneously cache requests where the Vary header contained an asterisk ('*'). This could lead to private data being stored and served.
This issue has severity "low" according to the Django security policy.
Fixed a misplaced </div> in the django/contrib/admin/templates/admin/change_list.html template added in Django 6.0 that could be problematic when overriding the pagination block (37029).
Fixed a bug in Django 6.0 where deprecation warnings incorrectly skipped lines from third-party packages prefixed with "django" (37067).
CVE-2026-3902: ASGI header spoofing via underscore/hyphen conflation
April 7, 2026
Django 6.0.4 fixes one security issue with severity "moderate", four security issues with severity "low", and several bugs in 6.0.3. Also, the latest string translations from Transifex are incorporated.
ASGIRequest normalizes header names following WSGI conventions, mapping hyphens to underscores. As a result, even in configurations where reverse proxies carefully strip security-sensitive headers named with hyphens, such a header could be spoofed by supplying a header named with underscores.
Under WSGI, it is the responsibility of the server or proxy to avoid ambiguous mappings. (Django's runserver was patched in 2015-0219.) But under ASGI, there is not the same uniform expectation, even if many proxies protect against this under default configuration (including nginx via underscores_in_headers off;).
Headers containing underscores are now ignored by ASGIRequest, matching the behavior of Daphne, the reference server for ASGI.
This issue has severity "low" according to the Django security policy.
Add permissions on inline model instances were not validated on submission of forged POST data in ~django.contrib.contenttypes.admin.GenericInlineModelAdmin.
This issue has severity "low" according to the Django security policy.
Admin changelist forms using ~django.contrib.admin.ModelAdmin.list_editable incorrectly allowed new instances to be created via forged POST data.
This issue has severity "low" according to the Django security policy.
When using django.http.multipartparser.MultiPartParser, multipart uploads with Content-Transfer-Encoding: base64 that include excessive whitespace may trigger repeated memory copying, potentially degrading performance.
This issue has severity "moderate" according to the Django security policy.
ASGI requests with a missing or understated Content-Length header could bypass the DATA_UPLOAD_MAX_MEMORY_SIZE limit when reading HttpRequest.body, potentially loading an unbounded request body into memory and causing service degradation.
This issue has severity "low" according to the Django security policy.
Fixed a regression in Django 6.0 where ~django.contrib.auth.alogin and ~django.contrib.auth.alogout did not respectively set or clear request.user if it had already been materialized (e.g., by sync middleware) (37017).
Fixed a regression in Django 6.0 in admin forms where RelatedFieldWidgetWrapper incorrectly wrapped all widgets in a <fieldset> (36949).
Fixed a bug in Django 6.0 where the fields.E348 system check did not detect name clashes between model managers and ~django.db.models.ForeignKey.related_names for non-self-referential relationships (36973).
CVE-2026-25673: Potential denial-of-service vulnerability in URLField via Unicode normalization on Windows
March 3, 2026
Django 6.0.3 fixes a security issue with severity "moderate", a security issue with severity "low", and several bugs in 6.0.2.
The ~django.forms.URLField form field's to_python() method used ~urllib.parse.urlsplit to determine whether to prepend a URL scheme to the submitted value. On Windows, urlsplit() performs NFKC normalization, which can be disproportionately slow for large inputs containing certain characters.
URLField.to_python() now uses a simplified scheme detection, avoiding Unicode normalization entirely and deferring URL validation to the appropriate layers. As a result, while leading and trailing whitespace is still stripped by default, characters such as newlines, tabs, and other control characters within the value are no longer handled by URLField.to_python(). When using the default ~django.core.validators.URLValidator, these values will continue to raise ~django.core.exceptions.ValidationError during validation, but if you rely on custom validators, ensure they do not depend on the previous behavior of URLField.to_python().
This issue has severity "moderate" according to the Django security policy.
Django's file-system storage and file-based cache backends used the process umask to control permissions when creating directories. In multi-threaded environments, one thread's temporary umask change can affect other threads' file and directory creation, resulting in file system objects being created with unintended permissions.
Django now applies the requested permissions via ~os.chmod after ~os.mkdir, removing the dependency on the process-wide umask.
This issue has severity "low" according to the Django security policy.
Fixed NameError when inspecting functions making use of deferred annotations in Python 3.14 (36903).
Fixed AttributeError when subclassing builtin lookups and neglecting to override as_sql() to accept any sequence (36934).
Fixed TypeError when deprecation warnings are emitted in environments importing Django by namespace (36961).
Fixed a visual regression where fieldset legends were misaligned in the admin (36920).
Prevented the django.tasks.signals.task_finished signal from writing extraneous log messages when no exceptions are encountered (36951).
CVE-2025-13473: Username enumeration through timing difference in mod_wsgi authentication handler
February 3, 2026
Django 6.0.2 fixes three security issues with severity "high", two security issues with severity "moderate", one security issue with severity "low", and several bugs in 6.0.1.
The django.contrib.auth.handlers.modwsgi.check_password() function for authentication via mod_wsgi allowed remote attackers to enumerate users via a timing attack.
This issue has severity "low" according to the Django security policy.
When receiving duplicates of a single header, ASGIRequest allowed a remote attacker to cause a potential denial-of-service via a specifically created request with multiple duplicate headers. The vulnerability resulted from repeated string concatenation while combining repeated headers, which produced super-linear computation resulting in service degradation or outage.
This issue has severity "moderate" according to the Django security policy.
Raster lookups on GIS fields (only implemented on PostGIS) were subject to SQL injection if untrusted data was used as a band index.
As a reminder, all untrusted user input should be validated before use.
This issue has severity "high" according to the Django security policy.
django.utils.text.Truncator.chars() and Truncator.words() methods (with html=True) and the truncatechars_html and truncatewords_html template filters were subject to a potential denial-of-service attack via certain inputs with a large number of unmatched HTML end tags, which could cause quadratic time complexity during HTML parsing.
This issue has severity "moderate" according to the Django security policy.
.FilteredRelation was subject to SQL injection in column aliases via control characters, using a suitably crafted dictionary, with dictionary expansion, as the **kwargs passed to .QuerySet.annotate, ~.QuerySet.aggregate, ~.QuerySet.extra, ~.QuerySet.values, ~.QuerySet.values_list, and ~.QuerySet.alias.
This issue has severity "high" according to the Django security policy.
.QuerySet.order_by was subject to SQL injection in column aliases containing periods when the same alias was, using a suitably crafted dictionary, with dictionary expansion, used in .FilteredRelation.
This issue has severity "high" according to the Django security policy.
Fixed a visual regression in Django 6.0 that caused the admin filter sidebar to wrap below the changelist when filter elements contained long text (36850).
Fixed a visual regression in Django 6.0 for admin form fields grouped under a <fieldset> aligned horizontally (36788).
Fixed a regression in Django 6.0 where auto_now_add field values were not populated during INSERT operations, due to incorrect parameters passed to field.pre_save() (36847).
Django 6.0.1 fixes one data loss bug introduced in Django 5.2 as well as several other bugs in Django 6.0.
January 6, 2026
Django 6.0.1 fixes one data loss bug introduced in Django 5.2 as well as several other bugs in Django 6.0.
Fixed a bug in Django 5.2 where data exceeding max_length was silently truncated by .QuerySet.bulk_create on PostgreSQL (33647).
Fixed a regression in Django 6.0 where querystring mishandled multi-value ~django.http.QueryDict keys, both by only preserving the last value and by incorrectly handling None values (36783).
Fixed a regression in Django 6.0 that prevented changing the name of a ~django.db.models.ManyToManyField from taking effect when applying migrations (36800).
Fixed a bug where management command colorized help (introduced in Python 3.14) ignored the --no-color option and the DJANGO_COLORS setting (36376).
Fixed a regression in Django 6.0 that caused ~django.db.models.query.QuerySet.bulk_create to crash when introspecting the connection on SQLite (36818).
Fixed a visual regression in Django 6.0 for admin form fields grouped under a <fieldset> in Safari (36807).
Fixed a crash in Django 6.0 caused by infinite recursion when calling repr() on an unevaluated django.utils.csp.LazyNonce instance (36810).
Fixed a regression in Django 6.0 where ~django.urls.path routes defined using ~django.utils.translation.gettext_lazy failed to resolve correctly (36796).
Fixed a regression in Django 6.0 where the .Widget.use_fieldset attribute of ~django.forms.ClearableFileInput was flipped from False to True (36829).
Reverted an undocumented optimization in Django 6.0 that modified permission ~django.contrib.auth.models.Permission.name and ~django.contrib.auth.models.Permission.codename values when renaming models via a migration. This change could affect unrelated ~django.contrib.auth.models.Permission objects (36843) and did not report conflicts (36793).
- Django 6.0.9 release notes - Django 6.0.8 release notes - Django 6.0.7 release notes - Django 6.0.6 release notes - Django 6.0.5 release notes - Dja
Django 6.0.9 release notes
Django 6.0.8 release notes
Django 6.0.7 release notes
Django 6.0.6 release notes
Django 6.0.5 release notes
Django 6.0.4 release notes
Django 6.0.3 release notes
Django 6.0.2 release notes
Django 6.0.1 release notes
Django 6.0 release notes
December 3, 2025
Welcome to Django 6.0!
These release notes cover the new features, as well as some backwards incompatible changes you should be aware of when upgrading from Django 5.2 or earlier. We've begun the deprecation process for some features.
See the /howto/upgrade-version guide if you're updating an existing project.
Django 6.0 supports Python 3.12, 3.13, and 3.14. We highly recommend, and only officially support, the latest release of each series.
The Django 5.2.x series is the last to support Python 3.10 and 3.11.
Following the release of Django 6.0, we suggest that third-party app authors drop support for all versions of Django prior to 5.2. At that time, you should be able to run your package's tests using python -Wd so that deprecation warnings appear. After making the deprecation warning fixes, your app should be compatible with Django 6.0.
Built-in support for the Content Security Policy (CSP) standard is now available, making it easier to protect web applications against content injection attacks such as cross-site scripting (XSS). CSP allows declaring trusted sources of content by giving browsers strict rules about which scripts, styles, images, or other resources can be loaded.
CSP policies can now be enforced or monitored directly using built-in tools: headers are added via the ~django.middleware.csp.ContentSecurityPolicyMiddleware, nonces are supported through the ~django.template.context_processors.csp context processor, and policies are configured using the SECURE_CSP and SECURE_CSP_REPORT_ONLY settings.
These settings accept Python dictionaries and support Django-provided constants for clarity and safety. For example:
from django.utils.csp import CSP
SECURE_CSP = {
"default-src": [CSP.SELF],
"script-src": [CSP.SELF, CSP.NONCE],
"img-src": [CSP.SELF, "https:"],
}
The resulting Content-Security-Policy header would be set to:
default-src 'self'; script-src 'self' 'nonce-SECRET'; img-src 'self' https:
To get started, follow the CSP how-to guide. For in-depth guidance, see the CSP security overview and the reference docs, which include details about decorators to override or disable policies on a per-view basis.
The Django Template Language now supports template partials, making it easier to encapsulate and reuse small named fragments within a template file. The new tags {% partialdef %} and {% partial %} define a partial and render it, respectively.
Partials can also be referenced using the template_name#partial_name syntax with ~django.template.Engine.get_template, ~django.shortcuts.render, {% include %}, and other template-loading tools, enabling more modular and maintainable templates without needing to split components into separate files.
A migration guide is available if you're updating from the django-template-partials third-party package.
Django now includes a built-in Tasks framework for running code outside the HTTP request–response cycle. This enables offloading work, such as sending emails or processing data, to background workers.
The framework provides task definition, validation, queuing, and result handling. Django guarantees consistent behavior for creating and managing tasks, while the responsibility for running them continues to belong to external worker processes.
Tasks are defined using the ~django.tasks.task decorator:
from django.core.mail import send_mail
from django.tasks import task
@task
def email_users(emails, subject, message):
return send_mail(subject, message, None, emails)
Once defined, tasks can be enqueued through a configured backend:
email_users.enqueue(
emails=["user@example.com"],
subject="You have a message",
message="Hello there!",
)
Backends are configured via the TASKS setting. The two built-in backends included in this release are primarily intended for development and testing.
Django handles task creation and queuing, but does not provide a worker mechanism to run tasks. Execution must be managed by external infrastructure, such as a separate process or service.
See /topics/tasks for an overview and the Tasks reference for API details.
Email handling in Django now uses Python's modern email API, introduced in Python 3.6. This API, centered around the email.message.EmailMessage class, offers a cleaner and Unicode-friendly interface for composing and sending emails. It replaces use of Python's older legacy (Compat32) API, which relied on lower-level MIME classes (from email.mime) and required more manual handling of message structure and encoding.
Notably, the return type of the EmailMessage.message() method is now an instance of Python's email.message.EmailMessage. This supports the same API as the previous SafeMIMEText and SafeMIMEMultipart return types, but is not an instance of those now-deprecated classes.
The Font Awesome Free icon set (version 6.7.2) is now used for the admin interface icons.
The new .AdminSite.password_change_form attribute allows customizing the form used in the admin site password change view.
Message levels messages.DEBUG and messages.INFO now have distinct icons and CSS styling. Previously, both levels shared the same appearance as messages.SUCCESS. Given that .ModelAdmin.message_user uses messages.INFO by default, set the level to messages.SUCCESS to keep the previous icon and styling.
The default iteration count for the PBKDF2 password hasher is increased from 1,000,000 to 1,200,000.
The new .GEOSGeometry.hasm property checks whether the geometry has the M dimension.
The new ~django.contrib.gis.db.models.functions.Rotate database function rotates a geometry by a specified angle around the origin or a specified point.
The new .BaseGeometryWidget.base_layer attribute allows specifying a JavaScript map base layer, enabling customization of map tile providers.
coveredby and isvalid lookups, ~django.contrib.gis.db.models.Collect aggregation, and ~django.contrib.gis.db.models.functions.GeoHash and ~django.contrib.gis.db.models.functions.IsValid database functions are now supported on MariaDB 12.0.1+.
The new geom_type lookup and GeometryType() database function allow filtering geometries by their types.
Widgets from django.contrib.gis.forms.widgets now render without inline JavaScript in templates. If you have customized any geometry widgets or their templates, you may need to update them to match the new layout.
The new Lexeme expression for full text search provides fine-grained control over search terms. Lexeme objects automatically escape their input and support logical combination operators (&, |, ~), prefix matching, and term weighting.
Model fields, indexes, and constraints from django.contrib.postgres now include system checks to verify that django.contrib.postgres is an installed app.
The .CreateExtension, .BloomExtension, .BtreeGinExtension, .BtreeGistExtension, .CITextExtension, .CryptoExtension, .HStoreExtension, .TrigramExtension, and .UnaccentExtension operations now support the optional hints parameter. This allows providing database hints to database routers to assist them in making routing decisions.
~django.contrib.staticfiles.storage.ManifestStaticFilesStorage now ensures consistent path ordering in manifest files, making them more reproducible and reducing unnecessary diffs.
The collectstatic command now reports only a summary for skipped files (and for deleted files when using --clear) at --verbosity 1. To see per-file details for either case, set --verbosity to 2 or higher.
The new policy argument for EmailMessage.message() allows specifying the email policy, the set of rules for updating and serializing the representation of the message. Defaults to email.policy.default.
EmailMessage.attach() now accepts a ~email.message.MIMEPart object from Python's modern email API.
Added support and translations for the Haitian Creole language.
The startproject and startapp commands now create the custom target directory if it doesn't exist.
Common utilities, such as django.conf.settings, are now automatically imported to the shell by default.
Squashed migrations can now themselves be squashed before being transitioned to normal migrations.
Migrations now support serialization of zoneinfo.ZoneInfo instances.
Serialization of deconstructible objects now supports keyword arguments with names that are not valid Python identifiers.
Constraints now implement a check() method that is already registered with the check framework.
The new order_by argument for ~django.db.models.Aggregate allows specifying the ordering of the elements in the result.
The new .Aggregate.allow_order_by class attribute determines whether the aggregate function allows passing an order_by keyword argument.
The new ~django.db.models.StringAgg aggregate returns the input values concatenated into a string, separated by the delimiter string. This aggregate was previously supported only for PostgreSQL.
The ~django.db.models.Model.save method now raises a specialized Model.NotUpdated exception, when a forced update results in no affected rows, instead of a generic django.db.DatabaseError.
.QuerySet.raw now supports models with a ~django.db.models.CompositePrimaryKey.
Subqueries returning a ~django.db.models.CompositePrimaryKey can now be used as the target of lookups other than __in, such as __exact.
~django.db.models.JSONField now supports negative array indexing on SQLite.
The new ~django.db.models.AnyValue aggregate returns an arbitrary value from the non-null input values. This is supported on SQLite, MySQL, Oracle, and PostgreSQL 16+.
~django.db.models.GeneratedFields and fields assigned expressions are now refreshed from the database after ~django.db.models.Model.save on backends that support the RETURNING clause (SQLite, PostgreSQL, and Oracle). On backends that don't support it (MySQL and MariaDB), the fields are marked as deferred to trigger a refresh on subsequent accesses.
Using a ForeignObject with multiple from_fields in Model indexes, constraints, or unique_together now emits a system check error.
The new ~django.core.paginator.AsyncPaginator and ~django.core.paginator.AsyncPage provide async implementations of ~django.core.paginator.Paginator and ~django.core.paginator.Page respectively.
Multiple Cookie headers are now supported for HTTP/2 requests when running with ASGI.
The new variable forloop.length is now available within a for loop.
The querystring template tag now consistently prefixes the returned query string with a ?, ensuring reliable link generation behavior.
The querystring template tag now accepts multiple positional arguments, which must be mappings, such as ~django.http.QueryDict or dict.
The .DiscoverRunner now supports parallel test execution on systems using the forkserver multiprocessing start method.
This section describes changes that may be needed in third-party database backends.
~django.db.backends.base.schema.BaseDatabaseSchemaEditor and PostgreSQL backends no longer use CASCADE when dropping a column.
DatabaseOperations.return_insert_columns() and DatabaseOperations.fetch_returned_insert_rows() methods are renamed to returning_columns() and fetch_returned_rows(), respectively, to denote they can be used in the context of UPDATE … RETURNING statements as well as INSERT … RETURNING.
The DatabaseOperations.fetch_returned_insert_columns() method is removed and the fetch_returned_rows() method replacing fetch_returned_insert_rows() expects both a cursor and returning_params to be provided, just like fetch_returned_insert_columns() did.
If the database supports UPDATE … RETURNING statements, backends can set DatabaseFeatures.can_return_rows_from_update=True.
Upstream support for MariaDB 10.5 ends in June 2025. Django 6.0 supports MariaDB 10.6 and higher.
Because Python 3.12 is now the minimum supported version for Django, any optional dependencies must also meet that requirement. The following versions of each library are the first to add or confirm compatibility with Python 3.12:
aiosmtpd 1.4.5
argon2-cffi 23.1.0
bcrypt 4.1.1
docutils 0.22
geoip2 4.8.0
Pillow 10.1.0
mysqlclient 2.2.1
numpy 1.26.0
PyYAML 6.0.2
psycopg 3.1.12
psycopg2 2.9.9
redis-py 5.1.0
selenium 4.23.0
sqlparse 0.5.0
tblib 3.0.0
The undocumented mixed_subtype and alternative_subtype properties of ~django.core.mail.EmailMessage and ~django.core.mail.EmailMultiAlternatives are no longer supported.
The undocumented encoding property of ~django.core.mail.EmailMessage no longer supports Python legacy email.charset.Charset objects.
As the internal implementations of ~django.core.mail.EmailMessage and ~django.core.mail.EmailMultiAlternatives have changed significantly, closely examine any custom subclasses that rely on overriding undocumented, internal underscore methods.
Since Django 3.2, when the DEFAULT_AUTO_FIELD setting was added, the default startproject template's settings.py contained:
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
and the default startapp template's AppConfig contained:
default_auto_field = "django.db.models.BigAutoField"
At that time, the default value of DEFAULT_AUTO_FIELD remained django.db.models.AutoField for backwards compatibility.
In Django 6.0, DEFAULT_AUTO_FIELD now defaults to django.db.models.BigAutoField and the aforementioned lines in the project and app templates are removed.
Most projects shouldn't be affected, since Django 3.2 has raised the system check warning models.W042 for projects that don't set DEFAULT_AUTO_FIELD.
If you haven't dealt with this warning by now, add DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' to your project's settings, or default_auto_field = 'django.db.models.AutoField' to an app's AppConfig, as needed.
Prior to Django 6.0, custom lookups and custom expressions implementing the as_sql() method (and its supporting methods process_lhs() and process_rhs()) were allowed to return a sequence of params in either a list or a tuple. To address the interoperability problems that resulted, the second return element of the as_sql() method should now be a tuple:
def as_sql(self, compiler, connection) -> tuple[str, tuple]: ...
If your custom expressions support multiple versions of Django, you should adjust any pre-processing of parameters to be resilient against either tuples or lists. For instance, prefer unpacking like this:
params = (*lhs_params, *rhs_params)
The JSON serializer now writes a newline at the end of the output, even without the indent option set.
The minimum supported version of asgiref is increased from 3.8.1 to 3.9.1.
Field.pre_save may now be called more than once when saving model instances, so custom implementations should be idempotent and free of side effects.
django.core.mail APIs now require keyword arguments for less commonly used parameters. Using positional arguments for these now emits a deprecation warning and will raise a TypeError when the deprecation period ends:
All optional parameters (fail_silently and later) must be passed as keyword arguments to .get_connection, .mail_admins, .mail_managers, .send_mail, and .send_mass_mail.
All parameters must be passed as keyword arguments when creating an .EmailMessage or .EmailMultiAlternatives instance, except for the first four (subject, body, from_email, and to), which may still be passed either as positional or keyword arguments.
BaseDatabaseCreation.create_test_db(serialize) is deprecated. Use serialize_db_to_string() instead.
The PostgreSQL StringAgg class is deprecated in favor of the generally available ~django.db.models.StringAgg class.
Passing a string to the ~django.contrib.postgres.aggregates.StringAgg.delimiter argument of the (deprecated) PostgreSQL StringAgg class is deprecated. Use a ~django.db.models.Value or expression instead to prepare for compatibility with the generally available ~django.db.models.StringAgg class.
The PostgreSQL OrderableAggMixin is deprecated in favor of the order_by attribute now available on the ~django.db.models.Aggregate class.
The default protocol in urlize and urlizetrunc will change from HTTP to HTTPS in Django 2028. Set the transitional setting URLIZE_ASSUME_HTTPS to True to opt into assuming HTTPS during the Django 6.x release cycle.
The URLIZE_ASSUME_HTTPS transitional setting is deprecated.
Setting ADMINS or MANAGERS to a list of (name, address) tuples is deprecated. Set to a list of email address strings instead. Django never used the name portion. To include a name, format the address string as '"Name" <address>' or use Python's email.utils.formataddr.
Support for the orphans argument being larger than or equal to the per_page argument of django.core.paginator.Paginator and django.core.paginator.AsyncPaginator is deprecated.
Using a percent sign in a column alias or annotation is deprecated.
Support for passing Python's legacy email ~email.mime.base.MIMEBase object to EmailMessage.attach() (or including one in the message's attachments list) is deprecated. For complex attachments requiring additional headers or parameters, switch to the modern email API's ~email.message.MIMEPart.
The django.core.mail.BadHeaderError exception is deprecated. Python's modern email raises a !ValueError for email headers containing prohibited characters.
The django.core.mail.SafeMIMEText and SafeMIMEMultipart classes are deprecated.
The undocumented django.core.mail.forbid_multi_line_headers() and django.core.mail.message.sanitize_address() functions are deprecated.
These features have reached the end of their deprecation cycle and are removed in Django 6.0.
See deprecated-features-5.0 for details on these changes, including how to remove usage of these features.
Support for passing positional arguments to BaseConstraint is removed.
The DjangoDivFormRenderer and Jinja2DivFormRenderer transitional form renderers are removed.
BaseDatabaseOperations.field_cast_sql() is removed.
request is required in the signature of ModelAdmin.lookup_allowed() subclasses.
Support for calling format_html() without passing args or kwargs is removed.
The default scheme for forms.URLField has changed from "http" to "https".
The FORMS_URLFIELD_ASSUME_HTTPS transitional setting is removed.
The django.db.models.sql.datastructures.Join no longer falls back to get_joining_columns().
The get_joining_columns() method of ForeignObject and ForeignObjectRel is removed.
The ForeignObject.get_reverse_joining_columns() method is removed.
Support for cx_Oracle is removed.
The ChoicesMeta alias to django.db.models.enums.ChoicesType is removed.
The Prefetch.get_current_queryset() method is removed.
The get_prefetch_queryset() method of related managers and descriptors is removed.
get_prefetcher() and prefetch_related_objects() no longer fall back to get_prefetch_queryset().
See deprecated-features-5.1 for details on these changes, including how to remove usage of these features.
django.urls.register_converter() no longer allows overriding existing converters.
The ModelAdmin.log_deletion() and LogEntryManager.log_action() methods are removed.
The undocumented django.utils.itercompat.is_iterable() function and the django.utils.itercompat module are removed.
The django.contrib.gis.geoip2.GeoIP2.coords() method is removed.
The django.contrib.gis.geoip2.GeoIP2.open() method is removed.
Support for passing positional arguments to Model.save() and Model.asave() is removed.
The setter for django.contrib.gis.gdal.OGRGeometry.coord_dim is removed.
The check keyword argument of CheckConstraint is removed.
The get_cache_name() method of FieldCacheMixin is removed.
The OS_OPEN_FLAGS attribute of ~django.core.files.storage.FileSystemStorage is removed.
Nothing published for this version
Nothing published for this version
Nothing published for this version
CVE-2026-15307: Server-side file-write and request forgery via spatial lookups
August 4, 2026
Django 5.2.17 fixes one security issue with severity "high", two security issues with severity "moderate", and one security issue with severity "low" in 5.2.16.
Spatial lookups allowed str and dict lookup values to be passed to ~django.contrib.gis.gdal.GDALRaster when they represented rasters. Depending on the raster driver, this could write a file to disk (in some cases enabling remote code execution) or issue a network request as the Django process user. Because the admin changelist permits filtering via ~django.contrib.admin.ModelAdmin.lookup_allowed, the flaw was reachable by staff users with view permission on any registered model containing a spatial field.
The following types are now disallowed by spatial lookups:
dict
A str that is not a valid ~django.contrib.gis.geos.GEOSGeometry, e.g. a serialized dictionary
This is a backward incompatible change. As a reminder, all untrusted user input should be validated before use. For that reason, assignments to model fields are unaffected and still accept these input types.
For guidance on how to keep using these types in spatial lookups, on validating untrusted input, and on further security considerations, see raster security considerations.
This issue has severity "high" according to the Django security policy.
~django.utils.translation.check_for_language was subject to a potential denial-of-service attack when checking many distinct, very long language codes. Each code was used as a key in an in-memory cache, consuming process memory.
The language value reaches this function through the django.views.i18n.set_language view (not active by default) from POST data. Since request data is limited by DATA_UPLOAD_MAX_MEMORY_SIZE and the cache is configured to store a maximum number of entries, the memory that could be consumed was bounded.
To mitigate this vulnerability, language codes longer than 500 characters are now rejected before the cached lookup.
This issue has severity "low" according to the Django security policy.
~django.contrib.gis.geos.GEOSGeometry was subject to a potential denial-of-service attack when provided deeply nested GEOMETRYCOLLECTION objects, leading to a segmentation fault in GEOS. A maximum depth of 198 GEOMETRYCOLLECTIONs is now enforced for the well-known text (WKT) format, and a maximum number of 198 GEOMETRYCOLLECTIONs in total (breadth and depth) is enforced for well-known binary (WKB).
Lookups against spatial fields and the ~django.contrib.gis.forms.GeometryField form field were also affected.
The limit can be customized through the new max_geom_collections argument, available on ~django.contrib.gis.geos.GEOSGeometry, the form field, and the model field. The limit is not applied to GeoJSON inputs, as they were parsed by GDAL and are not affected.
This issue has severity "moderate" according to the Django security policy.
The admin renders ~django.db.models.URLField values as clickable links on changelist views and read-only fields. The link was generated without validating the value as a safe URL, so a stored value using a potentially dangerous scheme was rendered as a link.
URLField values shown via display_for_field are now validated using ~django.core.validators.URLValidator before a link is rendered, and displayed as plain text if validation is failed.
This issue has severity "moderate" according to the Django security policy.
CVE-2026-48588: Potential exposure of private data via cached Set-Cookie response
July 7, 2026
Django 5.2.16 fixes three security issues with severity "low" in 5.2.15.
~django.middleware.cache.UpdateCacheMiddleware and ~django.views.decorators.cache.cache_page avoided caching responses that set a cookie while varying on Cookie only when the incoming request contained no cookies at all. When the request already carried an unrelated cookie (such as a language or theme preference cookie), the protection did not apply, allowing a response that sets a session or other sensitive cookie to be stored in Django's shared cache.
This issue has severity "low" according to the Django security policy.
When ~django.contrib.gis.gdal.GDALRaster was instantiated with a bytes object representing a raster file, the ~django.contrib.gis.gdal.GDALRaster.vsi_buffer property could over-read the allocated buffer by approximately 32 bytes. This could result in information disclosure of adjacent heap memory or, in rare cases, a segmentation fault. Only rasters stored in GDAL's virtual filesystem were affected.
This issue has severity "low" according to the Django security policy.
~django.core.validators.DomainNameValidator accepted newlines in domain names. If such values were included in HTTP responses, header injection attacks were possible. Django itself wasn't vulnerable because ~django.http.HttpResponse prohibits newlines in HTTP headers.
The vulnerability only affected uses of DomainNameValidator outside Django form fields, as CharField strips newlines by default.
This issue has severity "low" according to the Django security policy.
CVE-2026-6873: Signed cookie salt namespace collision
June 3, 2026
Django 5.2.15 fixes five security issues with severity "low" in 5.2.14.
~django.http.HttpRequest.get_signed_cookie derived the signing salt by concatenating the cookie name (key) and salt arguments. When distinct name and salt pairs produced the same concatenation, cookies could be accepted in a context different from the one where they were signed.
Cookies are now signed with an unambiguous salt derivation. For backwards compatibility, cookies signed by older Django versions are accepted until Django 2028. Projects affected by the above ambiguity should set SIGNED_COOKIE_LEGACY_SALT_FALLBACK to False to reject older cookies immediately.
This issue has severity "low" according to the Django security policy.
When using EMAIL_USE_TLS, a failed STARTTLS handshake could leave a partially-initialized connection that would subsequently be reused for sending email without encryption. This can occur with fail_silently=True, as used by ~django.core.mail.send_mail and ~django.middleware.common.BrokenLinkEmailsMiddleware, among others. Connections configured with EMAIL_USE_SSL are not affected.
This issue has severity "low" according to the Django security policy.
~django.middleware.cache.UpdateCacheMiddleware and ~django.views.decorators.cache.cache_page incorrectly cached responses marked with private Cache-Control directives when using mixed or uppercase values (e.g. Private).
The ~django.views.decorators.cache.cache_control decorator and ~django.utils.cache.patch_cache_control function were not affected, since they normalize directives to lowercase. This issue only affects responses where Cache-Control is set manually.
This issue has severity "low" according to the Django security policy.
~django.middleware.cache.UpdateCacheMiddleware and ~django.views.decorators.cache.cache_page decorator allowed responses to requests bearing an Authorization header (and without Cache-Control: public) to be cached. To conform with the existing mechanism for constructing cache keys, responses to these requests will now vary on Authorization.
This issue has severity "low" according to the Django security policy.
~django.middleware.cache.UpdateCacheMiddleware incorrectly cached responses whose Vary header values contained leading or trailing whitespace. Because has_vary_header() failed to strip that, a Vary: * header value with surrounding whitespace was not recognized as containing the wildcard, causing it to be stored and potentially served from the cache when it should not have been.
This issue has severity "low" according to the Django security policy.
CVE-2026-5766: Potential denial-of-service vulnerability in ASGI requests via file upload limit bypass
May 5, 2026
Django 5.2.14 fixes three security issues with severity "low" in 5.2.13.
ASGI requests with a missing or understated Content-Length header could bypass the FILE_UPLOAD_MAX_MEMORY_SIZE limit, potentially loading large files into memory and causing service degradation.
As a reminder, Django expects a limit to be configured at the web server level rather than solely relying on FILE_UPLOAD_MAX_MEMORY_SIZE.
This issue has severity "low" according to the Django security policy.
Response headers did not vary on cookies if a session was not modified, but SESSION_SAVE_EVERY_REQUEST was True. A remote attacker could steal a user's session after that user visits a cached public page.
This issue has severity "low" according to the Django security policy.
Previously, ~django.middleware.cache.UpdateCacheMiddleware would erroneously cache requests where the Vary header contained an asterisk ('*'). This could lead to private data being stored and served.
This issue has severity "low" according to the Django security policy.
CVE-2026-3902: ASGI header spoofing via underscore/hyphen conflation
April 7, 2026
Django 5.2.13 fixes one security issue with severity "moderate" and four security issues with severity "low" in 5.2.12.
ASGIRequest normalizes header names following WSGI conventions, mapping hyphens to underscores. As a result, even in configurations where reverse proxies carefully strip security-sensitive headers named with hyphens, such a header could be spoofed by supplying a header named with underscores.
Under WSGI, it is the responsibility of the server or proxy to avoid ambiguous mappings. (Django's runserver was patched in 2015-0219.) But under ASGI, there is not the same uniform expectation, even if many proxies protect against this under default configuration (including nginx via underscores_in_headers off;).
Headers containing underscores are now ignored by ASGIRequest, matching the behavior of Daphne, the reference server for ASGI.
This issue has severity "low" according to the Django security policy.
Add permissions on inline model instances were not validated on submission of forged POST data in ~django.contrib.contenttypes.admin.GenericInlineModelAdmin.
This issue has severity "low" according to the Django security policy.
Admin changelist forms using ~django.contrib.admin.ModelAdmin.list_editable incorrectly allowed new instances to be created via forged POST data.
This issue has severity "low" according to the Django security policy.
When using django.http.multipartparser.MultiPartParser, multipart uploads with Content-Transfer-Encoding: base64 that include excessive whitespace may trigger repeated memory copying, potentially degrading performance.
This issue has severity "moderate" according to the Django security policy.
ASGI requests with a missing or understated Content-Length header could bypass the DATA_UPLOAD_MAX_MEMORY_SIZE limit when reading HttpRequest.body, potentially loading an unbounded request body into memory and causing service degradation.
This issue has severity "low" according to the Django security policy.
CVE-2026-25673: Potential denial-of-service vulnerability in URLField via Unicode normalization on Windows
March 3, 2026
Django 5.2.12 fixes a security issue with severity "moderate" and a security issue with severity "low" in 5.2.11. It also fixes one bug related to support for Python 3.14.
The ~django.forms.URLField form field's to_python() method used ~urllib.parse.urlsplit to determine whether to prepend a URL scheme to the submitted value. On Windows, urlsplit() performs NFKC normalization, which can be disproportionately slow for large inputs containing certain characters.
URLField.to_python() now uses a simplified scheme detection, avoiding Unicode normalization entirely and deferring URL validation to the appropriate layers. As a result, while leading and trailing whitespace is still stripped by default, characters such as newlines, tabs, and other control characters within the value are no longer handled by URLField.to_python(). When using the default ~django.core.validators.URLValidator, these values will continue to raise ~django.core.exceptions.ValidationError during validation, but if you rely on custom validators, ensure they do not depend on the previous behavior of URLField.to_python().
This issue has severity "moderate" according to the Django security policy.
Django's file-system storage and file-based cache backends used the process umask to control permissions when creating directories. In multi-threaded environments, one thread's temporary umask change can affect other threads' file and directory creation, resulting in file system objects being created with unintended permissions.
Django now applies the requested permissions via ~os.chmod after ~os.mkdir, removing the dependency on the process-wide umask.
This issue has severity "low" according to the Django security policy.
Fixed NameError when inspecting functions making use of deferred annotations in Python 3.14 (36903).
CVE-2025-13473: Username enumeration through timing difference in mod_wsgi authentication handler
February 3, 2026
Django 5.2.11 fixes three security issues with severity "high", two security issues with severity "moderate", and one security issue with severity "low" in 5.2.10.
The django.contrib.auth.handlers.modwsgi.check_password() function for authentication via mod_wsgi allowed remote attackers to enumerate users via a timing attack.
This issue has severity "low" according to the Django security policy.
When receiving duplicates of a single header, ASGIRequest allowed a remote attacker to cause a potential denial-of-service via a specifically created request with multiple duplicate headers. The vulnerability resulted from repeated string concatenation while combining repeated headers, which produced super-linear computation resulting in service degradation or outage.
This issue has severity "moderate" according to the Django security policy.
Raster lookups on GIS fields (only implemented on PostGIS) were subject to SQL injection if untrusted data was used as a band index.
As a reminder, all untrusted user input should be validated before use.
This issue has severity "high" according to the Django security policy.
django.utils.text.Truncator.chars() and Truncator.words() methods (with html=True) and the truncatechars_html and truncatewords_html template filters were subject to a potential denial-of-service attack via certain inputs with a large number of unmatched HTML end tags, which could cause quadratic time complexity during HTML parsing.
This issue has severity "moderate" according to the Django security policy.
.FilteredRelation was subject to SQL injection in column aliases via control characters, using a suitably crafted dictionary, with dictionary expansion, as the **kwargs passed to .QuerySet.annotate, ~.QuerySet.aggregate, ~.QuerySet.extra, ~.QuerySet.values, ~.QuerySet.values_list, and ~.QuerySet.alias.
This issue has severity "high" according to the Django security policy.
.QuerySet.order_by was subject to SQL injection in column aliases containing periods when the same alias was, using a suitably crafted dictionary, with dictionary expansion, used in .FilteredRelation.
This issue has severity "high" according to the Django security policy.
Django 5.2.10 fixes a data loss bug introduced in Django 5.2 and one bug related to support for Python 3.14.
January 6, 2026
Django 5.2.10 fixes a data loss bug introduced in Django 5.2 and one bug related to support for Python 3.14.
Fixed a bug in Django 5.2 where data exceeding max_length was silently truncated by .QuerySet.bulk_create on PostgreSQL (33647).
Fixed a bug where management command colorized help (introduced in Python 3.14) ignored the --no-color option and the DJANGO_COLORS setting (36376).
CVE-2025-13372: Potential SQL injection in FilteredRelation column aliases on PostgreSQL
December 2, 2025
Django 5.2.9 fixes one security issue with severity "high", one security issue with severity "moderate", and several bugs in 5.2.8.
.FilteredRelation was subject to SQL injection in column aliases, using a suitably crafted dictionary, with dictionary expansion, as the **kwargs passed to .QuerySet.annotate or .QuerySet.alias on PostgreSQL.
XML Serialization was subject to a potential denial-of-service attack due to quadratic time complexity when deserializing crafted documents containing many nested invalid elements. The internal helper django.core.serializers.xml_serializer.getInnerText() previously accumulated inner text inefficiently during recursion. It now collects text per element, avoiding excessive resource usage.
Fixed a bug in Django 5.2 where django.utils.feedgenerator.Stylesheet.__str__() did not escape the url, mimetype, and media attributes, potentially leading to invalid XML markup (36733).
Fixed a bug in Django 5.2 on PostgreSQL where bulk_create() did not apply a field's custom query placeholders (36748).
Fixed a regression in Django 5.2.2 that caused a crash when using aggregate functions with an empty Q filter over a queryset with annotations (36751).
Fixed a regression in Django 5.2.8 where DisallowedRedirect was raised by ~django.http.HttpResponseRedirect and ~django.http.HttpResponsePermanentRedirect for URLs longer than 2048 characters. The limit is now 16384 characters (36743).
Fixed a crash on Python 3.14+ that prevented template tag functions from being registered when their type annotations required deferred evaluation (36712).
CVE-2025-64458: Potential denial-of-service vulnerability in HttpResponseRedirect and HttpResponsePermanentRedirect on Windows
November 5, 2025
Django 5.2.8 fixes one security issue with severity "high", one security issue with severity "moderate", and several bugs in 5.2.7. It also adds compatibility with Python 3.14.
Python's NFKC normalization is slow on Windows. As a consequence, ~django.http.HttpResponseRedirect, ~django.http.HttpResponsePermanentRedirect, and the shortcut redirect() were subject to a potential denial-of-service attack via certain inputs with a very large number of Unicode characters (follow up to 2025-27556).
.QuerySet.filter, ~.QuerySet.exclude, ~.QuerySet.get, and ~.Q were subject to SQL injection using a suitably crafted dictionary, with dictionary expansion, as the _connector argument.
Added compatibility for oracledb 3.4.0 (36646).
Fixed a bug in Django 5.2 where QuerySet.first() and QuerySet.last() raised an error on querysets performing aggregation that selected all fields of a composite primary key (36648).
Fixed a bug in Django 5.2 where proxy models having a CompositePrimaryKey incorrectly raised a models.E042 system check error (36704).
CVE-2025-59681: Potential SQL injection in QuerySet.annotate(), alias(), aggregate(), and extra() on MySQL and MariaDB
October 1, 2025
Django 5.2.7 fixes one security issue with severity "high", one security issue with severity "low", and one bug in 5.2.6. Also, the latest string translations from Transifex are incorporated.
.QuerySet.annotate, ~.QuerySet.alias, ~.QuerySet.aggregate, and ~.QuerySet.extra methods were subject to SQL injection in column aliases, using a suitably crafted dictionary, with dictionary expansion, as the **kwargs passed to these methods (follow up to 2022-28346).
The django.utils.archive.extract() function, used by startapp --template and startproject --template, allowed partial directory-traversal via an archive with file paths sharing a common prefix with the target directory (follow up to 2021-3281).
Fixed a regression in Django 5.2 that reduced the color contrast of the chosen label of filter_horizontal and filter_vertical widgets within a TabularInline (36601).
CVE-2025-57833: Potential SQL injection in FilteredRelation column aliases
September 3, 2025
Django 5.2.6 fixes a security issue with severity "high" and one bug in 5.2.5.
.FilteredRelation was subject to SQL injection in column aliases, using a suitably crafted dictionary, with dictionary expansion, as the **kwargs passed to .QuerySet.annotate or .QuerySet.alias.
Fixed a bug where using QuerySet.values() or values_list() with a ForeignObject composed of multiple fields returned incorrect results instead of tuples of the referenced fields (36431).
Django 5.2.5 fixes several bugs in 5.2.4.
August 6, 2025
Django 5.2.5 fixes several bugs in 5.2.4.
Fixed a regression in Django 5.2.1 that prevented the usage of UNNEST
PostgreSQL strategy of QuerySet.bulk_create() with foreign keys
(:ticket:36502).
Fixed a crash in Django 5.2 when filtering against a composite primary key
using a tuple containing expressions (:ticket:36522).
Fixed a crash in Django 5.2 when validating a model that uses
GeneratedField or constraints composed of Q and Case lookups
(:ticket:36518).
Added compatibility for docutils 0.22 (:ticket:36535).
Fixed a crash in Django 5.2 when using a ManyToManyField on a model with
a composite primary key, by extending the fields.E347 system check
(:ticket:36530).
Django 5.2.4 fixes several bugs in 5.2.3.
July 2, 2025
Django 5.2.4 fixes several bugs in 5.2.3.
Fixed a regression in Django 5.2.2 where HttpRequest.get_preferred_type() incorrectly preferred more specific media types with a lower quality (36447).
Fixed a regression in Django 5.2.3 where Value(None, JSONField()) used in a ~django.db.models.expressions.When condition was incorrectly serialized as SQL NULL instead of JSON null (36453).
Fixed a crash in Django 5.2 when performing an __in lookup involving a composite primary key and a subquery on backends that lack native support for tuple lookups (36464).
Django 5.2.3 fixes several bugs in 5.2.2. Also, the latest string translations from Transifex are incorporated.
June 10, 2025
Django 5.2.3 fixes several bugs in 5.2.2. Also, the latest string translations from Transifex are incorporated.
Fixed a log injection possibility by migrating remaining response logging to django.utils.log.log_response(), which safely escapes arguments such as the request path to prevent unsafe log output (2025-48432).
Fixed a regression in Django 5.2 that caused .QuerySet.bulk_update to incorrectly convert None to JSON null instead of SQL NULL for JSONField (36419).
Fixed a regression in Django 5.2.2 where the q parameter was removed from the internal django.http.MediaType.params property (36446).
CVE-2025-48432: Potential log injection via unescaped request path
June 4, 2025
Django 5.2.2 fixes a security issue with severity "low" and several bugs in 5.2.1.
Internal HTTP response logging used request.path directly, allowing control characters (e.g. newlines or ANSI escape sequences) to be written unescaped into logs. This could enable log injection or forgery, letting attackers manipulate log appearance or structure, especially in logs processed by external systems or viewed in terminals.
Although this does not directly impact Django's security model, it poses risks when logs are consumed or interpreted by other tools. To fix this, the internal django.utils.log.log_response() function now escapes all positional formatting arguments using a safe encoding.
Fixed a crash when using select_related against a ForeignObject originating from a model with a CompositePrimaryKey (36373).
Fixed a bug in Django 5.2 where subqueries using "pk" to reference models with a CompositePrimaryKey failed to raise ValueError when too many or too few columns were selected (36392).
Fixed a regression in Django 5.2 that caused a crash when no arguments were passed into QuerySet.union() (36388).
Fixed a regression in Django 5.2 where subclasses of RemoteUserMiddleware that had overridden process_request() were no longer supported (36390).
Fixed a regression in Django 5.2 that caused a crash when using OuterRef in the filter argument of an Aggregate expression (36404).
Fixed a regression in Django 5.2 that caused a crash when using OuterRef in PostgreSQL aggregate functions ArrayAgg, StringAgg, and JSONBAgg (36405).
Fixed a regression in Django 5.2 where admin's filter_horizontal buttons lacked type="button", causing them to intercept form submission when pressing the Enter key (36423).
Fixed a bug in Django 5.2 where calling QuerySet.in_bulk() with an id_list argument on models with a CompositePrimaryKey failed to observe database parameter limits (36416).
Fixed a bug in Django 5.2 where HttpRequest.get_preferred_type() did not account for media type parameters in Accept headers, reducing specificity in content negotiation (36411).
Fixed a regression in Django 5.2 that caused a crash when using QuerySet.prefetch_related() to prefetch a foreign key with a Prefetch queryset for a subclass of the foreign target (36432).
CVE-2025-32873: Denial-of-service possibility in strip_tags()
May 7, 2025
Django 5.2.1 fixes a security issue with severity "moderate" and several bugs in 5.2.
This release was built using an upgraded setuptools, producing filenames compliant with 491 and 625 and thus addressing a PyPI warning about non-compliant distribution filenames. This change only affects the Django packaging process and does not impact Django's behavior.
~django.utils.html.strip_tags would be slow to evaluate certain inputs containing large sequences of incomplete HTML tags. This function is used to implement the striptags template filter, which was thus also vulnerable.
~django.utils.html.strip_tags now raises a .SuspiciousOperation exception if it encounters an unusually large number of unclosed opening tags.
Fixed a regression in Django 5.2 that caused a crash when annotating aggregate expressions over query that uses explicit grouping by transforms followed by field references (36292).
Fixed a regression in Django 5.2 that caused unnecessary queries when prefetching nullable foreign key relationships (36290).
Fixed a regression in Django 5.2 that caused a crash of QuerySet.bulk_create() with nullable geometry fields on PostGIS (36289).
Fixed a regression in Django 5.2 that caused fields to be incorrectly selected when using QuerySet.alias() after values() (36299).
Fixed a data corruption possibility in file_move_safe() when allow_overwrite=True, where leftover content from a previously larger file could remain after overwriting with a smaller one due to lack of truncation (36298).
Fixed a regression in Django 5.2 that caused a crash when using QuerySet.select_for_update(of=(…)) with values()/values_list() including expressions (36301).
Fixed a regression in Django 5.2 that caused improper values to be returned from QuerySet.values_list() when duplicate field names were specified (36288).
Fixed a regression in Django 5.2 where the password validation error message from MinimumLengthValidator was not translated when using non-English locales (36314).
Fixed a regression in Django 5.2 that caused the object-tools block to be rendered twice when using custom admin templates with overridden blocks due to changes in the base admin page block structure (36331).
Fixed a regression in Django 5.2, introduced when fixing 2025-26699, where the wordwrap template filter did not preserve empty lines between paragraphs after wrapping text (36341).
Fixed a regression in Django 5.2 that caused a crash when serializing email alternatives or attachments due to named tuple mismatches (36309).
Fixed a regression in Django 5.2 that caused a crash when using update() on a QuerySet filtered against a related model and including references to annotations through values() (36360).
Fixed a bug in Django 5.2 that caused composite primary key introspection to wrongly identify IntegerField as AutoField on SQLite (36358).
Fixed a bug in Django 5.2 that caused a redundant unique_together constraint to be generated for composite primary keys when using inspectdb (36357).
- Django 5.2.18 release notes - Django 5.2.17 release notes - Django 5.2.16 release notes - Django 5.2.15 release notes - Django 5.2.14 release notes
Django 5.2.18 release notes
Django 5.2.17 release notes
Django 5.2.16 release notes
Django 5.2.15 release notes
Django 5.2.14 release notes
Django 5.2.13 release notes
Django 5.2.12 release notes
Django 5.2.11 release notes
Django 5.2.10 release notes
Django 5.2.9 release notes
Django 5.2.8 release notes
Django 5.2.7 release notes
Django 5.2.6 release notes
Django 5.2.5 release notes
Django 5.2.4 release notes
Django 5.2.3 release notes
Django 5.2.2 release notes
Django 5.2.1 release notes
Django 5.2 release notes
April 2, 2025
Welcome to Django 5.2!
These release notes cover the new features, as well as some backwards incompatible changes you should be aware of when upgrading from Django 5.1 or earlier. We've begun the deprecation process for some features.
See the /howto/upgrade-version guide if you're updating an existing project.
Django 5.2 is designated as a long-term support release. It will receive security updates for at least three years after its release. Support for the previous LTS, Django 4.2, will end in April 2026.
Django 5.2 supports Python 3.10, 3.11, 3.12, 3.13, and 3.14 (as of 5.2.8). We highly recommend and only officially support the latest release of each series.
The shell management command now automatically imports models from all installed apps. You can view further details of the imported objects by setting the --verbosity flag to 2 or more:
This behavior can be customized to add or remove automatic imports.
The new django.db.models.CompositePrimaryKey allows tables to be created with a primary key consisting of multiple fields.
To use a composite primary key, when defining a model set the pk attribute to be a CompositePrimaryKey:
from django.db import models
class Release(models.Model):
pk = models.CompositePrimaryKey("version", "name")
version = models.IntegerField()
name = models.CharField(max_length=20)
See /topics/composite-primary-key for more details.
Prior to version 5.2, overriding .Field.get_bound_field was the only option to use a custom ~django.forms.BoundField. Django now supports specifying the following attributes to customize form rendering:
.BaseRenderer.bound_field_class at the project level,
.Form.bound_field_class at the form level, and
.Field.bound_field_class at the field level.
For example, to customize the BoundField of a Form class:
from django import forms
class CustomBoundField(forms.BoundField):
custom_class = "custom"
def css_classes(self, extra_classes=None):
result = super().css_classes(extra_classes)
if self.custom_class not in result:
result += f" {self.custom_class}"
return result.strip()
class CustomForm(forms.Form):
bound_field_class = CustomBoundField
name = forms.CharField(
label="Your Name",
max_length=100,
required=False,
widget=forms.TextInput(attrs={"class": "name-input-class"}),
)
email = forms.EmailField(label="Your Email")
When rendering a CustomForm instance, the following HTML is included:
<div class="custom">
<label for="id_name">Your Name:</label>
<input type="text" name="name" class="name-input-class" maxlength="100" id="id_name">
</div>
<div class="custom">
<label for="id_email">Your Email:</label>
<input type="email" name="email" maxlength="320" required="" id="id_email">
</div>
See custom-boundfield for more details about this feature.
The admin/base.html template now has a new block extrabody for adding custom code before the closing </body> tag.
The value of a ~django.db.models.URLField now renders as a link.
Links to components in docstrings now supports custom link text, using the format link text. See documentation helpers for more details.
The model pages are now restricted to users with the corresponding view or change permissions.
The default iteration count for the PBKDF2 password hasher is increased from 870,000 to 1,000,000.
The following new asynchronous methods are now provided, using an a prefix:
.UserManager.acreate_user
.UserManager.acreate_superuser
.BaseUserManager.aget_by_natural_key
.User.aget_user_permissions
.User.aget_all_permissions
.User.aget_group_permissions
.User.ahas_perm
.User.ahas_perms
.User.ahas_module_perms
.ModelBackend.aauthenticate
.ModelBackend.aget_user_permissions
.ModelBackend.aget_group_permissions
.ModelBackend.aget_all_permissions
.ModelBackend.ahas_perm
.ModelBackend.ahas_module_perms
.RemoteUserBackend.aauthenticate
.RemoteUserBackend.aconfigure_user
Auth backends can now provide async implementations which are used when calling async auth functions (e.g. ~.django.contrib.auth.aauthenticate) to reduce context-switching which improves performance. See adding an async interface for more details.
The password validator classes now have a new method get_error_message(), which can be overridden in subclasses to customize the error messages.
GDAL now supports curved geometries CurvePolygon, CompoundCurve, CircularString, MultiSurface, and MultiCurve via the new .OGRGeometry.has_curve property, and the .OGRGeometry.get_linear_geometry and .OGRGeometry.get_curve_geometry methods.
coveredby and covers lookup are now supported on MySQL.
All ~django.utils.feedgenerator.SyndicationFeed classes now support a stylesheets attribute. If specified, an <? xml-stylesheet ?> processing instruction will be added to the top of the document for each stylesheet in the given list. See feed-stylesheets for more details.
MySQL connections now default to using the utf8mb4 character set, instead of utf8, which is an alias for the deprecated character set utf8mb3.
Oracle backends now support connection pools, by setting "pool" in the OPTIONS part of your database configuration.
~django.utils.decorators.method_decorator now supports wrapping asynchronous view methods.
Tuple items of EmailMessage.attachments and EmailMultiAlternatives.attachments are now named tuples, as opposed to regular tuples.
EmailMultiAlternatives.alternatives is now a list of named tuples, as opposed to regular tuples.
The new ~django.core.mail.EmailMultiAlternatives.body_contains method returns a boolean indicating whether a provided text is contained in the email body and in all attached MIME type text/* alternatives.
The attribute .SafeExceptionReporterFilter.hidden_settings now treats values as sensitive if their name includes AUTH.
The new ~django.forms.ColorInput form widget is for entering a color in rrggbb hexadecimal format and renders as <input type="color" ...>. Some browsers support a visual color picker interface for this input type.
The new ~django.forms.SearchInput form widget is for entering search queries and renders as <input type="search" ...>.
The new ~django.forms.TelInput form widget is for entering telephone numbers and renders as <input type="tel" ...>.
The new field_id argument for ~django.forms.ErrorList allows an HTML id attribute to be added in the error template. See .ErrorList.field_id for details.
An ~django.forms.BoundField.aria_describedby property is added to BoundField to ease use of this HTML attribute in templates.
To improve accessibility for screen reader users aria-describedby is used to associate form fields with their error messages. See how form errors are displayed for details.
The new asset object ~django.forms.Script is available for adding custom HTML-attributes to JavaScript in form media. See paths as objects for more details.
A new warning is displayed when running runserver, indicating that it is unsuitable for production. This warning can be suppressed by setting the DJANGO_RUNSERVER_HIDE_WARNING environment variable to "true".
The makemigrations and migrate commands have a new Command.autodetector attribute for subclasses to override in order to use a custom autodetector class.
The new .BaseCommand.get_check_kwargs method can be overridden in custom commands to control the running of system checks, e.g. to opt into database-dependent checks.
The new operation .AlterConstraint is a no-op operation that alters constraints without dropping and recreating constraints in the database.
The SELECT clause generated when using .QuerySet.values and .QuerySet.values_list now matches the specified order of the referenced expressions. Previously, the order was based on a set of counterintuitive rules which made query combination through methods such as .QuerySet.union unpredictable.
Added support for validation of model constraints which use a ~django.db.models.GeneratedField.
The new .Expression.set_returning attribute specifies that the expression contains a set-returning function, enforcing subquery evaluation. This is necessary for many Postgres set-returning functions.
CharField.max_length is no longer required to be set on SQLite, which supports unlimited VARCHAR columns.
.QuerySet.explain now supports the memory and serialize options on PostgreSQL 17+.
The new ~django.db.models.functions.JSONArray database function accepts a list of field names or expressions and returns a JSON array containing those values.
The new .Expression.allows_composite_expressions attribute specifies that the expression allows composite expressions, for example, to support composite primary keys.
The new .HttpResponse.text property provides the string representation of .HttpResponse.content.
The new .HttpRequest.get_preferred_type method can be used to query the preferred media type the client accepts.
The new preserve_request argument for ~django.http.HttpResponseRedirect and ~django.http.HttpResponsePermanentRedirect determines whether the HTTP status codes 302/307 or 301/308 are used, respectively.
The new preserve_request argument for ~django.shortcuts.redirect allows to instruct the user agent to reuse the HTTP method and body during redirection using specific status codes.
Each serialization format now defines a Deserializer class, rather than a function, to improve extensibility when defining a custom serialization format.
The new ~django.template.Library.simple_block_tag decorator enables the creation of simple block tags, which can accept and use a section of the template.
Stack frames from Django's custom assertions are now hidden. This makes test failures easier to read and enables test --pdb to directly enter into the failing test method.
Data loaded from ~django.test.TransactionTestCase.fixtures and from migrations enabled with serialized_rollback=True are now available during TransactionTestCase.setUpClass().
~django.urls.reverse and ~django.urls.reverse_lazy now accept query and fragment keyword arguments, allowing the addition of a query string and/or fragment identifier in the generated URL, respectively.
~django.utils.safestring.SafeString now returns NotImplemented in __add__ for non-string right-hand side values. This aligns with the str addition behavior and allows __radd__ to be used if available.
~django.utils.html.format_html_join now supports taking an iterable of mappings, passing their contents as keyword arguments to ~django.utils.html.format_html.
This section describes changes that may be needed in third-party database backends.
The new Model._is_pk_set() method allows checking if a Model instance's primary key is defined.
BaseDatabaseOperations.adapt_decimalfield_value() is now a no-op, simply returning the given value.
Support for PostGIS 3.0 is removed.
Support for GDAL 3.0 is removed.
Upstream support for PostgreSQL 13 ends in November 2025. Django 5.2 supports PostgreSQL 14 and higher.
MySQL connections now default to using the utf8mb4 character set, instead of utf8, which is an alias for the deprecated character set utf8mb3. utf8mb3 can be specified in the OPTIONS part of the DATABASES setting, if needed for legacy databases.
Adding .EmailMultiAlternatives.alternatives is now only supported via the ~.EmailMultiAlternatives.attach_alternative method.
The minimum supported version of gettext is increased from 0.15 to 0.19.
HttpRequest.accepted_types is now sorted by the client's preference, based on the request's Accept header.
The attributes .UniqueConstraint.violation_error_code and .UniqueConstraint.violation_error_message are now always used when provided. Previously, they were ignored if .UniqueConstraint.fields was set without a .UniqueConstraint.condition.
The ~django.template.context_processors.debug context processor is no longer included in the default project template.
The following methods now have alters_data=True set to prevent side effects when rendering a template context:
.UserManager.create_user
.UserManager.acreate_user
.UserManager.create_superuser
.UserManager.acreate_superuser
.QuerySet.create
.QuerySet.acreate
.QuerySet.bulk_create
.QuerySet.abulk_create
.QuerySet.get_or_create
.QuerySet.aget_or_create
.QuerySet.update_or_create
.QuerySet.aupdate_or_create
The minimum supported version of oracledb is increased from 1.3.2 to 2.3.0.
Built-in aggregate functions accepting only one argument (Avg, Count, Max, Min, StdDev, Sum, and Variance) now raise TypeError when called with an incorrect number of arguments.
The all argument for the django.contrib.staticfiles.finders.find() function is deprecated in favor of the find_all argument.
Fallbacks to request.user and request.auser() when user is None in django.contrib.auth.login() and django.contrib.auth.alogin(), respectively, are deprecated.
The ordering keyword argument of the PostgreSQL specific aggregation functions django.contrib.postgres.aggregates.ArrayAgg, django.contrib.postgres.aggregates.JSONBAgg, and django.contrib.postgres.aggregates.StringAgg is deprecated in favor of the order_by argument.
Support for subclasses of RemoteUserMiddleware that override process_request() without overriding aprocess_request() is deprecated.
Nothing published for this version
Nothing published for this version
Nothing published for this version
CVE-2025-13372: Potential SQL injection in FilteredRelation column aliases on PostgreSQL
December 2, 2025
Django 5.1.15 fixes one security issue with severity "high", one security issue with severity "moderate", and one bug in 5.1.14.
.FilteredRelation was subject to SQL injection in column aliases, using a suitably crafted dictionary, with dictionary expansion, as the **kwargs passed to .QuerySet.annotate or .QuerySet.alias on PostgreSQL.
XML Serialization was subject to a potential denial-of-service attack due to quadratic time complexity when deserializing crafted documents containing many nested invalid elements. The internal helper django.core.serializers.xml_serializer.getInnerText() previously accumulated inner text inefficiently during recursion. It now collects text per element, avoiding excessive resource usage.
Fixed a regression in Django 5.1.14 where DisallowedRedirect was raised by ~django.http.HttpResponseRedirect and ~django.http.HttpResponsePermanentRedirect for URLs longer than 2048 characters. The limit is now 16384 characters (36743).
CVE-2025-64458: Potential denial-of-service vulnerability in HttpResponseRedirect and HttpResponsePermanentRedirect on Windows
November 5, 2025
Django 5.1.14 fixes one security issue with severity "high" and one security issue with severity "moderate" in 5.1.13.
Python's NFKC normalization is slow on Windows. As a consequence, ~django.http.HttpResponseRedirect, ~django.http.HttpResponsePermanentRedirect, and the shortcut redirect() were subject to a potential denial-of-service attack via certain inputs with a very large number of Unicode characters (follow up to 2025-27556).
.QuerySet.filter, ~.QuerySet.exclude, ~.QuerySet.get, and ~.Q were subject to SQL injection using a suitably crafted dictionary, with dictionary expansion, as the _connector argument.
CVE-2025-59681: Potential SQL injection in QuerySet.annotate(), alias(), aggregate(), and extra() on MySQL and MariaDB
October 1, 2025
Django 5.1.13 fixes one security issue with severity "high" and one security issue with severity "low" in 5.1.12.
.QuerySet.annotate, ~.QuerySet.alias, ~.QuerySet.aggregate, and ~.QuerySet.extra methods were subject to SQL injection in column aliases, using a suitably crafted dictionary, with dictionary expansion, as the **kwargs passed to these methods (follow up to 2022-28346).
The django.utils.archive.extract() function, used by startapp --template and startproject --template, allowed partial directory-traversal via an archive with file paths sharing a common prefix with the target directory (follow up to 2021-3281).
CVE-2025-57833: Potential SQL injection in FilteredRelation column aliases
September 3, 2025
Django 5.1.12 fixes a security issue with severity "high" in 5.1.11.
.FilteredRelation was subject to SQL injection in column aliases, using a suitably crafted dictionary, with dictionary expansion, as the **kwargs passed to .QuerySet.annotate or .QuerySet.alias.
Django 5.1.11 fixes a potential log injection issue in 5.1.10.
June 10, 2025
Django 5.1.11 fixes a potential log injection issue in 5.1.10.
django.utils.log.log_response(), which safely escapes arguments such
as the request path to prevent unsafe log output (:cve:2025-48432).CVE-2025-48432: Potential log injection via unescaped request path
June 4, 2025
Django 5.1.10 fixes a security issue with severity "low" in 5.1.9.
Internal HTTP response logging used request.path directly, allowing control
characters (e.g. newlines or ANSI escape sequences) to be written unescaped
into logs. This could enable log injection or forgery, letting attackers
manipulate log appearance or structure, especially in logs processed by
external systems or viewed in terminals.
Although this does not directly impact Django's security model, it poses risks
when logs are consumed or interpreted by other tools. To fix this, the internal
django.utils.log.log_response() function now escapes all positional
formatting arguments using a safe encoding.
CVE-2025-32873: Denial-of-service possibility in strip_tags()
May 7, 2025
Django 5.1.9 fixes a security issue with severity "moderate", a data loss bug, and a regression in 5.1.8.
This release was built using an upgraded setuptools, producing filenames compliant with 491 and 625 and thus addressing a PyPI warning about non-compliant distribution filenames. This change only affects the Django packaging process and does not impact Django's behavior.
~django.utils.html.strip_tags would be slow to evaluate certain inputs containing large sequences of incomplete HTML tags. This function is used to implement the striptags template filter, which was thus also vulnerable.
~django.utils.html.strip_tags now raises a .SuspiciousOperation exception if it encounters an unusually large number of unclosed opening tags.
Fixed a data corruption possibility in file_move_safe() when allow_overwrite=True, where leftover content from a previously larger file could remain after overwriting with a smaller one due to lack of truncation (36298).
Fixed a regression in Django 5.1.8, introduced when fixing 2025-26699, where the wordwrap template filter did not preserve empty lines between paragraphs after wrapping text (36341).
CVE-2025-27556: Potential denial-of-service vulnerability in LoginView, LogoutView, and set_language() on Windows
April 2, 2025
Django 5.1.8 fixes a security issue with severity "moderate" and several bugs in 5.1.7.
Python's NFKC normalization is slow on Windows. As a consequence, ~django.contrib.auth.views.LoginView, ~django.contrib.auth.views.LogoutView, and ~django.views.i18n.set_language were subject to a potential denial-of-service attack via certain inputs with a very large number of Unicode characters.
Fixed a regression in Django 5.1.7 where the removal of the single_object parameter unintentionally altered the signature and return type of LogEntryManager.log_actions() (36234).
CVE-2025-26699: Potential denial-of-service vulnerability in django.utils.text.wrap()
March 6, 2025
Django 5.1.7 fixes a security issue with severity "moderate" and several bugs in 5.1.6.
django.utils.text.wrap()The wrap() and :tfilter:wordwrap template filter were subject to a
potential denial-of-service attack when used with very long strings.
Fixed a bug in Django 5.1 where the {% querystring %} template tag
returned an empty string rather than "?" when all parameters had been
removed from the query string (:ticket:36182).
Fixed a bug in Django 5.1 where FileSystemStorage, with
allow_overwrite set to True, did not truncate the overwritten file
content (:ticket:36191).
Fixed a regression in Django 5.1 where the count and exists methods
of ManyToManyField related managers would always return 0 and
False when the intermediary model back references used to_field
(:ticket:36197).
Fixed a regression in Django 5.1 where the pre_save and post_save
signals for LogEntry were not sent when deleting a single object in the
admin (:ticket:36217).
Django 5.1.6 fixes several bugs in 5.1.5.
February 5, 2025
Django 5.1.6 fixes several bugs in 5.1.5.
Fixed a regression in Django 5.1.5 that caused validate_ipv6_address() and validate_ipv46_address() to crash when handling non-string values (36098).
Fixed a regression in Django 5.1 where password fields, despite being set to required=False, were still treated as required in forms derived from ~django.contrib.auth.forms.BaseUserCreationForm (36140).
CVE-2024-56374: Potential denial-of-service vulnerability in IPv6 validation
January 14, 2025
Django 5.1.5 fixes a security issue with severity "moderate" and one bug in 5.1.4.
Lack of upper bound limit enforcement in strings passed when performing IPv6 validation could lead to a potential denial-of-service attack. The undocumented and private functions clean_ipv6_address and is_valid_ipv6_address were vulnerable, as was the django.forms.GenericIPAddressField form field, which has now been updated to define a max_length of 39 characters.
The django.db.models.GenericIPAddressField model field was not affected.
Fixed a crash when applying migrations with references to the removed Meta.index_together option (34856).
CVE-2024-53907: Denial-of-service possibility in strip_tags()
December 4, 2024
Django 5.1.4 fixes one security issue with severity "high", one security issue with severity "moderate", and several bugs in 5.1.3.
~django.utils.html.strip_tags would be extremely slow to evaluate certain inputs containing large sequences of nested incomplete HTML entities. The strip_tags() method is used to implement the corresponding striptags template filter, which was thus also vulnerable.
strip_tags() now has an upper limit of recursive calls to HTMLParser before raising a .SuspiciousOperation exception.
Remember that absolutely NO guarantee is provided about the results of strip_tags() being HTML safe. So NEVER mark safe the result of a strip_tags() call without escaping it first, for example with django.utils.html.escape.
Direct usage of the django.db.models.fields.json.HasKey lookup on Oracle was subject to SQL injection if untrusted data was used as a lhs value.
Applications that use the has_key lookup through the __ syntax are unaffected.
Fixed a crash in createsuperuser on Python 3.13+ caused by an unhandled OSError when the username could not be determined (35942).
Fixed a regression in Django 5.1 where relational fields were not updated when calling Model.refresh_from_db() on instances with deferred fields (35950).
Django 5.1.3 fixes several bugs in 5.1.2 and adds compatibility with Python 3.13.
November 5, 2024
Django 5.1.3 fixes several bugs in 5.1.2 and adds compatibility with Python 3.13.
Fixed a bug in Django 5.1 where ~django.core.validators.DomainNameValidator accepted any input value that contained a valid domain name, rather than only input values that were a valid domain name (35845).
Fixed a regression in Django 5.1 that prevented the use of DB-IP databases with ~django.contrib.gis.geoip2.GeoIP2 (35841).
Fixed a regression in Django 5.1 where non-ASCII fieldset names were not displayed when rendering admin fieldsets (35876).
Django 5.1.2 fixes several bugs in 5.1.1. Also, the latest string translations from Transifex are incorporated.
October 8, 2024
Django 5.1.2 fixes several bugs in 5.1.1. Also, the latest string translations from Transifex are incorporated.
Fixed a regression in Django 5.1 that caused a crash when using the
PostgreSQL lookup :lookup:trigram_similar on output fields from Concat
(:ticket:35732).
Fixed a regression in Django 5.1 that caused a crash of JSONObject()
when using server-side binding with PostgreSQL 16+ (:ticket:35734).
Fixed a regression in Django 5.1 that made selected items in multi-select
widgets indistinguishable from non-selected items in the admin dark theme
(:ticket:35809).
CVE-2024-45230: Potential denial-of-service vulnerability in django.utils.html.urlize()
September 3, 2024
Django 5.1.1 fixes one security issue with severity "moderate", one security issue with severity "low", and several bugs in 5.1.
urlize and urlizetrunc were subject to a potential denial-of-service attack via very large inputs with a specific sequence of characters.
Due to unhandled email sending failures, the ~django.contrib.auth.forms.PasswordResetForm class allowed remote attackers to enumerate user emails by issuing password reset requests and observing the outcomes.
To mitigate this risk, exceptions occurring during password reset email sending are now handled and logged using the django-contrib-auth-logger logger.
Fixed a regression in Django 5.1 that caused a crash of Window() when passing an empty sequence to the order_by parameter, and a crash of Prefetch() for a sliced queryset without ordering (35665).
Fixed a regression in Django 5.1 where a new usable_password field was included in ~django.contrib.auth.forms.BaseUserCreationForm (and children). A new ~django.contrib.auth.forms.AdminUserCreationForm including this field was added, isolating the feature to the admin where it was intended (35678).
Adjusted the deprecation warning stacklevel in .Model.save and .Model.asave to correctly point to the offending call site (35060).
Adjusted the deprecation warning stacklevel when using OS_OPEN_FLAGS in ~django.core.files.storage.FileSystemStorage to correctly point to the offending call site (35326).
Adjusted the deprecation warning stacklevel in FieldCacheMixin.get_cache_name() to correctly point to the offending call site (35405).
Restored, following a regression in Django 5.1, the ability to override the timezone and role setting behavior used within the init_connection_state method of the PostgreSQL backend (35688).
Fixed a bug in Django 5.1 where variable lookup errors were logged when rendering admin fieldsets (35716).
- Django 5.1.15 release notes - Django 5.1.14 release notes - Django 5.1.13 release notes - Django 5.1.12 release notes - Django 5.1.11 release notes
Django 5.1.15 release notes
Django 5.1.14 release notes
Django 5.1.13 release notes
Django 5.1.12 release notes
Django 5.1.11 release notes
Django 5.1.10 release notes
Django 5.1.9 release notes
Django 5.1.8 release notes
Django 5.1.7 release notes
Django 5.1.6 release notes
Django 5.1.5 release notes
Django 5.1.4 release notes
Django 5.1.3 release notes
Django 5.1.2 release notes
Django 5.1.1 release notes
Django 5.1 release notes
August 7, 2024
Welcome to Django 5.1!
These release notes cover the new features, as well as some backwards incompatible changes you should be aware of when upgrading from Django 5.0 or earlier. We've begun the deprecation process for some features.
See the /howto/upgrade-version guide if you're updating an existing project.
Django 5.1 supports Python 3.10, 3.11, 3.12, and 3.13 (as of 5.1.3). We highly recommend and only officially support the latest release of each series.
Django 5.1 introduces the {% querystring %} template tag, simplifying the modification of query parameters in URLs, making it easier to generate links that maintain existing query parameters while adding or changing specific ones.
For instance, navigating pagination and query strings in templates can be cumbersome. Consider this template fragment that dynamically generates a URL for navigating to the next page within a paginated view:
{# Linebreaks added for readability, this should be one, long line. #}
<a href="?{% for key, values in request.GET.iterlists %}
{% if key != "page" %}
{% for value in values %}
{{ key }}={{ value }}&
{% endfor %}
{% endif %}
{% endfor %}page={{ page.next_page_number }}">Next page</a>
When switching to using this new template tag, the above magically becomes:
<a href="{% querystring page=page.next_page_number %}">Next page</a>
Django 5.1 also introduces connection pool support for PostgreSQL. As the time to establish a new connection can be relatively long, keeping connections open can reduce latency.
To use a connection pool with psycopg, you can set the "pool" option inside OPTIONS to be a dict to be passed to ~psycopg:psycopg_pool.ConnectionPool, or to True to use the ConnectionPool defaults:
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
# ...
"OPTIONS": {
"pool": {
"min_size": 2,
"max_size": 4,
"timeout": 10,
}
},
},
}
The new ~django.contrib.auth.middleware.LoginRequiredMiddleware redirects all unauthenticated requests to a login page. Views can allow unauthenticated requests by using the new ~django.contrib.auth.decorators.login_not_required decorator.
LoginRequiredMiddleware respects the login_url and redirect_field_name values set via the ~.django.contrib.auth.decorators.login_required decorator, but does not support setting login_url or redirect_field_name via the ~django.contrib.auth.mixins.LoginRequiredMixin.
To enable this, add "django.contrib.auth.middleware.LoginRequiredMiddleware" to your MIDDLEWARE setting.
.ModelAdmin.list_display now supports using __ lookups to list fields from related models.
The default iteration count for the PBKDF2 password hasher is increased from 720,000 to 870,000.
The default parallelism of the ScryptPasswordHasher is increased from 1 to 5, to follow OWASP recommendations.
The new ~django.contrib.auth.forms.AdminUserCreationForm and the existing ~django.contrib.auth.forms.AdminPasswordChangeForm now support disabling password-based authentication by setting an unusable password on form save. This is now available in the admin when visiting the user creation and password change pages.
~.django.contrib.auth.decorators.login_required, ~.django.contrib.auth.decorators.permission_required, and ~.django.contrib.auth.decorators.user_passes_test decorators now support wrapping asynchronous view functions.
ReadOnlyPasswordHashWidget now includes a button to reset the user's password, which replaces the link previously embedded in the ReadOnlyPasswordHashField's help text, improving the overall accessibility of the ~django.contrib.auth.forms.UserChangeForm.
~django.contrib.gis.db.models.functions.BoundingCircle is now supported on SpatiaLite 5.1+.
~django.contrib.gis.db.models.Collect is now supported on MySQL 8.0.24+.
~django.contrib.gis.geoip2.GeoIP2 now allows querying using ipaddress.IPv4Address or ipaddress.IPv6Address objects.
.GeoIP2.country now exposes the continent_code, continent_name, and is_in_european_union values.
.GeoIP2.city now exposes the accuracy_radius and region_name values. In addition, the dma_code and region values are now exposed as metro_code and region_code, but the previous keys are also retained for backward compatibility.
~django.contrib.gis.measure.Area now supports the ha unit.
The new .OGRGeometry.is_3d attribute allows checking if a geometry has a Z coordinate dimension.
The new .OGRGeometry.set_3d method allows addition and removal of the Z coordinate dimension.
~django.contrib.gis.gdal.OGRGeometry, ~django.contrib.gis.gdal.Point, ~django.contrib.gis.gdal.LineString, ~django.contrib.gis.gdal.Polygon, and ~django.contrib.gis.gdal.GeometryCollection and its subclasses now support measured geometries via the new .OGRGeometry.is_measured and m properties, and the .OGRGeometry.set_measured method.
.OGRGeometry.centroid is now available on all supported geometry types.
FromWKB() and FromWKT() functions now support the optional srid argument (except for Oracle where it is ignored).
~django.contrib.postgres.indexes.BTreeIndex now supports the deduplicate_items parameter.
django.contrib.sessions.backends.cached_db.SessionStore now handles exceptions when storing session information in the cache, logging proper error messages with their traceback via the newly added sessions logger.
django.contrib.sessions.backends.base.SessionBase and all built-in session engines now provide async API. The new asynchronous methods all have a prefixed names, e.g. aget(), akeys(), or acycle_key().
"init_command" option is now supported in OPTIONS on SQLite to allow specifying pragma options to set upon connection.
"transaction_mode" option is now supported in OPTIONS on SQLite to allow specifying the sqlite-transaction-behavior.
"pool" option is now supported in OPTIONS on PostgreSQL to allow using connection pools.
In order to improve accessibility, the technical 404 and 500 error pages now use HTML landmark elements for the header, footer, and main content areas.
The ~django.core.files.storage.FileSystemStorage.allow_overwrite parameter of ~django.core.files.storage.FileSystemStorage now allows saving new files over existing ones.
In order to improve accessibility and enable screen readers to associate fieldsets with their help text, the form fieldset now includes the aria-describedby HTML attribute.
The makemigrations command now displays meaningful symbols for each operation to highlight operation categories.
The new Operation.category attribute allows specifying an operation category used by the makemigrations to display a meaningful symbol for the operation.
.QuerySet.explain now supports the generic_plan option on PostgreSQL 16+.
~django.db.models.expressions.RowRange now accepts positive integers for the start argument and negative integers for the end argument.
The new exclusion argument of ~django.db.models.expressions.RowRange and ~django.db.models.expressions.ValueRange allows excluding rows, groups, and ties from the window frames.
.QuerySet.order_by now supports ordering by annotation transforms such as JSONObject keys and ArrayAgg indices.
F() and OuterRef() expressions that output ~django.db.models.CharField, ~django.db.models.EmailField, ~django.db.models.SlugField, ~django.db.models.URLField, ~django.db.models.TextField, or ~django.contrib.postgres.fields.ArrayField can now be sliced.
The new from_queryset argument of .Model.refresh_from_db and .Model.arefresh_from_db allows customizing the queryset used to reload a model's value. This can be used to lock the row before reloading or to select related objects.
The new .Expression.constraint_validation_compatible attribute allows specifying that the expression should be ignored during a constraint validation.
Custom tags may now set extra data on the Parser object that will later be made available on the Template instance. Such data may be used, for example, by the template loader, or other template clients.
Template engines now implement a check() method that is already registered with the check framework.
~django.test.SimpleTestCase.assertContains, ~django.test.SimpleTestCase.assertNotContains, and ~django.test.SimpleTestCase.assertInHTML assertions now add haystacks to assertion error messages.
The ~django.test.RequestFactory, ~django.test.AsyncRequestFactory, ~django.test.Client, and ~django.test.AsyncClient classes now support the query_params parameter, which accepts a dictionary of query string keys and values. This allows setting query strings on any HTTP methods more easily.
self.client.post("/items/1", query_params={"action": "delete"})
await self.async_client.post("/items/1", query_params={"action": "delete"})
The new .SimpleTestCase.assertNotInHTML assertion allows testing that an HTML fragment is not contained in the given HTML haystack.
In order to enforce test isolation, database connections inside threads are no longer allowed in ~django.test.SimpleTestCase.
The new ~django.core.validators.DomainNameValidator validates domain names, including internationalized domain names. The new ~django.core.validators.validate_domain_name function returns an instance of ~django.core.validators.DomainNameValidator.
Support for PostGIS 2.5 is removed.
Support for PROJ < 6 is removed.
Support for GDAL 2.4 is removed.
~django.contrib.gis.geoip2.GeoIP2 no longer opens both city and country databases when a directory path is provided, preferring the city database, if it is available. The country database is a subset of the city database and both are not typically needed. If you require use of the country database when in the same directory as the city database, explicitly pass the country database path to the constructor.
Upstream support for MariaDB 10.4 ends in June 2024. Django 5.1 supports MariaDB 10.5 and higher.
Upstream support for PostgreSQL 12 ends in November 2024. Django 5.1 supports PostgreSQL 13 and higher.
In order to improve accessibility, the admin's changelist filter is now rendered in a <nav> tag instead of a <div>.
In order to improve accessibility, the admin's footer is now rendered in a <footer> tag instead of a <div>, and also moved below the <div id="main"> element.
In order to improve accessibility, the expandable widget used for ModelAdmin.fieldsets and InlineModelAdmin.fieldsets, when the fieldset has a name and use the collapse class, now includes <details> and <summary> elements.
The JavaScript file collapse.js is removed since it is no longer needed in the Django admin site.
.SimpleTestCase.assertURLEqual and ~django.test.SimpleTestCase.assertInHTML now add ": " to the msg_prefix. This is consistent with the behavior of other assertions.
django.utils.text.Truncator used by truncatechars_html and truncatewords_html template filters now uses html.parser.HTMLParser subclasses. This results in a more robust and faster operation, but there may be small differences in the output.
The undocumented django.urls.converters.get_converter() function is removed.
The minimum supported version of SQLite is increased from 3.27.0 to 3.31.0.
~django.db.models.FileField now raises a ~django.core.exceptions.FieldError when saving a file without a name.
ImageField.update_dimension_fields(force=True) is no longer called after saving the image to storage. If your storage backend resizes images, the width_field and height_field will not match the width and height of the image.
The minimum supported version of asgiref is increased from 3.7.0 to 3.8.1.
To improve performance, the delete_selected admin action now uses QuerySet.bulk_create() when creating multiple LogEntry objects. As a result, pre_save and post_save signals for LogEntry are not sent when multiple objects are deleted via this admin action.
The ModelAdmin.log_deletion() and LogEntryManager.log_action() methods are deprecated. Subclasses should implement ModelAdmin.log_deletions() and LogEntryManager.log_actions() instead.
The undocumented django.utils.itercompat.is_iterable() function and the django.utils.itercompat module are deprecated. Use isinstance(..., collections.abc.Iterable) instead.
The django.contrib.gis.geoip2.GeoIP2.coords() method is deprecated. Use django.contrib.gis.geoip2.GeoIP2.lon_lat() instead.
The django.contrib.gis.geoip2.GeoIP2.open() method is deprecated. Use the ~django.contrib.gis.geoip2.GeoIP2 constructor instead.
Passing positional arguments to .Model.save and .Model.asave is deprecated in favor of keyword-only arguments.
Setting django.contrib.gis.gdal.OGRGeometry.coord_dim is deprecated. Use ~django.contrib.gis.gdal.OGRGeometry.set_3d instead.
Overriding existing converters with django.urls.register_converter() is deprecated.
The check keyword argument of CheckConstraint is deprecated in favor of condition.
The undocumented OS_OPEN_FLAGS property of ~django.core.files.storage.FileSystemStorage is deprecated. To allow overwriting files in storage, set the new ~django.core.files.storage.FileSystemStorage.allow_overwrite option to True instead.
The get_cache_name() method of FieldCacheMixin is deprecated in favor of the cache_name cached property.
These features have reached the end of their deprecation cycle and are removed in Django 5.1.
See deprecated-features-4.2 for details on these changes, including how to remove usage of these features.
The BaseUserManager.make_random_password() method is removed.
The model's Meta.index_together option is removed.
The length_is template filter is removed.
The django.contrib.auth.hashers.SHA1PasswordHasher, django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher, and django.contrib.auth.hashers.UnsaltedMD5PasswordHasher are removed.
The model django.contrib.postgres.fields.CICharField, django.contrib.postgres.fields.CIEmailField, and django.contrib.postgres.fields.CITextField are removed, except for support in historical migrations.
The django.contrib.postgres.fields.CIText mixin is removed.
The map_width and map_height attributes of BaseGeometryWidget are removed.
The SimpleTestCase.assertFormsetError() method is removed.
The TransactionTestCase.assertQuerysetEqual() method is removed.
Support for passing encoded JSON string literals to JSONField and associated lookups and expressions is removed.
Support for passing positional arguments to Signer and TimestampSigner is removed.
The DEFAULT_FILE_STORAGE and STATICFILES_STORAGE settings is removed.
The django.core.files.storage.get_storage_class() function is removed.
Nothing published for this version
Nothing published for this version
Nothing published for this version
CVE-2025-27556: Potential denial-of-service vulnerability in LoginView, LogoutView, and set_language() on Windows
April 2, 2025
Django 5.0.14 fixes a security issue with severity "moderate" in 5.0.13.
Python's NFKC normalization is slow on Windows. As a consequence, ~django.contrib.auth.views.LoginView, ~django.contrib.auth.views.LogoutView, and ~django.views.i18n.set_language were subject to a potential denial-of-service attack via certain inputs with a very large number of Unicode characters.
CVE-2025-26699: Potential denial-of-service vulnerability in django.utils.text.wrap()
March 6, 2025
Django 5.0.13 fixes a security issue with severity "moderate" in 5.0.12.
django.utils.text.wrap()The wrap() and :tfilter:wordwrap template filter were subject to a
potential denial-of-service attack when used with very long strings.
Django 5.0.12 fixes a regression in 5.0.11.
February 5, 2025
Django 5.0.12 fixes a regression in 5.0.11.
validate_ipv6_address()
and validate_ipv46_address() to crash when handling non-string values
(:ticket:36098).Your coding agent can read these notes before it upgrades. Set up the MCP server →