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.
Django 4.2 supports Python 3.8, 3.9, 3.10, 3.11, and 3.12 (as of 4.2.8). We highly recommend and only officially support the latest release of each series.
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.
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
.
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.
The new django.core.files.storage.InMemoryStorage
class provides a
non-persistent storage useful for speeding up tests by avoiding disk access.
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.
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
and
filter_vertical
widgets are now
filterable.
The admin/base.html
template now has a new block nav-breadcrumbs
which contains the navigation landmark and the breadcrumbs
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 supports
pathlib.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 and
IsEmpty()
expression allow filtering empty geometries on PostGIS.
The new FromWKB()
and FromWKT()
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 the
TrigramStrictWordSimilarity()
and
TrigramStrictWordDistance()
expressions allow
using trigram strict word similarity.
The arrayfield.overlap
lookup now supports QuerySet.values()
and values_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 in
import
and export
statements with their hashed counterparts. If you
want to try it, subclass ManifestStaticFilesStorage
and set the
support_js_module_import_aggregation
attribute to True
.
The new ManifestStaticFilesStorage.manifest_hash
attribute provides
a hash over all files in the manifest and changes whenever one of the files
changes.
The new "assume_role"
option is now supported in OPTIONS
on
PostgreSQL to allow specifying the session role.
The new "server_side_binding"
option is now supported in
OPTIONS
on PostgreSQL with psycopg
3.1.8+ to allow using
server-side binding cursors.
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.
ModelForm
now accepts the new Meta
option
formfield_callback
to customize form fields.
modelform_factory()
now respects the
formfield_callback
attribute of the form
’s Meta
.
Added support and translations for the Central Kurdish (Sorani) language.
The django.db.backends logger now logs transaction management queries
(BEGIN
, COMMIT
, and ROLLBACK
) at the DEBUG
level.
makemessages
command now supports locales with private sub-tags
such as nl_NL-x-informal
.
The new makemigrations --update
option merges model changes into
the latest migration and optimizes the resulting operations.
Migrations now support serialization of enum.Flag
objects.
QuerySet
now extensively supports filtering against
Window functions with the exception of disjunctive filter lookups
against window functions when performing aggregation.
prefetch_related()
now supports
Prefetch
objects with sliced querysets.
Registering lookups on
Field
instances is now supported.
The new robust
argument for on_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 of
JSONField
.
Now
now supports microsecond precision
on MySQL and millisecond precision on SQLite.
F()
expressions that output BooleanField
can now be negated using ~F()
(inversion operator).
Model
now provides asynchronous versions of some methods that use the
database, using an a
prefix: adelete()
,
arefresh_from_db()
, and asave()
.
Related managers now provide asynchronous versions of methods that change a
set of related objects, using an a
prefix: aadd()
,
aclear()
, aremove()
, and
aset()
.
CharField.max_length
is no
longer required to be set on PostgreSQL, which supports unlimited VARCHAR
columns.
StreamingHttpResponse
now supports async iterators
when Django is served via ASGI.
The test --debug-sql
option now formats SQL queries with
sqlparse
.
The RequestFactory
,
AsyncRequestFactory
, Client
, and
AsyncClient
classes now support the headers
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"})
The new encoder
parameter for django.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 internal
url_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
a Content-Disposition
HTTP header value as specified by RFC 6266.
The list of common passwords used by CommonPasswordValidator
is updated
to the most recent version.
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 that
DatabaseFeatures.allows_group_by_selected_pks
is still supported and
should be enabled if your backend supports functional dependency detection in
GROUP BY
clauses as specified by the SQL:1999
standard.
inspectdb
now uses display_size
from
DatabaseIntrospection.get_table_description()
rather than
internal_size
for CharField
.
Upstream support for MariaDB 10.3 ends in May 2023. Django 4.2 supports MariaDB 10.4 and higher.
Upstream support for MySQL 5.7 ends in October 2023. Django 4.2 supports MySQL 8 and higher.
Upstream support for PostgreSQL 11 ends in November 2023. Django 4.2 supports PostgreSQL 12 and higher.
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.
MySQL 8+ allows functional dependencies on GROUP BY
columns, so the
pre-Django 4.2 workaround of grouping by primary keys of the main table is
removed. As a consequence, using RawSQL()
aggregations is no longer
supported on MySQL as there is no way to determine if such aggregations are
needed or valid in the GROUP BY
clause. Use Aggregation functions
instead.
The undocumented django.http.multipartparser.parse_header()
function is
removed. Use django.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 for Expression.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 the
Exists
expression is removed.
The is_summary
argument of the undocumented Query.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, use
BaseUserCreationForm
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 for wsgi.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 a
hostname
and
certificates
. If you need the
previous behavior that is less restrictive and not recommended, subclass
EmailBackend
and override the ssl_context
property.
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.
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 used 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.
The BaseUserManager.make_random_password()
method is deprecated. See
recipes and best practices
for using Python's secrets
module to generate passwords.
The length_is
template filter is deprecated in favor of length
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
, and
django.contrib.auth.hashers.UnsaltedMD5PasswordHasher
are deprecated.
django.contrib.postgres.fields.CICharField
is deprecated in favor of
CharField(db_collation="…")
with a case-insensitive non-deterministic
collation.
django.contrib.postgres.fields.CIEmailField
is deprecated in favor of
EmailField(db_collation="…")
with a case-insensitive non-deterministic
collation.
django.contrib.postgres.fields.CITextField
is deprecated in favor of
TextField(db_collation="…")
with a case-insensitive non-deterministic
collation.
django.contrib.postgres.fields.CIText
mixin is deprecated.
The map_height
and map_width
attributes of BaseGeometryWidget
are
deprecated, use CSS to size map widgets instead.
SimpleTestCase.assertFormsetError()
is deprecated in favor of
assertFormSetError()
.
TransactionTestCase.assertQuerysetEqual()
is deprecated in favor of
assertQuerySetEqual()
.
Passing positional arguments to Signer
and TimestampSigner
is
deprecated in favor of keyword-only arguments.
The DEFAULT_FILE_STORAGE
setting is deprecated in favor of
STORAGES["default"]
.
The STATICFILES_STORAGE
setting is deprecated in favor of
STORAGES["staticfiles"]
.
The django.core.files.storage.get_storage_class()
function is deprecated.
Jan 15, 2024