Opened 7 years ago

Closed 7 years ago

Last modified 5 years ago

#27473 closed New feature (fixed)

Allow using Extract() with DurationField

Reported by: Daniel Hahler Owned by: nobody
Component: Database layer (models, ORM) Version: 1.10
Severity: Normal Keywords:
Cc: Josh Smeaton Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I am trying to use Extract on a duration field, but this results in ValueError: Extract input expression must be DateField, DateTimeField, or TimeField.:

qs = qs.annotate(
    speed=Sum(
        F('distance_in_meters') * Value(3.6) /
            Extract(F('duration_in_seconds'), 'epoch'),
        output_field=FloatField()),
)

The following patch however makes it work on PostgreSQL, so it should be considered to be enabled there - in case other DB backends do not support it:

diff --git i/django/db/models/functions/datetime.py w/django/db/models/functions/datetime.py
index 2980460..3266ef6 100644
--- i/django/db/models/functions/datetime.py
+++ w/django/db/models/functions/datetime.py
@@ -5,6 +5,7 @@
 from django.conf import settings
 from django.db.models import (
     DateField, DateTimeField, IntegerField, TimeField, Transform,
+    DurationField,
 )
 from django.db.models.lookups import (
     YearExact, YearGt, YearGte, YearLt, YearLte,
@@ -52,6 +53,8 @@ def as_sql(self, compiler, connection):
             sql = connection.ops.date_extract_sql(self.lookup_name, sql)
         elif isinstance(lhs_output_field, TimeField):
             sql = connection.ops.time_extract_sql(self.lookup_name, sql)
+        elif isinstance(lhs_output_field, DurationField):
+            sql = connection.ops.date_extract_sql(self.lookup_name, sql)
         else:
             # resolve_expression has already validated the output_field so this
             # assert should never be hit.
@@ -61,7 +64,7 @@ def as_sql(self, compiler, connection):
     def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
         copy = super(Extract, self).resolve_expression(query, allow_joins, reuse, summarize, for_save)
         field = copy.lhs.output_field
-        if not isinstance(field, (DateField, DateTimeField, TimeField)):
+        if not isinstance(field, (DateField, DateTimeField, TimeField, DurationField)):
             raise ValueError('Extract input expression must be DateField, DateTimeField, or TimeField.')
         # Passing dates to functions expecting datetimes is most likely a mistake.
         if type(field) == DateField and copy.lookup_name in ('hour', 'minute', 'second'):

Am I missing something?
What needs to be done to get it enabled for DurationFields?

Change History (8)

comment:1 by Tim Graham, 7 years ago

Cc: Josh Smeaton added
Summary: Use/enable db.models.functions.Extract with/for DurationFieldsAllow using Extract() with DurationField
Triage Stage: UnreviewedAccepted

I'm not sure, Josh might have some advice. If we have a tested patch, we can go from there.

comment:2 by Daniel Hahler, 7 years ago

Cool, working on a PR now.

I've noticed that with SQLite, the "Extract" for a DurationField appears to return NULL/None always, e.g. with DTModel.objects.annotate(duration_s=Extract('duration', 'second')).

Should this throw some error instead? Would that be possible in Extract already (so basically keeping the current behavior there for unsupported backends)?
Or would it need some other special casing?

comment:4 by Tim Graham, 7 years ago

Patch needs improvement: set

comment:5 by Daniel Hahler, 7 years ago

Patch needs improvement: unset

comment:6 by Tim Graham <timograham@…>, 7 years ago

Resolution: fixed
Status: newclosed

In 43a4835e:

Fixed #27473 -- Added DurationField support to Extract.

comment:7 by Jurgis Pralgauskis, 5 years ago

Are such features considered for backporting to 1.11?

comment:8 by Claude Paroz, 5 years ago

Not at all, 1.11 only receive security updates.

Note: See TracTickets for help on using tickets.
Back to Top