Ticket #10154: dateexpressions.diff

File dateexpressions.diff, 6.3 KB (added by Koen Biermans <koen.biermans@…>, 10 years ago)

first stab at comining expressions with timedeltas

  • django/db/models/expressions.py

     
    11from copy import deepcopy
    2 from datetime import datetime
     2import datetime
     3from django.db import connection
    34
    45from django.utils import tree
    56
     
    2627        super(ExpressionNode, self).__init__(children, connector, negated)
    2728
    2829    def _combine(self, other, connector, reversed, node=None):
     30        if isinstance(other, datetime.timedelta):
     31            return DateModifierNode([self, other], connector)
     32
    2933        if reversed:
    3034            obj = ExpressionNode([other], connector)
    3135            obj.add(node or self, connector)
     
    108112
    109113    def evaluate(self, evaluator, qn):
    110114        return evaluator.evaluate_leaf(self, qn)
     115
     116class DateModifierNode(ExpressionNode):
     117    """
     118    Node that implements the following syntax:
     119    filter(end_date__gt=F('start_date') + datetime.timedelta(days=3, seconds=200))
     120
     121    which translates into:
     122        SQLITE:
     123            where end_date > date(start_date, "3 days 200 seconds")
     124
     125        POSTGRES:
     126            where end_date > (start_date + interval '3 days 200 seconds')
     127
     128    """
     129    def __init__(self, children, connector=None, negated=False):
     130        if len(children) != 2:
     131            raise TypeError('You have to specify a node and a timedelta.')
     132        if connector is None:
     133            raise TypeError('You have to specify a connector.')
     134        super(DateModifierNode, self).__init__([children[0], DateModifierLeaf(children[1])], connector, negated)
     135
     136    def prepare(self, evaluator, query, allow_joins):
     137        return evaluator.prepare_node(self, query, allow_joins)
     138
     139    def evaluate(self, evaluator, qn):
     140        if not qn:
     141            qn = connection.ops.quote_name
     142
     143        modifiers = self.children.pop().evaluate(evaluator, qn)
     144        sql, params = evaluator.evaluate_node(self, qn)
     145
     146        return connection.ops.date_interval_sql(sql, self.connector, modifiers), params
     147
     148class DateModifierLeaf(object):
     149    def __init__(self, timedelta):
     150        self.days = timedelta.days
     151        self.seconds = timedelta.seconds
     152
     153    def prepare(self, evaluator, query, allow_joins):
     154        pass
     155
     156    def evaluate(self, evaluator, qn):
     157        # move exact representation to backend ?
     158        mods = []
     159        if self.days:
     160            mods.append(u'%s days' % self.days)
     161        if self.seconds:
     162            mods.append(u'%s seconds' % self.seconds)
     163        return mods
  • django/db/backends/postgresql/operations.py

     
    2828        # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT
    2929        return "EXTRACT('%s' FROM %s)" % (lookup_type, field_name)
    3030
     31    def date_interval_sql(self, sql, connector, modifiers):
     32        # implements the interval functionality for expressions
     33        mods = u' '.join([u'%s' % modifier for modifier in modifiers])
     34        conn = u' %s ' % connector
     35        return u'(%s)' % conn.join([sql, u'interval \'%s\'' % mods])
     36
    3137    def date_trunc_sql(self, lookup_type, field_name):
    3238        # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
    3339        return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name)
  • django/db/backends/__init__.py

     
    104104        """
    105105        raise NotImplementedError()
    106106
     107    def date_interval_sql(self, sql, connector, modifiers):
     108        """
     109        Implements the interval functionality for expressions
     110        """
     111        mods = u','.join([u'"%s%s"' % (connector, modifier) for modifier in modifiers])
     112        return u'DATE(%s, %s)' % (sql, mods)
     113
    107114    def date_trunc_sql(self, lookup_type, field_name):
    108115        """
    109116        Given a lookup_type of 'year', 'month' or 'day', returns the SQL that
  • tests/modeltests/expressions/models.py

     
    77class Employee(models.Model):
    88    firstname = models.CharField(max_length=50)
    99    lastname = models.CharField(max_length=50)
     10    contract_date = models.DateField(null=True)
     11    start_date = models.DateField(null=True)
    1012
    1113    def __unicode__(self):
    1214        return u'%s %s' % (self.firstname, self.lastname)
     
    3032__test__ = {'API_TESTS': """
    3133>>> from django.db.models import F
    3234
     35>>> import datetime
     36
    3337>>> Company(name='Example Inc.', num_employees=2300, num_chairs=5,
    34 ...     ceo=Employee.objects.create(firstname='Joe', lastname='Smith')).save()
     38...     ceo=Employee.objects.create(firstname='Joe', lastname='Smith',
     39...     contract_date=datetime.date(2008,1,1), start_date=datetime.date(2008,3,1))).save()
    3540>>> Company(name='Foobar Ltd.', num_employees=3, num_chairs=3,
    36 ...     ceo=Employee.objects.create(firstname='Frank', lastname='Meyer')).save()
     41...     ceo=Employee.objects.create(firstname='Frank', lastname='Meyer',
     42...     contract_date=datetime.date(2008,1,1), start_date=datetime.date(2008,5,1))).save()
    3743>>> Company(name='Test GmbH', num_employees=32, num_chairs=1,
    38 ...     ceo=Employee.objects.create(firstname='Max', lastname='Mustermann')).save()
     44...     ceo=Employee.objects.create(firstname='Max', lastname='Mustermann',
     45...     contract_date=datetime.date(2008,1,1), start_date=datetime.date(2008,7,1))).save()
    3946
    4047# We can filter for companies where the number of employees is greater than the
    4148# number of chairs.
     
    6875...
    6976FieldError: Joined field references are not permitted in this query
    7077
     78# F expressions for dates may be combined with timedelta
     79
     80>>> Employee.objects.filter(start_date__gt=F('contract_date')+datetime.timedelta(days=80, seconds=500))
     81[<Employee: Frank Meyer>, <Employee: Max Mustermann>]
     82
     83>>> Employee.objects.filter(contract_date__gt=F('start_date')-datetime.timedelta(days=80, seconds=500))
     84[<Employee: Joe Smith>]
     85
    7186"""}
Back to Top