Hi folks, Ticket 31506 <https://code.djangoproject.com/ticket/31506> is an older ticket that I thought I'd look into – there are a couple of points of discussion I thought I'd run past folks before offering a PR.
To summarise the issue: On Postgres when you add or subtract an interval with a date the result is a timestamp <https://www.postgresql.org/docs/current/functions-datetime.html#OPERATORS-DATETIME-TABLE>. The issue reports that if you have a queryset to calculate an interval, then using ExpressionWrapper to define the output_field essentially ignores the response type that the db returns. Eg: class StartModel(Model): start = DateField() StartModel.objects.create(start=date(2022, 1, 1)) qs = StartModel.objects.annotate( end=ExpressionWrapper(F("start") + timedelta(days=1), output_field=DateField()) ) print(type(qs.first().end)) <class 'datetime.datetime'> This raises a couple of questions: *1.* Is this the responsibility of ExpressionWrapper (or the relevant db backend) to ensure the correct types are returned or is it the responsibility of the developer to use Cast() ? The equivalent with Cast() returns the correct type: qs = StartModel.objects.annotate(end=Cast(F("start") + timedelta(days=1), output_field=DateField())) print(type(qs.first().end)) <class 'datetime.date'> If so, should the ticket be marked as *wontfix*? *2.* If it is Django's responsibility there are a couple of solutions that work (fixes failing test attached to the ticket): *a. Add a db converter:* diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py index 2303703ebc..e4158b8130 100644 --- a/django/db/backends/postgresql/operations.py +++ b/django/db/backends/postgresql/operations.py @@ -1,3 +1,4 @@ +from datetime import datetime from psycopg2.extras import Inet from django.conf import settings @@ -354,3 +355,15 @@ class DatabaseOperations(BaseDatabaseOperations): update_fields, unique_fields, ) + + def get_db_converters(self, expression): + converters = super().get_db_converters(expression) + internal_type = expression.output_field.get_internal_type() + if internal_type == "DateField": + converters.append(self.convert_datefield_value) + return converters + + def convert_datefield_value(self, value, expression, connection): + if type(value) == datetime: + value = value.date() + return value *b. Manually cast the expression in ExpressionWrapper*diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 5e3c7cab82..347c107936 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -1247,6 +1247,10 @@ class ExpressionWrapper(SQLiteNumericMixin, Expression): def as_sql(self, compiler, connection): return compiler.compile(self.expression) + def as_postgresql(self, compiler, connection): + sql, params = self.as_sql(compiler, connection) + return "{}::{}".format(sql, self.output_field.db_type(connection)), params + def __repr__(self): return "{}({})".format(self.__class__.__name__, self.expression) Any other ideas? Regards, David -- You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group. To unsubscribe from this group and stop receiving emails from it, send an email to django-developers+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CADyZw-5UDjiFBKwL94NiF-pHu3AU82nh-A5Y071q%2BhOiMU0Yig%40mail.gmail.com.