August 3, 2022
Welcome to Django 4.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 4.0 or earlier. We've begun the deprecation process for some features.
See the How to upgrade Django to a newer version guide if you're updating an existing project.
Django 4.1 supports Python 3.8, 3.9, 3.10, and 3.11 (as of 4.1.3). We highly recommend and only officially support the latest release of each series.
View subclasses may now define async HTTP method handlers:
import asyncio
from django.http import HttpResponse
from django.views import View
class AsyncView(View):
async def get(self, request, *args, **kwargs):
# Perform view logic using await.
await asyncio.sleep(1)
return HttpResponse("Hello async world!")
See Asynchronous class-based views for more details.
QuerySet
now provides an asynchronous interface for all data access
operations. These are named as-per the existing synchronous operations but with
an a
prefix, for example acreate()
, aget()
, and so on.
The new interface allows you to write asynchronous code without needing to wrap
ORM operations in sync_to_async()
:
async for author in Author.objects.filter(name__startswith="A"):
book = await author.books.afirst()
Note that, at this stage, the underlying database operations remain
synchronous, with contributions ongoing to push asynchronous support down into
the SQL compiler, and integrate asynchronous database drivers. The new
asynchronous queryset interface currently encapsulates the necessary
sync_to_async()
operations for you, and will allow your code to take
advantage of developments in the ORM's asynchronous support as it evolves.
See Asynchronous queries for details and limitations.
Check
,
unique
, and exclusion
constraints defined
in the Meta.constraints
option
are now checked during model validation.
In order to aid users with screen readers, and other assistive technology, new
<div>
based form templates are available from this release. These provide
more accessible navigation than the older templates, and are able to correctly
group related controls, such as radio-lists, into fieldsets.
The new templates are recommended, and will become the default form rendering
style when outputting a form, like {{ form }}
in a template, from Django
5.0.
In order to ease adopting the new output style, the default form and formset
templates are now configurable at the project level via the
FORM_RENDERER
setting.
See the Forms section (below) for full details.
django.contrib.admin
¶The admin dark mode CSS variables are now applied in a separate stylesheet and template block.
ModelAdmin List Filters providing custom FieldListFilter
subclasses can now control the query string value separator when filtering
for multiple values using the __in
lookup.
The admin history view
is now paginated.
Related widget wrappers now have a link to object's change form.
The AdminSite.get_app_list()
method now allows changing the order of
apps and models on the admin index page.
django.contrib.auth
¶The default iteration count for the PBKDF2 password hasher is increased from 320,000 to 390,000.
The RemoteUserBackend.configure_user()
method now allows synchronizing
user attributes with attributes in a remote system such as an LDAP directory.
django.contrib.gis
¶The new GEOSGeometry.make_valid()
method allows converting invalid
geometries to valid ones.
The new clone
argument for GEOSGeometry.normalize()
allows
creating a normalized clone of the geometry.
django.contrib.postgres
¶The new BitXor()
aggregate function returns an int
of the bitwise XOR
of all non-null
input values.
SpGistIndex
now supports covering
indexes on PostgreSQL 14+.
ExclusionConstraint
now
supports covering exclusion constraints using SP-GiST indexes on PostgreSQL
14+.
The new default_bounds
attribute of DateTimeRangeField
and
DecimalRangeField
allows
specifying bounds for list and tuple inputs.
ExclusionConstraint
now allows
specifying operator classes with the
OpClass()
expression.
django.contrib.sitemaps
¶The default sitemap index template <sitemapindex>
now includes the
<lastmod>
timestamp where available, through the new
get_latest_lastmod()
method. Custom
sitemap index templates should be updated for the adjusted context
variables.
django.contrib.staticfiles
¶ManifestStaticFilesStorage
now
replaces paths to CSS source map references with their hashed counterparts.
Third-party database backends can now specify the minimum required version of
the database using the DatabaseFeatures.minimum_database_version
attribute which is a tuple (e.g. (10, 0)
means "10.0"). If a minimum
version is specified, backends must also implement
DatabaseWrapper.get_database_version()
, which returns a tuple of the
current database version. The backend's
DatabaseWrapper.init_connection_state()
method must call super()
in
order for the check to run.
The default template used to render forms when cast to a string, e.g. in
templates as {{ form }}
, is now configurable at the project-level by
setting form_template_name
on
the class provided for FORM_RENDERER
.
Form.template_name
is now a property deferring to the renderer, but
may be overridden with a string value to specify the template name per-form
class.
Similarly, the default template used to render formsets can be specified via
the matching
formset_template_name
renderer
attribute.
The new div.html
form template, referencing
Form.template_name_div
attribute, and matching Form.as_div()
method, render forms using HTML <div>
elements.
This new output style is recommended over the existing
as_table()
, as_p()
and as_ul()
styles,
as the template implements <fieldset>
and <legend>
to group related
inputs and is easier for screen reader users to navigate.
The div-based output will become the default rendering style from Django 5.0.
In order to smooth adoption of the new <div>
output style, two
transitional form renderer classes are available:
django.forms.renderers.DjangoDivFormRenderer
and
django.forms.renderers.Jinja2DivFormRenderer
, for the Django and Jinja2
template backends respectively.
You can apply one of these via the FORM_RENDERER
setting. For
example:
FORM_RENDERER = "django.forms.renderers.DjangoDivFormRenderer"
Once the <div>
output style is the default, from Django 5.0, these
transitional renderers will be deprecated, for removal in Django 6.0. The
FORM_RENDERER
declaration can be removed at that time.
If the new <div>
output style is not appropriate for your project, you should
define a renderer subclass specifying
form_template_name
and
formset_template_name
for your
required style, and set FORM_RENDERER
accordingly.
For example, for the <p>
output style used by as_p()
, you
would define a form renderer setting form_template_name
to
"django/forms/p.html"
and formset_template_name
to
"django/forms/formsets/p.html"
.
The new legend_tag()
allows rendering field
labels in <legend>
tags via the new tag
argument of
label_tag()
.
The new edit_only
argument for modelformset_factory()
and
inlineformset_factory()
allows preventing new objects creation.
The js
and css
class attributes of Media
now allow using hashable objects, not only path strings, as long as those
objects implement the __html__()
method (typically when decorated with
the html_safe()
decorator).
The new BoundField.use_fieldset
and Widget.use_fieldset
attributes help to identify widgets where its inputs should be grouped in a
<fieldset>
with a <legend>
.
The error_messages argument for
BaseFormSet
now allows customizing
error messages for invalid number of forms by passing 'too_few_forms'
and 'too_many_forms'
keys.
IntegerField
, FloatField
, and
DecimalField
now optionally accept a step_size
argument. This is used to set the step
HTML attribute, and is validated
on form submission.
The i18n_patterns()
function now supports
languages with both scripts and regions.
makemigrations --no-input
now logs default answers and reasons why
migrations cannot be created.
The new makemigrations --scriptable
option diverts log output and
input prompts to stderr
, writing only paths of generated migration files
to stdout
.
The new migrate --prune
option allows deleting nonexistent
migrations from the django_migrations
table.
Python files created by startproject
, startapp
,
optimizemigration
, makemigrations
, and
squashmigrations
are now formatted using the black
command if
it is present on your PATH
.
The new optimizemigration
command allows optimizing operations for
a migration.
The new RenameIndex
operation
allows renaming indexes defined in the
Meta.indexes
or
index_together
options.
The migrations autodetector now generates
RenameIndex
operations instead of
RemoveIndex
and AddIndex
, when renaming indexes defined in the
Meta.indexes
.
The migrations autodetector now generates
RenameIndex
operations instead of
AlterIndexTogether
and AddIndex
, when moving indexes defined in the
Meta.index_together
to the
Meta.indexes
.
The order_by
argument of the
Window
expression now accepts string
references to fields and transforms.
The new CONN_HEALTH_CHECKS
setting allows enabling health checks
for persistent database connections
in order to reduce the number of failed requests, e.g. after database server
restart.
QuerySet.bulk_create()
now supports updating fields when a row
insertion fails uniqueness constraints. This is supported on MariaDB, MySQL,
PostgreSQL, and SQLite 3.24+.
QuerySet.iterator()
now supports prefetching related objects as long
as the chunk_size
argument is provided. In older versions, no prefetching
was done.
Q
objects and querysets can now be combined using
^
as the exclusive or (XOR
) operator. XOR
is natively supported
on MariaDB and MySQL. For databases that do not support XOR
, the query
will be converted to an equivalent using AND
, OR
, and NOT
.
The new Field.non_db_attrs attribute allows customizing attributes of fields that don't affect a column definition.
On PostgreSQL, AutoField
, BigAutoField
, and SmallAutoField
are
now created as identity columns rather than serial columns with sequences.
HttpResponse.set_cookie()
now supports timedelta
objects for the max_age
argument.
The new SECRET_KEY_FALLBACKS
setting allows providing a list of
values for secret key rotation.
The SECURE_PROXY_SSL_HEADER
setting now supports a comma-separated
list of protocols in the header value.
The pre_delete
and
post_delete
signals now dispatch the
origin
of the deletion.
The HTML <script>
element id
attribute is no longer required when
wrapping the json_script
template filter.
The cached template loader
is now enabled in development, when DEBUG
is True
, and
OPTIONS['loaders']
isn't specified. You may
specify OPTIONS['loaders']
to override this, if necessary.
The DiscoverRunner
now supports running tests in parallel on
macOS, Windows, and any other systems where the default
multiprocessing
start method is spawn
.
A nested atomic block marked as durable in django.test.TestCase
now
raises a RuntimeError
, the same as outside of tests.
SimpleTestCase.assertFormError()
and
assertFormsetError()
now support passing a form/formset object directly.
The new ResolverMatch.captured_kwargs
attribute stores the captured
keyword arguments, as parsed from the URL.
The new ResolverMatch.extra_kwargs
attribute stores the additional
keyword arguments passed to the view function.
SimpleLazyObject
now supports addition operations.
mark_safe()
now preserves lazy objects.
The new StepValueValidator
checks if a value
is an integral multiple of a given step size. This new validator is used for
the new step_size
argument added to form fields representing numeric
values.
This section describes changes that may be needed in third-party database backends.
BaseDatabaseFeatures.has_case_insensitive_like
is changed from True
to False
to reflect the behavior of most databases.
DatabaseIntrospection.get_key_columns()
is removed. Use
DatabaseIntrospection.get_relations()
instead.
DatabaseOperations.ignore_conflicts_suffix_sql()
method is replaced by
DatabaseOperations.on_conflict_suffix_sql()
that accepts the fields
,
on_conflict
, update_fields
, and unique_fields
arguments.
The ignore_conflicts
argument of the
DatabaseOperations.insert_statement()
method is replaced by
on_conflict
that accepts django.db.models.constants.OnConflict
.
DatabaseOperations._convert_field_to_tz()
is replaced by
DatabaseOperations._convert_sql_to_tz()
that accepts the sql
,
params
, and tzname
arguments.
Several date and time methods on DatabaseOperations
now take sql
and
params
arguments instead of field_name
and return 2-tuple containing
some SQL and the parameters to be interpolated into that SQL. The changed
methods have these new signatures:
DatabaseOperations.date_extract_sql(lookup_type, sql, params)
DatabaseOperations.datetime_extract_sql(lookup_type, sql, params, tzname)
DatabaseOperations.time_extract_sql(lookup_type, sql, params)
DatabaseOperations.date_trunc_sql(lookup_type, sql, params, tzname=None)
DatabaseOperations.datetime_trunc_sql(self, lookup_type, sql, params, tzname)
DatabaseOperations.time_trunc_sql(lookup_type, sql, params, tzname=None)
DatabaseOperations.datetime_cast_date_sql(sql, params, tzname)
DatabaseOperations.datetime_cast_time_sql(sql, params, tzname)
django.contrib.gis
¶Support for GDAL 2.1 is removed.
Support for PostGIS 2.4 is removed.
Upstream support for PostgreSQL 10 ends in November 2022. Django 4.1 supports PostgreSQL 11 and higher.
Upstream support for MariaDB 10.2 ends in May 2022. Django 4.1 supports MariaDB 10.3 and higher.
Admin changelist searches using multiple search terms are now applied in a
single call to filter()
, rather than in sequential filter()
calls.
For multi-valued relationships, this means that rows from the related model
must match all terms rather than any term. For example, if search_fields
is set to ['child__name', 'child__age']
, and a user searches for
'Jamal 17'
, parent rows will be returned only if there is a relationship to
some 17-year-old child named Jamal, rather than also returning parents who
merely have a younger or older child named Jamal in addition to some other
17-year-old.
See the Spanning multi-valued relationships topic for more discussion of
this difference. In Django 4.0 and earlier,
get_search_results()
followed the
second example query, but this undocumented behavior led to queries with
excessive joins.
In order to unify the behavior with many-to-many relations for unsaved model
instances, a reverse foreign key now raises ValueError
when calling
related managers
for
unsaved objects.
Related managers for ForeignKey
,
ManyToManyField
, and
GenericRelation
are now cached
on the Model
instance to which they belong. This
change was reverted in Django 4.1.2.
The Django test runner now returns a non-zero error code for unexpected
successes from tests marked with unittest.expectedFailure()
.
CsrfViewMiddleware
no longer masks the CSRF
cookie like it does the CSRF token in the DOM.
CsrfViewMiddleware
now uses
request.META['CSRF_COOKIE']
for storing the unmasked CSRF secret rather
than a masked version. This is an undocumented, private API.
The ModelAdmin.actions
and
inlines
attributes now default to an
empty tuple rather than an empty list to discourage unintended mutation.
The type="text/css"
attribute is no longer included in <link>
tags
for CSS form media.
formset:added
and formset:removed
JavaScript events are now pure
JavaScript events and don't depend on jQuery. See
Inline form events for more details on the change.
The exc_info
argument of the undocumented
django.utils.log.log_response()
function is replaced by exception
.
The size
argument of the undocumented
django.views.static.was_modified_since()
function is removed.
The admin log out UI now uses POST
requests.
The undocumented InlineAdminFormSet.non_form_errors
property is replaced
by the non_form_errors()
method. This is consistent with BaseFormSet
.
As per above, the cached template loader is now
enabled in development. You may specify OPTIONS['loaders']
to override
this, if necessary.
The undocumented django.contrib.auth.views.SuccessURLAllowedHostsMixin
mixin is replaced by RedirectURLMixin
.
BaseConstraint
subclasses must implement
validate()
method to allow those
constraints to be used for validation.
The undocumented URLResolver._is_callback()
,
URLResolver._callback_strs
, and URLPattern.lookup_str()
are
moved to django.contrib.admindocs.utils
.
The Model.full_clean()
method now converts an exclude
value to a
set
. It’s also preferable to pass an exclude
value as a set
to
the Model.clean_fields()
, Model.full_clean()
,
Model.validate_unique()
, and Model.validate_constraints()
methods.
The minimum supported version of asgiref
is increased from 3.4.1 to
3.5.2.
Combined expressions no longer use the error-prone behavior of guessing
output_field
when argument types match. As a consequence, resolving an
output_field
for database functions and combined expressions may now
crash with mixed types. You will need to explicitly set the output_field
in such cases.
The makemessages
command no longer changes .po
files when up
to date. In older versions, POT-Creation-Date
was always updated.
Logging out via GET
requests to the built-in logout view
is deprecated. Use POST
requests
instead.
If you want to retain the user experience of an HTML link, you can use a form that is styled to appear as a link:
<form id="logout-form" method="post" action="{% url 'admin:logout' %}">
{% csrf_token %}
<button type="submit">{% translate "Log out" %}</button>
</form>
#logout-form {
display: inline;
}
#logout-form button {
background: none;
border: none;
cursor: pointer;
padding: 0;
text-decoration: underline;
}
The context for sitemap index templates of a flat list of URLs is deprecated.
Custom sitemap index templates should be updated for the adjusted
context variables, expecting a list
of objects with location
and optional lastmod
attributes.
CSRF_COOKIE_MASKED
transitional setting is deprecated.
The name
argument of django.utils.functional.cached_property()
is
deprecated as it's unnecessary as of Python 3.6.
The opclasses
argument of
django.contrib.postgres.constraints.ExclusionConstraint
is deprecated in
favor of using OpClass()
in ExclusionConstraint.expressions
. To use it, you need to add
'django.contrib.postgres'
in your INSTALLED_APPS
.
After making this change, makemigrations
will generate a new
migration with two operations: RemoveConstraint
and AddConstraint
.
Since this change has no effect on the database schema,
the SeparateDatabaseAndState
operation can be used to only update the migration state without running any
SQL. Move the generated operations into the state_operations
argument of
SeparateDatabaseAndState
. For
example:
class Migration(migrations.Migration):
...
operations = [
migrations.SeparateDatabaseAndState(
database_operations=[],
state_operations=[
migrations.RemoveConstraint(...),
migrations.AddConstraint(...),
],
),
]
The undocumented ability to pass errors=None
to
SimpleTestCase.assertFormError()
and
assertFormsetError()
is deprecated. Use errors=[]
instead.
django.contrib.sessions.serializers.PickleSerializer
is deprecated due to
the risk of remote code execution.
The usage of QuerySet.iterator()
on a queryset that prefetches related
objects without providing the chunk_size
argument is deprecated. In older
versions, no prefetching was done. Providing a value for chunk_size
signifies that the additional query per chunk needed to prefetch is desired.
Passing unsaved model instances to related filters is deprecated. In Django 5.0, the exception will be raised.
created=True
is added to the signature of
RemoteUserBackend.configure_user()
. Support for RemoteUserBackend
subclasses that do not accept this argument is deprecated.
The django.utils.timezone.utc
alias to datetime.timezone.utc
is
deprecated. Use datetime.timezone.utc
directly.
Passing a response object and a form/formset name to
SimpleTestCase.assertFormError()
and assertFormsetError()
is
deprecated. Use:
assertFormError(response.context["form_name"], ...)
assertFormsetError(response.context["formset_name"], ...)
or pass the form/formset object directly instead.
The undocumented django.contrib.gis.admin.OpenLayersWidget
is deprecated.
django.contrib.auth.hashers.CryptPasswordHasher
is deprecated.
The ability to pass nulls_first=False
or nulls_last=False
to
Expression.asc()
and Expression.desc()
methods, and the OrderBy
expression is deprecated. Use None
instead.
The "django/forms/default.html"
and
"django/forms/formsets/default.html"
templates which are a proxy to the
table-based templates are deprecated. Use the specific template instead.
The undocumented LogoutView.get_next_page()
method is renamed to
get_success_url()
.
These features have reached the end of their deprecation cycle and are removed in Django 4.1.
See Features deprecated in 3.2 for details on these changes, including how to remove usage of these features.
Support for assigning objects which don't support creating deep copies with
copy.deepcopy()
to class attributes in TestCase.setUpTestData()
is
removed.
Support for using a boolean value in
BaseCommand.requires_system_checks
is removed.
The whitelist
argument and domain_whitelist
attribute of
django.core.validators.EmailValidator
are removed.
The default_app_config
application configuration variable is removed.
TransactionTestCase.assertQuerysetEqual()
no longer calls repr()
on a
queryset when compared to string values.
The django.core.cache.backends.memcached.MemcachedCache
backend is
removed.
Support for the pre-Django 3.2 format of messages used by
django.contrib.messages.storage.cookie.CookieStorage
is removed.
Jan 15, 2024