Fixed #31506 -- Clarified that ExpressionWrapper does not perform database casts.
Some checks are pending
Docs / spelling (push) Waiting to run
Docs / blacken-docs (push) Waiting to run
Docs / lint-docs (push) Waiting to run
Linters / flake8 (push) Waiting to run
Linters / isort (push) Waiting to run
Linters / black (push) Waiting to run
Linters / zizmor (push) Waiting to run
Tests / Windows, SQLite, Python 3.14 (push) Waiting to run
Tests / JavaScript tests (push) Waiting to run

Added warning in DateField documentation about type differences when using
timedelta on PostgreSQL and MySQL. Mentioned Cast() and integer arithmetic
solutions.
This commit is contained in:
Cha Hwa Young 2025-10-13 02:29:15 +09:00 committed by Jacob Walls
parent e48527f91d
commit 55af4749b9
2 changed files with 45 additions and 0 deletions

View file

@ -37,6 +37,12 @@ determine the result's field type, such as complex expressions that mix field
types. For example, adding a ``DecimalField()`` and a ``FloatField()`` requires
an output field, like ``output_field=FloatField()``.
``output_field`` also allows using custom fields that perform type conversions
outside a specific model field context. For example, if you frequently need to
perform date arithmetic with ``timedelta``, you can create a custom field that
handles the conversion, ensuring consistent results across databases. See
:doc:`/howto/custom-model-fields`.
Some examples
=============
@ -575,6 +581,12 @@ available on other expressions. ``ExpressionWrapper`` is necessary when using
arithmetic on ``F()`` expressions with different types as described in
:ref:`using-f-with-annotations`.
.. admonition:: Database casting not performed
``ExpressionWrapper`` only sets the output field for the ORM and does not
perform any database-level casting. To ensure a specific type is returned
from the database, use :class:`~django.db.models.functions.Cast` instead.
``JSONNull()`` expression
-------------------------

View file

@ -831,6 +831,39 @@ exclusive. Any combination of these options will result in an error.
instance, removing its time component. This is true for both storage and
comparison.
.. warning::
On PostgreSQL and MySQL, arithmetic operations on a ``DateField`` with a
:class:`~datetime.timedelta` return a ``datetime`` instead of a ``date``.
This occurs because Python's ``timedelta`` is converted to SQL
``INTERVAL``, and the SQL operation ``date +/- interval`` returns a
``timestamp`` on these databases.
To ensure a ``date`` result, use one of the following approaches. Either
explicitly cast the result to a date::
import datetime
from django.db.models import DateField, F
from django.db.models.functions import Cast
qs = MyModel.objects.annotate(
previous_day=Cast(
F("date_field") - datetime.timedelta(days=1),
output_field=DateField(),
)
)
Or on PostgreSQL only, use integer arithmetic to represent days::
from django.db.models import DateField, ExpressionWrapper, F
qs = MyModel.objects.annotate(
previous_day=ExpressionWrapper(
F("date_field") - 1, # Subtract 1 day as integer
output_field=DateField(),
)
)
``DateTimeField``
-----------------