Opened 2 years ago

Last modified 2 years ago

#25177 new Bug

Filter reference field on model to a date difference raise a TypeError

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

Description

I want to filter all products that the start_date and signed_date are lesser than or equal than a specific value.

Before 1.7, I was doing the following:

Product.objects.filter(start_date__isnull=False).extra(
            where=['"appname_product"."start_date" - "appname_product"."signed_date" <= %s' % days],
        ).count()

Now, I tried to do the following using F expressions:

Product.objects.filter(start_date__isnull=False).annotate(days=F('start_date')-F('signed_date')).filter(days__lte=days)

The problem, this will raise a TypeError because I think it will try to parse the value to Date:

/usr/local/lib/python2.7/dist-packages/django/db/models/query.pyc in filter(self, *args, **kwargs)
    677         set.
    678         """
--> 679         return self._filter_or_exclude(False, *args, **kwargs)
    680
    681     def exclude(self, *args, **kwargs):

/usr/local/lib/python2.7/dist-packages/django/db/models/query.pyc in _filter_or_exclude(self, negate, *args, **kwargs)
    695             clone.query.add_q(~Q(*args, **kwargs))
    696         else:
--> 697             clone.query.add_q(Q(*args, **kwargs))
    698         return clone
    699

/usr/local/lib/python2.7/dist-packages/django/db/models/sql/query.pyc in add_q(self, q_object)
   1307         existing_inner = set(
   1308             (a for a in self.alias_map if self.alias_map[a].join_type == INNER))
-> 1309         clause, require_inner = self._add_q(where_part, self.used_aliases)
   1310         self.where.add(clause, AND)
   1311         for hp in having_parts:

/usr/local/lib/python2.7/dist-packages/django/db/models/sql/query.pyc in _add_q(self, q_object, used_aliases, branch_negated, current_negated, allow_joins, split_subq)
   1335                     child, can_reuse=used_aliases, branch_negated=branch_negated,
   1336                     current_negated=current_negated, connector=connector,
-> 1337                     allow_joins=allow_joins, split_subq=split_subq,
   1338                 )
   1339                 joinpromoter.add_votes(needed_inner)

/usr/local/lib/python2.7/dist-packages/django/db/models/sql/query.pyc in build_filter(self, filter_expr, branch_negated, current_negated, can_reuse, connector, allow_joins, split_subq)
   1157         clause = self.where_class()
   1158         if reffed_expression:
-> 1159             condition = self.build_lookup(lookups, reffed_expression, value)
   1160             if not condition:
   1161                 # Backwards compat for custom lookups

/usr/local/lib/python2.7/dist-packages/django/db/models/sql/query.pyc in build_lookup(self, lookups, lhs, rhs)
   1099                     lhs = self.try_transform(lhs, name, lookups)
   1100                     final_lookup = lhs.get_lookup('exact')
-> 1101                 return final_lookup(lhs, rhs)
   1102             lhs = self.try_transform(lhs, name, lookups)
   1103             lookups = lookups[1:]

/usr/local/lib/python2.7/dist-packages/django/db/models/lookups.pyc in __init__(self, lhs, rhs)
     99     def __init__(self, lhs, rhs):
    100         self.lhs, self.rhs = lhs, rhs
--> 101         self.rhs = self.get_prep_lookup()
    102         if hasattr(self.lhs, 'get_bilateral_transforms'):
    103             bilateral_transforms = self.lhs.get_bilateral_transforms()

/usr/local/lib/python2.7/dist-packages/django/db/models/lookups.pyc in get_prep_lookup(self)
    137
    138     def get_prep_lookup(self):
--> 139         return self.lhs.output_field.get_prep_lookup(self.lookup_name, self.rhs)
    140
    141     def get_db_prep_lookup(self, value, connection):

/usr/local/lib/python2.7/dist-packages/django/db/models/fields/__init__.pyc in get_prep_lookup(self, lookup_type, value)
   1311         if lookup_type in ('month', 'day', 'week_day', 'hour', 'minute', 'second'):
   1312             return int(value)
-> 1313         return super(DateField, self).get_prep_lookup(lookup_type, value)
   1314
   1315     def get_prep_value(self, value):

/usr/local/lib/python2.7/dist-packages/django/db/models/fields/__init__.pyc in get_prep_lookup(self, lookup_type, value)
    725             return value
    726         elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'):
--> 727             return self.get_prep_value(value)
    728         elif lookup_type in ('range', 'in'):
    729             return [self.get_prep_value(v) for v in value]

/usr/local/lib/python2.7/dist-packages/django/db/models/fields/__init__.pyc in get_prep_value(self, value)
   1315     def get_prep_value(self, value):
   1316         value = super(DateField, self).get_prep_value(value)
-> 1317         return self.to_python(value)
   1318
   1319     def get_db_prep_value(self, value, connection, prepared=False):

/usr/local/lib/python2.7/dist-packages/django/db/models/fields/__init__.pyc in to_python(self, value)
   1272
   1273         try:
-> 1274             parsed = parse_date(value)
   1275             if parsed is not None:
   1276                 return parsed

/usr/local/lib/python2.7/dist-packages/django/utils/dateparse.pyc in parse_date(value)
     58     Returns None if the input isn't well formatted.
     59     """
---> 60     match = date_re.match(value)
     61     if match:
     62         kw = {k: int(v) for k, v in six.iteritems(match.groupdict())}

TypeError: expected string or buffer

Attachments (1)

25177-test.diff (659 bytes) - added by Tim Graham 16 months ago.
A test for Django's test suite that reproduces the crash.

Download all attachments as: .zip

Change History (3)

comment:1 Changed 2 years ago by Josh Smeaton

Triage Stage: UnreviewedAccepted

comment:2 Changed 2 years ago by Josh Smeaton

This ticket seems really familiar, but I can't seem to find a duplicate at the moment. I'll keep hunting for it. In the mean time, there's a workaround for what you want to do:

Product.objects.annotate(days=ExpressionWrapper(F('start_date')-F('signed_date'), output_field=DurationField())).filter(days__lte=2)

That works on Postgres at least, I'm not sure how other backends handle it.

I think Combinable._combine() may need special handling for date - date producing durations though.

Changed 16 months ago by Tim Graham

Attachment: 25177-test.diff added

A test for Django's test suite that reproduces the crash.

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