Django 4.2 release notes¶
April 3, 2023
Welcome to Django 4.2!
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.1 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.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 3.2, will end in April 2024.
Python compatibility¶
Django 4.2 supports Python 3.8, 3.9, 3.10, and 3.11. We highly recommend and only officially support the latest release of each series.
What’s new in Django 4.2¶
Psycopg 3 support¶
Django now supports psycopg version 3.1.8 or higher. To update your code,
install the psycopg library, you don’t need to change the
ENGINE
as django.db.backends.postgresql
supports both libraries.
Support for psycopg2
is likely to be deprecated and removed at some point
in the future.
Be aware that psycopg
3 introduces some breaking changes over psycopg2
.
As a consequence, you may need to make some changes to account for
differences from psycopg2.
Comments on columns and tables¶
The new Field.db_comment
and
Meta.db_table_comment
options allow creating comments on columns and tables, respectively. For
example:
from django.db import models
class Question(models.Model):
text = models.TextField(db_comment="Poll question")
pub_date = models.DateTimeField(
db_comment="Date and time when the question was published",
)
class Meta:
db_table_comment = "Poll questions"
class Answer(models.Model):
question = models.ForeignKey(
Question,
on_delete=models.CASCADE,
db_comment="Reference to a question",
)
answer = models.TextField(db_comment="Question answer")
class Meta:
db_table_comment = "Question answers"
Also, the new AlterModelTableComment
operation allows changing table comments defined in the
Meta.db_table_comment
.
Mitigation for the BREACH attack¶
GZipMiddleware
now includes a mitigation for
the BREACH attack. It will add up to 100 random bytes to gzip responses to make
BREACH attacks harder. Read more about the mitigation technique in the Heal
The Breach (HTB) paper.
In-memory file storage¶
The new django.core.files.storage.InMemoryStorage
class provides a
non-persistent storage useful for speeding up tests by avoiding disk access.
Custom file storages¶
The new STORAGES
setting allows configuring multiple custom file
storage backends. It also controls storage engines for managing
files (the "default"
key) and static files (the "staticfiles"
key).
The old DEFAULT_FILE_STORAGE
and STATICFILES_STORAGE
settings are
deprecated as of this release.
Minor features¶
django.contrib.admin
¶
- The light or dark color theme of the admin can now be toggled in the UI, as well as being set to follow the system setting.
- The admin’s font stack now prefers system UI fonts and no longer requires downloading fonts. Additionally, CSS variables are available to more easily override the default font families.
- The admin/delete_confirmation.html template now has some additional blocks and scripting hooks to ease customization.
- The chosen options of
filter_horizontal
andfilter_vertical
widgets are now filterable. - The
admin/base.html
template now has a new blocknav-breadcrumbs
which contains the navigation landmark and thebreadcrumbs
block. ModelAdmin.list_editable
now uses atomic transactions when making edits.- jQuery is upgraded from version 3.6.0 to 3.6.4.
django.contrib.auth
¶
- The default iteration count for the PBKDF2 password hasher is increased from 390,000 to 600,000.
UserCreationForm
now saves many-to-many form fields for a custom user model.- The new
BaseUserCreationForm
is now the recommended base class for customizing the user creation form.
django.contrib.gis
¶
- The GeoJSON serializer now outputs the
id
key for serialized features, which defaults to the primary key of objects. - The
GDALRaster
class now supportspathlib.Path
. - The
GeoIP2
class now supports.mmdb
files downloaded from DB-IP. - The OpenLayers template widget no longer includes inline CSS (which also
removes the former
map_css
block) to better comply with a strict Content Security Policy. OpenLayersWidget
is now based on OpenLayers 7.2.2 (previously 4.6.5).- The new
isempty
lookup andIsEmpty()
expression allow filtering empty geometries on PostGIS. - The new
FromWKB()
andFromWKT()
functions allow creating geometries from Well-known binary (WKB) and Well-known text (WKT) representations.
django.contrib.postgres
¶
- The new
trigram_strict_word_similar
lookup, and theTrigramStrictWordSimilarity()
andTrigramStrictWordDistance()
expressions allow using trigram strict word similarity. - The
arrayfield.overlap
lookup now supportsQuerySet.values()
andvalues_list()
as a right-hand side.
django.contrib.sitemaps
¶
- The new
Sitemap.get_languages_for_item()
method allows customizing the list of languages for which the item is displayed.
django.contrib.staticfiles
¶
ManifestStaticFilesStorage
now has experimental support for replacing paths to JavaScript modules inimport
andexport
statements with their hashed counterparts. If you want to try it, subclassManifestStaticFilesStorage
and set thesupport_js_module_import_aggregation
attribute toTrue
.- The new
ManifestStaticFilesStorage.manifest_hash
attribute provides a hash over all files in the manifest and changes whenever one of the files changes.
Database backends¶
- The new
"assume_role"
option is now supported inOPTIONS
on PostgreSQL to allow specifying the session role. - The new
"server_side_binding"
option is now supported inOPTIONS
on PostgreSQL withpsycopg
3.1.8+ to allow using server-side binding cursors.
Error Reporting¶
- The debug page now shows exception notes and fine-grained error locations on Python 3.11+.
- Session cookies are now treated as credentials and therefore hidden and
replaced with stars (
**********
) in error reports.
Forms¶
ModelForm
now accepts the newMeta
optionformfield_callback
to customize form fields.modelform_factory()
now respects theformfield_callback
attribute of theform
’sMeta
.
Internationalization¶
- Added support and translations for the Central Kurdish (Sorani) language.
Logging¶
- The django.db.backends logger now logs transaction management queries
(
BEGIN
,COMMIT
, andROLLBACK
) at theDEBUG
level.
Management Commands¶
makemessages
command now supports locales with private sub-tags such asnl_NL-x-informal
.- The new
makemigrations --update
option merges model changes into the latest migration and optimizes the resulting operations.
Migrations¶
- Migrations now support serialization of
enum.Flag
objects.
Models¶
QuerySet
now extensively supports filtering against Window functions with the exception of disjunctive filter lookups against window functions when performing aggregation.prefetch_related()
now supportsPrefetch
objects with sliced querysets.- Registering lookups on
Field
instances is now supported. - The new
robust
argument foron_commit()
allows performing actions that can fail after a database transaction is successfully committed. - The new
KT()
expression represents the text value of a key, index, or path transform ofJSONField
. Now
now supports microsecond precision on MySQL and millisecond precision on SQLite.F()
expressions that outputBooleanField
can now be negated using~F()
(inversion operator).Model
now provides asynchronous versions of some methods that use the database, using ana
prefix:adelete()
,arefresh_from_db()
, andasave()
.- Related managers now provide asynchronous versions of methods that change a
set of related objects, using an
a
prefix:aadd()
,aclear()
,aremove()
, andaset()
. CharField.max_length
is no longer required to be set on PostgreSQL, which supports unlimitedVARCHAR
columns.
Requests and Responses¶
StreamingHttpResponse
now supports async iterators when Django is served via ASGI.
Tests¶
The
test --debug-sql
option now formats SQL queries withsqlparse
.The
RequestFactory
,AsyncRequestFactory
,Client
, andAsyncClient
classes now support theheaders
parameter, which accepts a dictionary of header names and values. This allows a more natural syntax for declaring headers.# Before: self.client.get("/home/", HTTP_ACCEPT_LANGUAGE="fr") await self.async_client.get("/home/", ACCEPT_LANGUAGE="fr") # After: self.client.get("/home/", headers={"accept-language": "fr"}) await self.async_client.get("/home/", headers={"accept-language": "fr"})
Utilities¶
- The new
encoder
parameter fordjango.utils.html.json_script()
function allows customizing a JSON encoder class. - The private internal vendored copy of
urllib.parse.urlsplit()
now strips'\r'
,'\n'
, and'\t'
(see CVE-2022-0391 and bpo-43882). This is to protect projects that may be incorrectly using the internalurl_has_allowed_host_and_scheme()
function, instead of using one of the documented functions for handling URL redirects. The Django functions were not affected. - The new
django.utils.http.content_disposition_header()
function returns aContent-Disposition
HTTP header value as specified by RFC 6266.
Validators¶
- The list of common passwords used by
CommonPasswordValidator
is updated to the most recent version.
Backwards incompatible changes in 4.2¶
Database backend API¶
This section describes changes that may be needed in third-party database backends.
DatabaseFeatures.allows_group_by_pk
is removed as it only remained to accommodate a MySQL extension that has been supplanted by proper functional dependency detection in MySQL 5.7.15. Note thatDatabaseFeatures.allows_group_by_selected_pks
is still supported and should be enabled if your backend supports functional dependency detection inGROUP BY
clauses as specified by theSQL:1999
standard.inspectdb
now usesdisplay_size
fromDatabaseIntrospection.get_table_description()
rather thaninternal_size
forCharField
.
Dropped support for MariaDB 10.3¶
Upstream support for MariaDB 10.3 ends in May 2023. Django 4.2 supports MariaDB 10.4 and higher.
Dropped support for MySQL 5.7¶
Upstream support for MySQL 5.7 ends in October 2023. Django 4.2 supports MySQL 8 and higher.
Dropped support for PostgreSQL 11¶
Upstream support for PostgreSQL 11 ends in November 2023. Django 4.2 supports PostgreSQL 12 and higher.
Setting update_fields
in Model.save()
may now be required¶
In order to avoid updating unnecessary columns,
QuerySet.update_or_create()
now passes update_fields
to the
Model.save()
calls. As a consequence, any
fields modified in the custom save()
methods should be added to the
update_fields
keyword argument before calling super()
. See
Overriding predefined model methods for more details.
Miscellaneous¶
- The undocumented
django.http.multipartparser.parse_header()
function is removed. Usedjango.utils.http.parse_header_parameters()
instead. {% blocktranslate asvar … %}
result is now marked as safe for (HTML) output purposes.- The
autofocus
HTML attribute in the admin search box is removed as it can be confusing for screen readers. - The
makemigrations --check
option no longer creates missing migration files. - The
alias
argument forExpression.get_group_by_cols()
is removed. - The minimum supported version of
sqlparse
is increased from 0.2.2 to 0.3.1. - The undocumented
negated
parameter of theExists
expression is removed. - The
is_summary
argument of the undocumentedQuery.add_annotation()
method is removed. - The minimum supported version of SQLite is increased from 3.9.0 to 3.21.0.
- The minimum supported version of
asgiref
is increased from 3.5.2 to 3.6.0. UserCreationForm
now rejects usernames that differ only in case. If you need the previous behavior, useBaseUserCreationForm
instead.- The minimum supported version of
mysqlclient
is increased from 1.4.0 to 1.4.3. - The minimum supported version of
argon2-cffi
is increased from 19.1.0 to 19.2.0. - The minimum supported version of
Pillow
is increased from 6.2.0 to 6.2.1. - The minimum supported version of
jinja2
is increased from 2.9.2 to 2.11.0. - The minimum supported version of redis-py is increased from 3.0.0 to 3.4.0.
- Manually instantiated
WSGIRequest
objects must be provided a file-like object forwsgi.input
. Previously, Django was more lax than the expected behavior as specified by the WSGI specification. - Support for
PROJ
< 5 is removed. EmailBackend
now verifies ahostname
andcertificates
. If you need the previous behavior that is less restrictive and not recommended, subclassEmailBackend
and override thessl_context
property.
Features deprecated in 4.2¶
index_together
option is deprecated in favor of indexes
¶
The Meta.index_together
option is deprecated in favor of the indexes
option.
Migrating existing index_together
should be handled as a migration. For
example:
class Author(models.Model):
rank = models.IntegerField()
name = models.CharField(max_length=30)
class Meta:
index_together = [["rank", "name"]]
Should become:
class Author(models.Model):
rank = models.IntegerField()
name = models.CharField(max_length=30)
class Meta:
indexes = [models.Index(fields=["rank", "name"])]
Running the makemigrations
command will generate a migration
containing a RenameIndex
operation
which will rename the existing index. Next, consider squashing migrations to
remove index_together
from historical migrations.
The AlterIndexTogether
migration operation is now officially supported only
for pre-Django 4.2 migration files. For backward compatibility reasons, it’s
still part of the public API, and there’s no plan to deprecate or remove it,
but it should not be used for new migrations. Use
AddIndex
and
RemoveIndex
operations instead.
Passing encoded JSON string literals to JSONField
is deprecated¶
JSONField
and its associated lookups and aggregates used to allow passing
JSON encoded string literals which caused ambiguity on whether string literals
were already encoded from database backend’s perspective.
During the deprecation period string literals will be attempted to be JSON decoded and a warning will be emitted on success that points at passing non-encoded forms instead.
Code that use to pass JSON encoded string literals:
Document.objects.bulk_create(
Document(data=Value("null")),
Document(data=Value("[]")),
Document(data=Value('"foo-bar"')),
)
Document.objects.annotate(
JSONBAgg("field", default=Value("[]")),
)
Should become:
Document.objects.bulk_create(
Document(data=Value(None, JSONField())),
Document(data=[]),
Document(data="foo-bar"),
)
Document.objects.annotate(
JSONBAgg("field", default=[]),
)
From Django 5.1+ string literals will be implicitly interpreted as JSON string literals.
Miscellaneous¶
The
BaseUserManager.make_random_password()
method is deprecated. See recipes and best practices for using Python’ssecrets
module to generate passwords.The
length_is
template filter is deprecated in favor oflength
and the==
operator within an{% if %}
tag. For example{% if value|length == 4 %}…{% endif %} {% if value|length == 4 %}True{% else %}False{% endif %}
instead of:
{% if value|length_is:4 %}…{% endif %} {{ value|length_is:4 }}
django.contrib.auth.hashers.SHA1PasswordHasher
,django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher
, anddjango.contrib.auth.hashers.UnsaltedMD5PasswordHasher
are deprecated.django.contrib.postgres.fields.CICharField
is deprecated in favor ofCharField(db_collation="…")
with a case-insensitive non-deterministic collation.django.contrib.postgres.fields.CIEmailField
is deprecated in favor ofEmailField(db_collation="…")
with a case-insensitive non-deterministic collation.django.contrib.postgres.fields.CITextField
is deprecated in favor ofTextField(db_collation="…")
with a case-insensitive non-deterministic collation.django.contrib.postgres.fields.CIText
mixin is deprecated.The
map_height
andmap_width
attributes ofBaseGeometryWidget
are deprecated, use CSS to size map widgets instead.SimpleTestCase.assertFormsetError()
is deprecated in favor ofassertFormSetError()
.TransactionTestCase.assertQuerysetEqual()
is deprecated in favor ofassertQuerySetEqual()
.Passing positional arguments to
Signer
andTimestampSigner
is deprecated in favor of keyword-only arguments.The
DEFAULT_FILE_STORAGE
setting is deprecated in favor ofSTORAGES["default"]
.The
STATICFILES_STORAGE
setting is deprecated in favor ofSTORAGES["staticfiles"]
.The
django.core.files.storage.get_storage_class()
function is deprecated.