Django 4.1 release notes¶
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.
Python compatibility¶
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.
What’s new in Django 4.1¶
Asynchronous handlers for class-based views¶
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.
Asynchronous ORM interface¶
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.
Validation of Constraints¶
Check
,
unique
, and exclusion
constraints defined
in the Meta.constraints
option
are now checked during model validation.
Form rendering accessibility¶
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.
Minor features¶
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 forGEOSGeometry.normalize()
allows creating a normalized clone of the geometry.
django.contrib.postgres
¶
The new
BitXor()
aggregate function returns anint
of the bitwiseXOR
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 ofDateTimeRangeField
andDecimalRangeField
allows specifying bounds for list and tuple inputs.ExclusionConstraint
now allows specifying operator classes with theOpClass()
expression.
django.contrib.sitemaps
¶
The default sitemap index template
<sitemapindex>
now includes the<lastmod>
timestamp where available, through the newget_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.
Database backends¶
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 implementDatabaseWrapper.get_database_version()
, which returns a tuple of the current database version. The backend’sDatabaseWrapper.init_connection_state()
method must callsuper()
in order for the check to run.
Forms¶
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 settingform_template_name
on the class provided forFORM_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, referencingForm.template_name_div
attribute, and matchingForm.as_div()
method, render forms using HTML<div>
elements.This new output style is recommended over the existing
as_table()
,as_p()
andas_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
anddjango.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. TheFORM_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 specifyingform_template_name
andformset_template_name
for your required style, and setFORM_RENDERER
accordingly.For example, for the
<p>
output style used byas_p()
, you would define a form renderer settingform_template_name
to"django/forms/p.html"
andformset_template_name
to"django/forms/formsets/p.html"
.The new
legend_tag()
allows rendering field labels in<legend>
tags via the newtag
argument oflabel_tag()
.The new
edit_only
argument formodelformset_factory()
andinlineformset_factory()
allows preventing new objects creation.The
js
andcss
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 thehtml_safe()
decorator).The new
BoundField.use_fieldset
andWidget.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
, andDecimalField
now optionally accept astep_size
argument. This is used to set thestep
HTML attribute, and is validated on form submission.
Internationalization¶
The
i18n_patterns()
function now supports languages with both scripts and regions.
Management Commands¶
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 tostderr
, writing only paths of generated migration files tostdout
.The new
migrate --prune
option allows deleting nonexistent migrations from thedjango_migrations
table.Python files created by
startproject
,startapp
,optimizemigration
,makemigrations
, andsquashmigrations
are now formatted using theblack
command if it is present on yourPATH
.The new
optimizemigration
command allows optimizing operations for a migration.
Migrations¶
The new
RenameIndex
operation allows renaming indexes defined in theMeta.indexes
orindex_together
options.The migrations autodetector now generates
RenameIndex
operations instead ofRemoveIndex
andAddIndex
, when renaming indexes defined in theMeta.indexes
.The migrations autodetector now generates
RenameIndex
operations instead ofAlterIndexTogether
andAddIndex
, when moving indexes defined in theMeta.index_together
to theMeta.indexes
.
Models¶
The
order_by
argument of theWindow
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 thechunk_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 supportXOR
, the query will be converted to an equivalent usingAND
,OR
, andNOT
.The new Field.non_db_attrs attribute allows customizing attributes of fields that don’t affect a column definition.
On PostgreSQL,
AutoField
,BigAutoField
, andSmallAutoField
are now created as identity columns rather than serial columns with sequences.
Requests and Responses¶
HttpResponse.set_cookie()
now supportstimedelta
objects for themax_age
argument.
Security¶
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.
Signals¶
The
pre_delete
andpost_delete
signals now dispatch theorigin
of the deletion.
Templates¶
The HTML
<script>
elementid
attribute is no longer required when wrapping thejson_script
template filter.The
cached template loader
is now enabled in development, whenDEBUG
isTrue
, andOPTIONS['loaders']
isn’t specified. You may specifyOPTIONS['loaders']
to override this, if necessary.
Tests¶
The
DiscoverRunner
now supports running tests in parallel on macOS, Windows, and any other systems where the defaultmultiprocessing
start method isspawn
.A nested atomic block marked as durable in
django.test.TestCase
now raises aRuntimeError
, the same as outside of tests.SimpleTestCase.assertFormError()
andassertFormsetError()
now support passing a form/formset object directly.
URLs¶
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.
Utilities¶
SimpleLazyObject
now supports addition operations.mark_safe()
now preserves lazy objects.
Validators¶
The new
StepValueValidator
checks if a value is an integral multiple of a given step size. This new validator is used for the newstep_size
argument added to form fields representing numeric values.
Backwards incompatible changes in 4.1¶
Database backend API¶
This section describes changes that may be needed in third-party database backends.
BaseDatabaseFeatures.has_case_insensitive_like
is changed fromTrue
toFalse
to reflect the behavior of most databases.DatabaseIntrospection.get_key_columns()
is removed. UseDatabaseIntrospection.get_relations()
instead.DatabaseOperations.ignore_conflicts_suffix_sql()
method is replaced byDatabaseOperations.on_conflict_suffix_sql()
that accepts thefields
,on_conflict
,update_fields
, andunique_fields
arguments.The
ignore_conflicts
argument of theDatabaseOperations.insert_statement()
method is replaced byon_conflict
that acceptsdjango.db.models.constants.OnConflict
.DatabaseOperations._convert_field_to_tz()
is replaced byDatabaseOperations._convert_sql_to_tz()
that accepts thesql
,params
, andtzname
arguments.Several date and time methods on
DatabaseOperations
now takesql
andparams
arguments instead offield_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.
Dropped support for PostgreSQL 10¶
Upstream support for PostgreSQL 10 ends in November 2022. Django 4.1 supports PostgreSQL 11 and higher.
Dropped support for MariaDB 10.2¶
Upstream support for MariaDB 10.2 ends in May 2022. Django 4.1 supports MariaDB 10.3 and higher.
Admin changelist searches spanning multi-valued relationships changes¶
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.
Reverse foreign key changes for unsaved model instances¶
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.
Miscellaneous¶
Related managers for
ForeignKey
,ManyToManyField
, andGenericRelation
are now cached on theModel
instance to which they belong. This change was reverted in Django 4.1.2.DiscoverRunner
now returns a non-zero error code for unexpected successes from tests marked withunittest.expectedFailure()
.CsrfViewMiddleware
no longer masks the CSRF cookie like it does the CSRF token in the DOM.CsrfViewMiddleware
now usesrequest.META['CSRF_COOKIE']
for storing the unmasked CSRF secret rather than a masked version. This is an undocumented, private API.The
ModelAdmin.actions
andinlines
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
andformset: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 undocumenteddjango.utils.log.log_response()
function is replaced byexception
.The
size
argument of the undocumenteddjango.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 thenon_form_errors()
method. This is consistent withBaseFormSet
.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 byRedirectURLMixin
.BaseConstraint
subclasses must implementvalidate()
method to allow those constraints to be used for validation.The undocumented
URLResolver._is_callback()
,URLResolver._callback_strs
, andURLPattern.lookup_str()
are moved todjango.contrib.admindocs.utils
.The
Model.full_clean()
method now converts anexclude
value to aset
. It’s also preferable to pass anexclude
value as aset
to theModel.clean_fields()
,Model.full_clean()
,Model.validate_unique()
, andModel.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 anoutput_field
for database functions and combined expressions may now crash with mixed types. You will need to explicitly set theoutput_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.
Features deprecated in 4.1¶
Log out via GET¶
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;
}
Miscellaneous¶
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 optionallastmod
attributes.CSRF_COOKIE_MASKED
transitional setting is deprecated.The
name
argument ofdjango.utils.functional.cached_property()
is deprecated as it’s unnecessary as of Python 3.6.The
opclasses
argument ofdjango.contrib.postgres.constraints.ExclusionConstraint
is deprecated in favor of usingOpClass()
inExclusionConstraint.expressions
. To use it, you need to add'django.contrib.postgres'
in yourINSTALLED_APPS
.After making this change,
makemigrations
will generate a new migration with two operations:RemoveConstraint
andAddConstraint
. Since this change has no effect on the database schema, theSeparateDatabaseAndState
operation can be used to only update the migration state without running any SQL. Move the generated operations into thestate_operations
argument ofSeparateDatabaseAndState
. For example:class Migration(migrations.Migration): ... operations = [ migrations.SeparateDatabaseAndState( database_operations=[], state_operations=[ migrations.RemoveConstraint(...), migrations.AddConstraint(...), ], ), ]
The undocumented ability to pass
errors=None
toSimpleTestCase.assertFormError()
andassertFormsetError()
is deprecated. Useerrors=[]
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 thechunk_size
argument is deprecated. In older versions, no prefetching was done. Providing a value forchunk_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 ofRemoteUserBackend.configure_user()
. Support forRemoteUserBackend
subclasses that do not accept this argument is deprecated.The
django.utils.timezone.utc
alias todatetime.timezone.utc
is deprecated. Usedatetime.timezone.utc
directly.Passing a response object and a form/formset name to
SimpleTestCase.assertFormError()
andassertFormsetError()
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
ornulls_last=False
toExpression.asc()
andExpression.desc()
methods, and theOrderBy
expression is deprecated. UseNone
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 toget_success_url()
.
Features removed in 4.1¶
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 inTestCase.setUpTestData()
is removed.Support for using a boolean value in
BaseCommand.requires_system_checks
is removed.The
whitelist
argument anddomain_whitelist
attribute ofdjango.core.validators.EmailValidator
are removed.The
default_app_config
application configuration variable is removed.TransactionTestCase.assertQuerysetEqual()
no longer callsrepr()
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.