Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/ref/tasks.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Tasks
.. module:: django.tasks
:synopsis: Django's built-in background Task system.

The Task framework provides the contract and plumbing for background work, not
the engine that runs it. The Tasks API defines how work is described, queued,
and tracked, but leaves actual execution to external infrastructure.

Task definition
===============

Expand Down Expand Up @@ -274,6 +278,10 @@ Task errors
Backends
========

Backends handle how Tasks are stored and executed. All backends share a common
interface defined by ``BaseTaskBackend``, which specifies the core methods for
enqueueing Tasks and retrieving results.

Base backend
------------

Expand Down Expand Up @@ -365,6 +373,10 @@ Feature :class:`.DummyBackend` :class:`.ImmediateBackend`
Available backends
------------------

Django includes only development and testing backends. These support local
execution and inspection, for production ready backends refer to
:ref:`configuring-a-task-backend`.

Immediate backend
~~~~~~~~~~~~~~~~~

Expand Down
12 changes: 12 additions & 0 deletions docs/releases/6.0.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
==========================
Django 6.0.1 release notes
==========================

*Expected January 6, 2026*

Django 6.0.1 fixes several bugs in 6.0.

Bugfixes
========

* ...
61 changes: 31 additions & 30 deletions docs/releases/6.0.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
============================================
Django 6.0 release notes - UNDER DEVELOPMENT
============================================
========================
Django 6.0 release notes
========================

*Expected December 2025*
*December 3, 2025*

Welcome to Django 6.0!

These release notes cover the :ref:`new features <whats-new-6.0>`, as well as
some :ref:`backwards incompatible changes <backwards-incompatible-6.0>` you'll
want to be aware of when upgrading from Django 5.2 or earlier. We've
some :ref:`backwards incompatible changes <backwards-incompatible-6.0>` you
should be aware of when upgrading from Django 5.2 or earlier. We've
:ref:`begun the deprecation process for some features
<deprecated-features-6.0>`.

Expand Down Expand Up @@ -102,6 +102,11 @@ Django now includes a built-in Tasks framework for running code outside the
HTTP request–response cycle. This enables offloading work, such as sending
emails or processing data, to background workers.

The framework provides task definition, validation, queuing, and result
handling. Django guarantees consistent behavior for creating and managing
tasks, while the responsibility for running them continues to belong to
external worker processes.

Tasks are defined using the :func:`~django.tasks.task` decorator::

from django.core.mail import send_mail
Expand Down Expand Up @@ -160,6 +165,12 @@ Minor features
* The new :attr:`.AdminSite.password_change_form` attribute allows customizing
the form used in the admin site password change view.

* Message levels ``messages.DEBUG`` and ``messages.INFO`` now have distinct
icons and CSS styling. Previously, both levels shared the same appearance as
``messages.SUCCESS``. Given that :meth:`.ModelAdmin.message_user` uses
``messages.INFO`` by default, set the level to ``messages.SUCCESS`` to keep
the previous icon and styling.

:mod:`django.contrib.auth`
~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -189,6 +200,11 @@ Minor features
:class:`GeometryType() <django.contrib.gis.db.models.functions.GeometryType>`
database function allow filtering geometries by their types.

* Widgets from :mod:`django.contrib.gis.forms.widgets` now render without
inline JavaScript in templates. If you have customized any geometry widgets
or their templates, you may need to :ref:`update them
<geometry-widgets-customization>` to match the new layout.

:mod:`django.contrib.postgres`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -217,6 +233,10 @@ Minor features
ensures consistent path ordering in manifest files, making them more
reproducible and reducing unnecessary diffs.

* The :djadmin:`collectstatic` command now reports only a summary for skipped
files (and for deleted files when using ``--clear``) at ``--verbosity`` 1. To
see per-file details for either case, set ``--verbosity`` to 2 or higher.

Email
~~~~~

