Ticket #10154: dateexpressions_2.diff

File dateexpressions_2.diff, 7.0 KB (added by Koen Biermans <koen.biermans@…>, 11 years ago)

new patch without the leaf

  • 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, negated=False):
     130        if len(children) != 2:
     131            raise TypeError('You have to specify a node and a timedelta.')
     132        if not isinstance(children[1], datetime.timedelta):
     133            raise TypeError('Second child must be a timedelta.')
     134        if connector is None:
     135            raise TypeError('You have to specify a connector.')
     136        super(DateModifierNode, self).__init__(children, connector, negated)
     137
     138    def prepare(self, evaluator, query, allow_joins):
     139        return evaluator.prepare_node(self, query, allow_joins)
     140
     141    def evaluate(self, evaluator, qn):
     142        if not qn:
     143            qn = connection.ops.quote_name
     144
     145        timedelta = self.children.pop()
     146        sql, params = evaluator.evaluate_node(self, qn)
     147
     148        return connection.ops.date_interval_sql(sql, self.connector, timedelta), params
     149
  • 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, timedelta):
     32        """
     33        implements the interval functionality for expressions
     34        format for Postgres: (datefield + interval '3 days 200 seconds')
     35        """
     36        modifiers = []
     37        if timedelta.days:
     38            modifiers.append(u'%s days' % timedelta.days)
     39        if timedelta.seconds:
     40            modifiers.append(u'%s seconds' % timedelta.seconds)
     41        mods = u' '.join(modifiers)
     42        conn = u' %s ' % connector
     43        return u'(%s)' % conn.join([sql, u'interval \'%s\'' % mods])
     44
    3145    def date_trunc_sql(self, lookup_type, field_name):
    3246        # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
    3347        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, timedelta):
     108        """
     109        Implements the interval functionality for expressions
     110        format for sqlite: DATE(datefield, "3 days", "200 seconds")
     111        """
     112        modifiers = []
     113        if timedelta.days:
     114            modifiers.append(u'%s days' % timedelta.days)
     115        if timedelta.seconds:
     116            modifiers.append(u'%s seconds' % timedelta.seconds)
     117        mods = u','.join([u'"%s%s"' % (connector, modifier) for modifier in modifiers])
     118        return u'DATE(%s, %s)' % (sql, mods)
     119
    107120    def date_trunc_sql(self, lookup_type, field_name):
    108121        """
    109122        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# in filter:
     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
     86# chaining multiple timedeltas:
     87>>> Employee.objects.filter(start_date__gt=F('contract_date')+datetime.timedelta(days=80)+datetime.timedelta(seconds=500))
     88[<Employee: Frank Meyer>, <Employee: Max Mustermann>]
     89
     90# in update:
     91# move contractdates to 14 days before startdate
     92>>> Employee.objects.update(contract_date=F('start_date')-datetime.timedelta(days=14))
     934
     94>>> [e.contract_date for e in Employee.objects.all()]
     95[datetime.date(2008, 2, 16), datetime.date(2008, 4, 17), datetime.date(2008, 6, 17), None]
     96
    7197"""}
Back to Top