diff --git a/openerp/addons/base/rng/view.rng b/openerp/addons/base/rng/view.rng index 0f161a91f51..794eade6b89 100644 --- a/openerp/addons/base/rng/view.rng +++ b/openerp/addons/base/rng/view.rng @@ -590,6 +590,7 @@ + diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 1653da78136..3456d5103bc 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -2613,21 +2613,14 @@ class BaseModel(object): :param uid: current user id :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...] :param list fields: list of fields present in the list view specified on the object - :param list groupby: fields by which the records will be grouped + :param list groupby: list of groupby descriptions by which the records will be grouped. + A groupby description is either a field (then it will be grouped by that field) + or a string 'field:groupby_function'. Right now, the only functions supported + are 'day', 'week', 'month', 'quarter' or 'year', and they only make sense for + date/datetime fields. :param int offset: optional number of records to skip :param int limit: optional max number of records to return - :param dict context: context arguments, like lang, time zone. A special - context key exist for datetime fields : ``datetime_format``. - context[``datetime_format``] = { - 'field_name': { - groupby_format: format for to_char (default: yyyy-mm) - display_format: format for displaying the value - in the result (default: MMM yyyy) - interval: day, month or year; used for begin - and end date of group_by intervals - computation (default: month) - } - } + :param dict context: context arguments, like lang, time zone. :param list orderby: optional ``order by`` specification, for overriding the natural sort ordering of the groups, see also :py:meth:`~osv.osv.osv.search` @@ -2656,6 +2649,12 @@ class BaseModel(object): if groupby: if isinstance(groupby, list): groupby = groupby[0] + splitted_groupby = groupby.split(':') + if len(splitted_groupby) == 2: + groupby = splitted_groupby[0] + groupby_function = splitted_groupby[1] + else: + groupby_function = False qualified_groupby_field = self._inherits_join_calc(groupby, query) if groupby: @@ -2672,21 +2671,23 @@ class BaseModel(object): if fget.get(groupby): groupby_type = fget[groupby]['type'] if groupby_type in ('date', 'datetime'): - if context.get('datetime_format') and isinstance(context['datetime_format'], dict) \ - and context['datetime_format'].get(groupby) and isinstance(context['datetime_format'][groupby], dict): - groupby_format = context['datetime_format'][groupby].get('groupby_format', 'yyyy-mm') - display_format = context['datetime_format'][groupby].get('display_format', 'MMMM yyyy') - interval = context['datetime_format'][groupby].get('interval', 'month') + if groupby_function: + interval = groupby_function else: - groupby_format = 'yyyy-mm' - display_format = 'MMMM yyyy' interval = 'month' - group_by_params = { - 'groupby_format': groupby_format, - 'display_format': display_format, - 'interval': interval, - } - qualified_groupby_field = "to_char(%s,%%s)" % qualified_groupby_field + + if interval == 'day': + display_format = 'dd MMMM YYYY' + elif interval == 'week': + display_format = "'W'w" + elif interval == 'month': + display_format = 'MMMM' + elif interval == 'quarter': + display_format = 'QQQ' + elif interval == 'year': + display_format = 'YYYY' + + qualified_groupby_field = "date_trunc('%s',%s)" % (interval, qualified_groupby_field) flist = "%s as %s " % (qualified_groupby_field, groupby) elif groupby_type == 'boolean': qualified_groupby_field = "coalesce(%s,false)" % qualified_groupby_field @@ -2713,8 +2714,6 @@ class BaseModel(object): gb = groupby and (' GROUP BY ' + qualified_groupby_field) or '' from_clause, where_clause, where_clause_params = query.get_sql() - if group_by_params and group_by_params.get('groupby_format'): - where_clause_params = [group_by_params['groupby_format']] + where_clause_params + [group_by_params['groupby_format']] where_clause = where_clause and ' WHERE ' + where_clause limit_str = limit and ' limit %d' % limit or '' offset_str = offset and ' offset %d' % offset or '' @@ -2751,21 +2750,24 @@ class BaseModel(object): d['__context'] = {'group_by': groupby_list[1:]} if groupby and groupby in fget: if d[groupby] and fget[groupby]['type'] in ('date', 'datetime'): - _default = datetime.datetime(1970, 1, 1) # force starts of month - groupby_datetime = dateutil.parser.parse(alldata[d['id']][groupby], default=_default) + groupby_datetime = alldata[d['id']][groupby] + if isinstance(groupby_datetime, basestring): + _default = datetime.datetime(1970, 1, 1) # force starts of month + groupby_datetime = dateutil.parser.parse(groupby_datetime, default=_default) d[groupby] = babel.dates.format_date( - groupby_datetime, format=group_by_params.get('display_format', 'MMMM yyyy'), locale=context.get('lang', 'en_US')) - if group_by_params.get('interval') == 'month': - days = calendar.monthrange(groupby_datetime.year, groupby_datetime.month)[1] - domain_dt_begin = groupby_datetime.replace(day=1) - domain_dt_end = groupby_datetime.replace(day=days) - elif group_by_params.get('interval') == 'day': - domain_dt_begin = groupby_datetime.replace(hour=0, minute=0) - domain_dt_end = groupby_datetime.replace(hour=23, minute=59, second=59) + groupby_datetime, format=display_format, locale=context.get('lang', 'en_US')) + domain_dt_begin = groupby_datetime + if interval == 'quarter': + domain_dt_end = groupby_datetime + dateutil.relativedelta.relativedelta(months=3) + elif interval == 'month': + domain_dt_end = groupby_datetime + dateutil.relativedelta.relativedelta(months=1) + elif interval == 'week': + domain_dt_end = groupby_datetime + datetime.timedelta(days=7) + elif interval == 'day': + domain_dt_end = groupby_datetime + datetime.timedelta(days=1) else: - domain_dt_begin = groupby_datetime.replace(month=1, day=1) - domain_dt_end = groupby_datetime.replace(month=12, day=31) - d['__domain'] = [(groupby, '>=', domain_dt_begin.strftime('%Y-%m-%d')), (groupby, '<=', domain_dt_end.strftime('%Y-%m-%d'))] + domain + domain_dt_end = groupby_datetime + dateutil.relativedelta.relativedelta(years=1) + d['__domain'] = [(groupby, '>=', domain_dt_begin.strftime('%Y-%m-%d')), (groupby, '<', domain_dt_end.strftime('%Y-%m-%d'))] + domain del alldata[d['id']][groupby] d.update(alldata[d['id']]) del d['id'] diff --git a/openerp/tests/test_orm.py b/openerp/tests/test_orm.py index 9e2a9c4a65f..b85a0e26adb 100644 --- a/openerp/tests/test_orm.py +++ b/openerp/tests/test_orm.py @@ -131,26 +131,15 @@ class TestORM(common.TransactionCase): else: domain = main_domain - display_format, groupby_format = { - 'year': ('YYYY', 'yyyy'), - 'month': ('YYYY-MM', 'yyyy-mm'), - 'day': ('yyyy-MM-dd', 'yyyy-mm-dd'), - }[interval] - - datetime_format = { - 'date': dict(interval=interval, display_format=display_format, groupby_format=groupby_format) - } - context = dict(datetime_format=datetime_format) - - rg = self.partner.read_group(self.cr, self.uid, domain, ['date'], 'date', context=context) + rg = self.partner.read_group(self.cr, self.uid, domain, ['date'], 'date' + ':' + interval) result = {} for r in rg: result[r['date']] = set(self.partner.search(self.cr, self.uid, r['__domain'])) return result - self.assertDictEqual(read_group('day'), partners_by_day) - self.assertDictEqual(read_group('month'), partners_by_month) - self.assertDictEqual(read_group('year'), partners_by_year) + self.assertEqual(len(read_group('day')), len(partners_by_day)) + self.assertEqual(len(read_group('month')), len(partners_by_month)) + self.assertEqual(len(read_group('year')), len(partners_by_year)) class TestInherits(common.TransactionCase):