Expand Down Expand Up @@ -462,29 +482,9 @@ Miscellaneous
refactored to use Python's :class:`email.message.Message` for parsing.
Input headers exceeding 10000 characters will now raise :exc:`ValueError`.

* Widgets from :mod:`django.contrib.gis.forms.widgets` now render without
inline JavaScript in templates. If you have customized any geometry widgets
or their templates, you may need to :ref:`update them
<geometry-widgets-customization>` to match the new layout.

* Message levels ``messages.DEBUG`` and ``messages.INFO`` now have distinct
icons and CSS styling in the admin. Previously, these used the same icon and
styling as the ``messages.SUCCESS`` level. Since
:meth:`.ModelAdmin.message_user` uses the ``messages.INFO`` level by default,
set the level to ``messages.SUCCESS`` to retain the previous icon and
styling.

* The minimum supported version of ``asgiref`` is increased from 3.8.1 to
3.9.1.

* The :djadmin:`collectstatic` command now reports only a summary of skipped
files due to conflicts when ``--verbosity`` is 1. To see warnings for each
conflicting destination path, set the ``--verbosity`` flag to 2 or higher.

* The :option:`collectstatic --clear` command now reports only a summary of
deleted files when ``--verbosity`` is 1. To see the details for each file
deleted, set the ``--verbosity`` flag to 2 or higher.

.. _deprecated-features-6.0:

Features deprecated in 6.0
Expand Down Expand Up @@ -516,14 +516,15 @@ Miscellaneous
available :class:`~django.db.models.StringAgg` class.

* The PostgreSQL ``OrderableAggMixin`` is deprecated in favor of the
``order_by`` attribute now available on the ``Aggregate`` class.
``order_by`` attribute now available on the
:class:`~django.db.models.Aggregate` class.

* The default protocol in :tfilter:`urlize` and :tfilter:`urlizetrunc` will
change from HTTP to HTTPS in Django 7.0. Set the transitional setting
``URLIZE_ASSUME_HTTPS`` to ``True`` to opt into assuming HTTPS during the
Django 6.x release cycle.
:setting:`URLIZE_ASSUME_HTTPS` to ``True`` to opt into assuming HTTPS during
the Django 6.x release cycle.

* ``URLIZE_ASSUME_HTTPS`` transitional setting is deprecated.
* The :setting:`URLIZE_ASSUME_HTTPS` transitional setting is deprecated.

* Setting :setting:`ADMINS` or :setting:`MANAGERS` to a list of (name, address)
tuples is deprecated. Set to a list of email address strings instead. Django
Expand Down
1 change: 1 addition & 0 deletions docs/releases/index.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ versions of the documentation contain the release notes for any later releases.
.. toctree::
:maxdepth: 1

6.0.1
6.0

5.2 release
Expand Down
28 changes: 17 additions & 11 deletions docs/topics/tasks.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ to be run elsewhere, potentially at a later date. This keeps requests fast,
reduces latency, and improves the user experience. For example, a user
shouldn't have to wait for an email to send before their page finishes loading.

Django's new Tasks framework makes it easy to define and enqueue such work. It
Django's Tasks framework makes it easy to define and enqueue such work. It
does not provide a worker mechanism to run Tasks. The actual execution must be
handled by infrastructure outside Django, such as a separate process or
service.
service. Given that, a :ref:`task backend <configuring-a-task-backend>` capable
of executing tasks on that service should be evaluated and configured.

Background Task fundamentals
============================
Expand All @@ -41,9 +42,13 @@ Configuring a Task backend
The Task backend determines how and where Tasks are stored for execution and
how they are executed. Different Task backends have different characteristics
and configuration options, which may impact the performance and reliability of
your application. Django comes with a number of :ref:`built-in backends
<task-available-backends>`. Django does not provide a generic way to execute
Tasks, only enqueue them.
your application. Django comes with :ref:`built-in backends
<task-available-backends>`, but these are for development and testing only.

Django handles task definition, validation, queuing, and result handling, not
execution, so production setups need a backend or worker process that actually
runs queued work. Relevant options are listed in the `Community Ecosystem
<https://www.djangoproject.com/community/ecosystem/>`__ page.

