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):