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
FieldListFiltersubclasses can now control the query string value separator when filtering for multiple values using the__inlookup.The admin
history viewis 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
cloneargument forGEOSGeometry.normalize()allows creating a normalized clone of the geometry.
django.contrib.postgres¶
The new
BitXor()aggregate function returns anintof the bitwiseXORof all non-null input values.SpGistIndexnow supports covering indexes on PostgreSQL 14+.ExclusionConstraintnow supports covering exclusion constraints using SP-GiST indexes on PostgreSQL 14+.The new
default_boundsattribute ofDateTimeRangeFieldandDecimalRangeFieldallows specifying bounds for list and tuple inputs.ExclusionConstraintnow 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¶
ManifestStaticFilesStoragenow 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_versionattribute 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_nameon the class provided forFORM_RENDERER.Form.template_nameis 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_namerenderer attribute.The new
div.htmlform template, referencingForm.template_name_divattribute, 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.DjangoDivFormRendereranddjango.forms.renderers.Jinja2DivFormRenderer, for the Django and Jinja2 template backends respectively.You can apply one of these via the
FORM_RENDERERsetting. 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_RENDERERdeclaration 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_nameandformset_template_namefor your required style, and setFORM_RENDERERaccordingly.For example, for the
<p>output style used byas_p(), you would define a form renderer settingform_template_nameto"django/forms/p.html"andformset_template_nameto"django/forms/formsets/p.html".The new
legend_tag()allows rendering field labels in<legend>tags via the newtagargument oflabel_tag().The new
edit_onlyargument formodelformset_factory()andinlineformset_factory()allows preventing new objects creation.The
jsandcssclass 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_fieldsetandWidget.use_fieldsetattributes help to identify widgets where its inputs should be grouped in a<fieldset>with a<legend>.The error_messages argument for
BaseFormSetnow allows customizing error messages for invalid number of forms by passing'too_few_forms'and'too_many_forms'keys.IntegerField,FloatField, andDecimalFieldnow optionally accept astep_sizeargument. This is used to set thestepHTML attribute, and is validated on form submission.
Internationalization¶
The
i18n_patterns()function now supports languages with both scripts and regions.
Management Commands¶
makemigrations --no-inputnow logs default answers and reasons why migrations cannot be created.The new
makemigrations --scriptableoption diverts log output and input prompts tostderr, writing only paths of generated migration files tostdout.The new
migrate --pruneoption allows deleting nonexistent migrations from thedjango_migrationstable.Python files created by
startproject,startapp,optimizemigration,makemigrations, andsquashmigrationsare now formatted using theblackcommand if it is present on yourPATH.The new
optimizemigrationcommand allows optimizing operations for a migration.
Migrations¶
The new
RenameIndexoperation allows renaming indexes defined in theMeta.indexesorindex_togetheroptions.The migrations autodetector now generates
RenameIndexoperations instead ofRemoveIndexandAddIndex, when renaming indexes defined in theMeta.indexes.The migrations autodetector now generates
RenameIndexoperations instead ofAlterIndexTogetherandAddIndex, when moving indexes defined in theMeta.index_togetherto theMeta.indexes.
Models¶
The
order_byargument of theWindowexpression now accepts string references to fields and transforms.The new
CONN_HEALTH_CHECKSsetting 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_sizeargument is provided. In older versions, no prefetching was done.Qobjects and querysets can now be combined using^as the exclusive or (XOR) operator.XORis 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, andSmallAutoFieldare now created as identity columns rather than serial columns with sequences.
Requests and Responses¶
HttpResponse.set_cookie()now supportstimedeltaobjects for themax_ageargument.
Security¶
The new
SECRET_KEY_FALLBACKSsetting allows providing a list of values for secret key rotation.The
SECURE_PROXY_SSL_HEADERsetting now supports a comma-separated list of protocols in the header value.
Signals¶
The
pre_deleteandpost_deletesignals now dispatch theoriginof the deletion.
Templates¶
The HTML
<script>elementidattribute is no longer required when wrapping thejson_scripttemplate filter.The
cached template loaderis now enabled in development, whenDEBUGisTrue, andOPTIONS['loaders']isn’t specified. You may specifyOPTIONS['loaders']to override this, if necessary.
Tests¶
The
DiscoverRunnernow supports running tests in parallel on macOS, Windows, and any other systems where the defaultmultiprocessingstart method isspawn.A nested atomic block marked as durable in
django.test.TestCasenow raises aRuntimeError, the same as outside of tests.SimpleTestCase.assertFormError()andassertFormsetError()now support passing a form/formset object directly.
URLs¶
The new
ResolverMatch.captured_kwargsattribute stores the captured keyword arguments, as parsed from the URL.The new
ResolverMatch.extra_kwargsattribute stores the additional keyword arguments passed to the view function.
Utilities¶
SimpleLazyObjectnow supports addition operations.mark_safe()now preserves lazy objects.
Validators¶
The new
StepValueValidatorchecks if a value is an integral multiple of a given step size. This new validator is used for the newstep_sizeargument 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_likeis changed fromTruetoFalseto 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_fieldsarguments.The
ignore_conflictsargument of theDatabaseOperations.insert_statement()method is replaced byon_conflictthat acceptsdjango.db.models.constants.OnConflict.DatabaseOperations._convert_field_to_tz()is replaced byDatabaseOperations._convert_sql_to_tz()that accepts thesql,params, andtznamearguments.Several date and time methods on
DatabaseOperationsnow takesqlandparamsarguments instead offield_nameand 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, andGenericRelationare now cached on theModelinstance to which they belong. This change was reverted in Django 4.1.2.DiscoverRunnernow returns a non-zero error code for unexpected successes from tests marked withunittest.expectedFailure().CsrfViewMiddlewareno longer masks the CSRF cookie like it does the CSRF token in the DOM.CsrfViewMiddlewarenow usesrequest.META['CSRF_COOKIE']for storing the unmasked CSRF secret rather than a masked version. This is an undocumented, private API.The
ModelAdmin.actionsandinlinesattributes 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:addedandformset:removedJavaScript events are now pure JavaScript events and don’t depend on jQuery. See Inline form events for more details on the change.The
exc_infoargument of the undocumenteddjango.utils.log.log_response()function is replaced byexception.The
sizeargument of the undocumenteddjango.views.static.was_modified_since()function is removed.The admin log out UI now uses
POSTrequests.The undocumented
InlineAdminFormSet.non_form_errorsproperty 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.SuccessURLAllowedHostsMixinmixin is replaced byRedirectURLMixin.BaseConstraintsubclasses 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 anexcludevalue to aset. It’s also preferable to pass anexcludevalue as asetto theModel.clean_fields(),Model.full_clean(),Model.validate_unique(), andModel.validate_constraints()methods.The minimum supported version of
asgirefis increased from 3.4.1 to 3.5.2.Combined expressions no longer use the error-prone behavior of guessing
output_fieldwhen argument types match. As a consequence, resolving anoutput_fieldfor database functions and combined expressions may now crash with mixed types. You will need to explicitly set theoutput_fieldin such cases.The
makemessagescommand no longer changes.pofiles when up to date. In older versions,POT-Creation-Datewas 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
locationand optionallastmodattributes.CSRF_COOKIE_MASKEDtransitional setting is deprecated.The
nameargument ofdjango.utils.functional.cached_property()is deprecated as it’s unnecessary as of Python 3.6.The
opclassesargument ofdjango.contrib.postgres.constraints.ExclusionConstraintis deprecated in favor of usingOpClass()inExclusionConstraint.expressions. To use it, you need to add'django.contrib.postgres'in yourINSTALLED_APPS.After making this change,
makemigrationswill generate a new migration with two operations:RemoveConstraintandAddConstraint. Since this change has no effect on the database schema, theSeparateDatabaseAndStateoperation can be used to only update the migration state without running any SQL. Move the generated operations into thestate_operationsargument ofSeparateDatabaseAndState. For example:class Migration(migrations.Migration): ... operations = [ migrations.SeparateDatabaseAndState( database_operations=[], state_operations=[ migrations.RemoveConstraint(...), migrations.AddConstraint(...), ], ), ]
The undocumented ability to pass
errors=NonetoSimpleTestCase.assertFormError()andassertFormsetError()is deprecated. Useerrors=[]instead.django.contrib.sessions.serializers.PickleSerializeris deprecated due to the risk of remote code execution.The usage of
QuerySet.iterator()on a queryset that prefetches related objects without providing thechunk_sizeargument is deprecated. In older versions, no prefetching was done. Providing a value forchunk_sizesignifies 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=Trueis added to the signature ofRemoteUserBackend.configure_user(). Support forRemoteUserBackendsubclasses that do not accept this argument is deprecated.The
django.utils.timezone.utcalias todatetime.timezone.utcis deprecated. Usedatetime.timezone.utcdirectly.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.OpenLayersWidgetis deprecated.django.contrib.auth.hashers.CryptPasswordHasheris deprecated.The ability to pass
nulls_first=Falseornulls_last=FalsetoExpression.asc()andExpression.desc()methods, and theOrderByexpression is deprecated. UseNoneinstead.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_checksis removed.The
whitelistargument anddomain_whitelistattribute ofdjango.core.validators.EmailValidatorare removed.The
default_app_configapplication configuration variable is removed.TransactionTestCase.assertQuerysetEqual()no longer callsrepr()on a queryset when compared to string values.The
django.core.cache.backends.memcached.MemcachedCachebackend is removed.Support for the pre-Django 3.2 format of messages used by
django.contrib.messages.storage.cookie.CookieStorageis removed.