Task backends are configured using the :setting:`TASKS` setting in your
settings file. Whilst most applications will only need a single backend,
Expand Down Expand Up @@ -103,13 +108,14 @@ Stored results can be cleared using the
>>> len(default_task_backend.results)
0

Using a custom backend
----------------------
Third-party backends
--------------------

While Django includes support for a number of Task backends out-of-the-box,
sometimes you might want to customize the Task backend. To use an external Task
backend with Django, use the Python import path as the :setting:`BACKEND
<TASKS-BACKEND>` of the :setting:`TASKS` setting, like so::
As mentioned at the beginning of this section, Django includes backends
suitable for development and testing only. Production systems should rely on
backends that supply a worker process and durable queue implementation. To use
an external Task backend with Django, use the Python import path as the
:setting:`BACKEND <TASKS-BACKEND>` of the :setting:`TASKS` setting, like so::

TASKS = {
"default": {
Expand Down
24 changes: 10 additions & 14 deletions tests/auth_tests/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,24 +202,24 @@ def test_validate(self):
self.assertEqual(cm.exception.messages, [expected_error % "username"])
self.assertEqual(cm.exception.error_list[0].code, "password_too_similar")

with self.assertRaises(ValidationError) as cm:
msg = expected_error % "email address"
with self.assertRaisesMessage(ValidationError, msg):
UserAttributeSimilarityValidator().validate("example.com", user=user)
self.assertEqual(cm.exception.messages, [expected_error % "email address"])

with self.assertRaises(ValidationError) as cm:
msg = expected_error % "first name"
with self.assertRaisesMessage(ValidationError, msg):
UserAttributeSimilarityValidator(
user_attributes=["first_name"],
max_similarity=0.3,
).validate("testclient", user=user)
self.assertEqual(cm.exception.messages, [expected_error % "first name"])
# max_similarity=1 doesn't allow passwords that are identical to the
# attribute's value.
with self.assertRaises(ValidationError) as cm:
msg = expected_error % "first name"
with self.assertRaisesMessage(ValidationError, msg):
UserAttributeSimilarityValidator(
user_attributes=["first_name"],
max_similarity=1,
).validate(user.first_name, user=user)
self.assertEqual(cm.exception.messages, [expected_error % "first name"])
# Very low max_similarity is rejected.
msg = "max_similarity must be at least 0.1"
with self.assertRaisesMessage(ValueError, msg):
Expand All @@ -240,11 +240,9 @@ class TestUser(models.Model):
def username(self):
return "foobar"

with self.assertRaises(ValidationError) as cm:
msg = "The password is too similar to the username."
with self.assertRaisesMessage(ValidationError, msg):
UserAttributeSimilarityValidator().validate("foobar", user=TestUser())
self.assertEqual(
cm.exception.messages, ["The password is too similar to the username."]
)

def test_help_text(self):
self.assertEqual(
Expand Down Expand Up @@ -294,18 +292,16 @@ def test_validate(self):
expected_error = "This password is too common."
self.assertIsNone(CommonPasswordValidator().validate("a-safe-password"))

with self.assertRaises(ValidationError) as cm:
with self.assertRaisesMessage(ValidationError, expected_error):
CommonPasswordValidator().validate("godzilla")
self.assertEqual(cm.exception.messages, [expected_error])

def test_common_hexed_codes(self):
expected_error = "This password is too common."
common_hexed_passwords = ["asdfjkl:", "&#2336:"]
for password in common_hexed_passwords:
with self.subTest(password=password):
with self.assertRaises(ValidationError) as cm:
with self.assertRaisesMessage(ValidationError, expected_error):
CommonPasswordValidator().validate(password)
self.assertEqual(cm.exception.messages, [expected_error])

def test_validate_custom_list(self):
path = os.path.join(
Expand Down
46 changes: 14 additions & 32 deletions tests/postgres_tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -1087,12 +1087,9 @@ def test_blank_true(self):
def test_with_size(self):
field = ArrayField(models.IntegerField(), size=3)
field.clean([1, 2, 3], None)
with self.assertRaises(exceptions.ValidationError) as cm:
msg = "List contains 4 items, it should contain no more than 3."
with self.assertRaisesMessage(exceptions.ValidationError, msg):
field.clean([1, 2, 3, 4], None)
self.assertEqual(
cm.exception.messages[0],
"List contains 4 items, it should contain no more than 3.",
)

def test_with_size_singular(self):
field = ArrayField(models.IntegerField(), size=1)
Expand Down Expand Up @@ -1156,21 +1153,15 @@ def test_valid(self):

def test_to_python_fail(self):
field = SimpleArrayField(forms.IntegerField())
with self.assertRaises(exceptions.ValidationError) as cm:
msg = "Item 1 in the array did not validate: Enter a whole number."
with self.assertRaisesMessage(exceptions.ValidationError, msg):
field.clean("a,b,9")
self.assertEqual(
cm.exception.messages[0],
"Item 1 in the array did not validate: Enter a whole number.",
)

def test_validate_fail(self):
field = SimpleArrayField(forms.CharField(required=True))
with self.assertRaises(exceptions.ValidationError) as cm:
msg = "Item 3 in the array did not validate: This field is required."
with self.assertRaisesMessage(exceptions.ValidationError, msg):
field.clean("a,b,")
self.assertEqual(
cm.exception.messages[0],
"Item 3 in the array did not validate: This field is required.",
)

def test_validate_fail_base_field_error_params(self):
field = SimpleArrayField(forms.CharField(max_length=2))
Expand Down Expand Up @@ -1203,12 +1194,9 @@ def test_validate_fail_base_field_error_params(self):

def test_validators_fail(self):
field = SimpleArrayField(forms.RegexField("[a-e]{2}"))
with self.assertRaises(exceptions.ValidationError) as cm:
msg = "Item 1 in the array did not validate: Enter a valid value."
with self.assertRaisesMessage(exceptions.ValidationError, msg):
field.clean("a,bc,de")
self.assertEqual(
cm.exception.messages[0],
"Item 1 in the array did not validate: Enter a valid value.",
)

def test_delimiter(self):
field = SimpleArrayField(forms.CharField(), delimiter="|")
Expand All @@ -1227,21 +1215,15 @@ def test_prepare_value(self):

def test_max_length(self):
field = SimpleArrayField(forms.CharField(), max_length=2)
with self.assertRaises(exceptions.ValidationError) as cm:
msg = "List contains 3 items, it should contain no more than 2."
with self.assertRaisesMessage(exceptions.ValidationError, msg):
field.clean("a,b,c")
self.assertEqual(
cm.exception.messages[0],
"List contains 3 items, it should contain no more than 2.",
)

def test_min_length(self):
field = SimpleArrayField(forms.CharField(), min_length=4)
with self.assertRaises(exceptions.ValidationError) as cm:
msg = "List contains 3 items, it should contain no fewer than 4."
with self.assertRaisesMessage(exceptions.ValidationError, msg):
field.clean("a,b,c")
self.assertEqual(
cm.exception.messages[0],
"List contains 3 items, it should contain no fewer than 4.",
)

def test_min_length_singular(self):
field = SimpleArrayField(forms.IntegerField(), min_length=2)
Expand All @@ -1252,9 +1234,9 @@ def test_min_length_singular(self):

def test_required(self):
field = SimpleArrayField(forms.CharField(), required=True)
with self.assertRaises(exceptions.ValidationError) as cm:
msg = "This field is required."
with self.assertRaisesMessage(exceptions.ValidationError, msg):
field.clean("")
self.assertEqual(cm.exception.messages[0], "This field is required.")

def test_model_field_formfield(self):
model_field = ArrayField(models.CharField(max_length=27))
Expand Down
Loading