Python compatibility
Django 4.0 supports Python 3.8, 3.9, and 3.10. It’s highly recommend and only officially support the latest release of each series.
Attention:
The Django 3.2.x series was the last to support Python 3.6 and 3.7
What you need to know about Django 4.0
1- You can use Scrypt password hasher
The new scrypt password hasher is more secure and recommended over PBKDF2. However, it’s not the default as it requires OpenSSL 1.1+ and more memory.
scrypt is similar to PBKDF2 and bcrypt in utilizing a set number of iterations to slow down brute-force attacks. However, because PBKDF2 and bcrypt do not require a lot of memory, attackers with sufficient resources can launch large-scale parallel attacks in order to speed up the attacking process. scrypt is specifically designed to use more memory compared to other password-based key derivation functions in order to limit the amount of parallelism an attacker can use.
To use scrypt as your default storage algorithm,
modify PASSWORD_HASHERS
to list ScryptPasswordHasher
first. That is, in your settings file:
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.ScryptPasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]
2- Requests and Responses
The SecurityMiddleware
now adds the Cross-Origin Opener Policy header with a value of 'same-origin'
to prevent cross-origin popups from sharing the same browsing context. You can prevent this header from being added by setting the SECURE_CROSS_ORIGIN_OPENER_POLICY
setting to None
.
3- SecurityMiddleware no longer sets the X-XSS-Production header
The SecurityMiddleware
no longer sets the X-XSS-Protection
header if the SECURE_BROWSER_XSS_FILTER
setting is True
. The setting is removed.
Most modern browsers don’t honor the X-XSS-Protection
HTTP header. You can use Content-Security-Policy without allowing 'unsafe-inline'
scripts instead.
If you want to support legacy browsers and set the header, use this line in a custom middleware:
response.headers.setdefault('X-XSS-Protection', '1; mode=block')
4- CSRF-TOKEN
CSRF protection now consults the Origin
header, if present. To facilitate this, some changes to the CSRF_TRUSTED_ORIGINS
setting are required.
5- Generic Views
DeleteView
now uses FormMixin
to handle POST requests. As a consequence, any custom deletion logic in delete()
handlers should be moved to form_valid()
, or a shared helper method, if required.
6- Forms
ModelChoiceField
now includes the provided value in the params
argument of a raised ValidationError
for the invalid_choice
error message. This allows custom error messages to use the %(value)s
placeholder.
BaseFormSet
now renders non-form errors with an additional class of nonform
to help distinguish them from form-specific errors.
BaseFormSet
now allows customizing the widget used when deleting forms via can_delete
by setting the deletion_widget
attribute or overriding get_deletion_widget()
method.
7- Models
New QuerySet.contains(obj)
method returns whether the queryset contains the given object. This tries to perform the query in the simplest and fastest way possible.
The new precision
argument of the Round()
database function allows specifying the number of decimal places after rounding.
QuerySet.bulk_create()
now sets the primary key on objects when using SQLite 3.35+.
DurationField
now supports multiplying and dividing by scalar values on SQLite.
QuerySet.bulk_update()
now returns the number of objects updated.
The new Expression.empty_result_set_value
attribute allows specifying a value to return when the function is used over an empty result set.
The skip_locked
argument of QuerySet.select_for_update()
is now allowed on MariaDB 10.6+.
Lookup
expressions may now be used in QuerySet
annotations, aggregations, and directly in filters.
The new default argument for built-in aggregates allows specifying a value to be returned when the queryset (or grouping) contains no entries, rather than None
.
8- Functional unique constraints
The new *expressions
positional argument of UniqueConstraint()
enables creating functional unique constraints on expressions and database functions. For example:
from django.db import models
from django.db.models import UniqueConstraint
from django.db.models.functions import Lower
class MyModel(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255) class Meta:
constraints = [
UniqueConstraint(
Lower('first_name'),
Lower('last_name').desc(),
name='first_last_name_unique',
),
]
Functional unique constraints are added to models using the Meta.constraints
option.
9- Templates
The Template based form rendering
Now Forms
, Formsets, and ErrorList
rendered using the template engine to enhance customization. See the new render()
, get_context()
, and template_name
for Form
and formset rendering for Formset
.
floatformat
template filter now allows using the u
suffix to force disabling localization.
10- Management Commands
The runserver
management command now supports the --skip-checks
option.
On PostgreSQL, dbshell
now supports specifying a password file.
The shell
command now respects sys.__interactivehook__
at startup. This allows loading shell history between interactive sessions. As a consequence, readline
is no longer loaded if running in isolated mode.
The new BaseCommand.suppressed_base_arguments
attribute allows suppressing unsupported default command options in the help output.
The new startapp --exclude
and startproject --exclude
options allow excluding directories from the template.