From c2a0f93b9eb501ac4f37d729d8007bbb83211dd9 Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Tue, 7 Jan 2014 09:58:00 +0100 Subject: [PATCH 1/9] [IMP] changes the readgroup method to allow better control in the groupby field when it is a date/datetime (for example, it is now possible to group by week/quarter) (orm.py) bzr revid: ged@openerp.com-20140107085800-hwyt41j3hwj1h1n8 --- openerp/osv/orm.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 610d0a5d503..882400a8dd7 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -2684,7 +2684,7 @@ class BaseModel(object): 'display_format': display_format, 'interval': interval, } - qualified_groupby_field = "to_char(%s,%%s)" % qualified_groupby_field + 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 @@ -2711,8 +2711,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 '' @@ -2749,8 +2747,10 @@ 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': From fcf97a559aee46e58efb33416db54115ce52faae Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Mon, 13 Jan 2014 14:36:02 +0100 Subject: [PATCH 2/9] [IMP] adds support for week/quarter groupby in readgroup method (orm.py) bzr revid: ged@openerp.com-20140113133602-ytxleg91bu3fyx62 --- openerp/osv/orm.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 882400a8dd7..008070e25a8 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -2672,15 +2672,12 @@ class BaseModel(object): 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') else: - groupby_format = 'yyyy-mm' display_format = 'MMMM yyyy' interval = 'month' group_by_params = { - 'groupby_format': groupby_format, 'display_format': display_format, 'interval': interval, } @@ -2753,17 +2750,18 @@ class BaseModel(object): 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) + domain_dt_begin = groupby_datetime + if group_by_params.get('interval') == 'quarter': + domain_dt_end = groupby_datetime + dateutil.relativedelta.relativedelta(months=3) + elif group_by_params.get('interval') == 'month': + domain_dt_end = groupby_datetime + dateutil.relativedelta.relativedelta(months=1) + elif group_by_params.get('interval') == 'week': + domain_dt_end = groupby_datetime + datetime.timedelta(days=7) 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) + 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'] From 74386a5bb95cb5a241b4fe29e5736e5a1cc93dea Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Wed, 22 Jan 2014 13:42:06 +0100 Subject: [PATCH 3/9] [IMP] changes the method read_group so that it now accepts groupbys of the form 'field:groupby_function', like 'create_date:week' for example. Also, removes the previous hack using the context (orm.py) bzr revid: ged@openerp.com-20140122124206-b5p06tpzw2i7tpo2 --- openerp/osv/orm.py | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 999ed065b84..b316b50e654 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,13 +2671,22 @@ 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): - 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: - display_format = 'MMMM yyyy' interval = 'month' + + if interval == 'day': + display_format = 'dd MMMM YYYY' + elif interval == 'week': + display_format = 'w' + elif interval == 'month': + display_format = 'MMMM' + elif interval == 'quarter': + display_format = 'QQQ' + elif interval == 'year': + display_format = 'YYYY' + group_by_params = { 'display_format': display_format, 'interval': interval, From 74fd48e60825ab2b4bc2b923ab210db2ff207c56 Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Thu, 23 Jan 2014 11:56:38 +0100 Subject: [PATCH 4/9] [FIX] fixes the read_group by dates tests (makes them use the field:interval syntax instead of using the context (test_orm.py) bzr revid: ged@openerp.com-20140123105638-l4ej76ynf4avcjma --- openerp/tests/test_orm.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/openerp/tests/test_orm.py b/openerp/tests/test_orm.py index 9e2a9c4a65f..0f685e292a7 100644 --- a/openerp/tests/test_orm.py +++ b/openerp/tests/test_orm.py @@ -131,18 +131,7 @@ 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'])) From 99230cd3d1b06f57515b171c199cddb5179ed1ee Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Thu, 23 Jan 2014 12:23:47 +0100 Subject: [PATCH 5/9] [FIX] prints some value so i can understand how to debug the failing test in test_orm.py bzr revid: ged@openerp.com-20140123112347-0swvj6xrhp5r5mpq --- openerp/tests/test_orm.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openerp/tests/test_orm.py b/openerp/tests/test_orm.py index 0f685e292a7..ad8378a527e 100644 --- a/openerp/tests/test_orm.py +++ b/openerp/tests/test_orm.py @@ -137,6 +137,14 @@ class TestORM(common.TransactionCase): result[r['date']] = set(self.partner.search(self.cr, self.uid, r['__domain'])) return result + print partners_by_day + print read_group('day') + print '-----------------' + print partners_by_month + print read_group('month') + print '-----------------' + print partners_by_year + print read_group('year') self.assertDictEqual(read_group('day'), partners_by_day) self.assertDictEqual(read_group('month'), partners_by_month) self.assertDictEqual(read_group('year'), partners_by_year) From a42900b8eaca6bb9dfae0b8b8ced9dc2f504652b Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Thu, 23 Jan 2014 14:06:03 +0100 Subject: [PATCH 6/9] [FIX] fixes the read_group by dates tests (makes them use the field:interval syntax instead of using the context (test_orm.py) bzr revid: ged@openerp.com-20140123130603-5kdlb7as7fhmchd8 --- openerp/tests/test_orm.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/openerp/tests/test_orm.py b/openerp/tests/test_orm.py index ad8378a527e..b85a0e26adb 100644 --- a/openerp/tests/test_orm.py +++ b/openerp/tests/test_orm.py @@ -137,17 +137,9 @@ class TestORM(common.TransactionCase): result[r['date']] = set(self.partner.search(self.cr, self.uid, r['__domain'])) return result - print partners_by_day - print read_group('day') - print '-----------------' - print partners_by_month - print read_group('month') - print '-----------------' - print partners_by_year - print read_group('year') - 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): From 02f776006344b7e55509dd3740af69b4d40e3b93 Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Fri, 24 Jan 2014 09:43:19 +0100 Subject: [PATCH 7/9] [IMP] adds an 'interval' attribute to 'field' in the rng file (view.rng) bzr revid: ged@openerp.com-20140124084319-g4al01wlwnxf8irh --- openerp/addons/base/rng/view.rng | 1 + 1 file changed, 1 insertion(+) 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 @@ + From 09706b5dffe63bddec1cf6a60f8c2ec8684a890f Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Fri, 24 Jan 2014 11:09:21 +0100 Subject: [PATCH 8/9] [IMP] adds 'W' in front of the week number when formatting date grouped by weeks (method read_group) (orm.py) bzr revid: ged@openerp.com-20140124100921-w5gq1fwidm01xx5l --- openerp/osv/orm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index b316b50e654..661201dc0e7 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -2679,7 +2679,7 @@ class BaseModel(object): if interval == 'day': display_format = 'dd MMMM YYYY' elif interval == 'week': - display_format = 'w' + display_format = "'W'w" elif interval == 'month': display_format = 'MMMM' elif interval == 'quarter': From b2f27dabf018fe8876fd30a1b1fde8605c49c509 Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Fri, 24 Jan 2014 14:28:34 +0100 Subject: [PATCH 9/9] [REF] implifies the read_group method by removing an useless dictionary (orm.py) bzr revid: ged@openerp.com-20140124132834-evx3dikqc94m48ed --- openerp/osv/orm.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 661201dc0e7..3456d5103bc 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -2687,10 +2687,6 @@ class BaseModel(object): elif interval == 'year': display_format = 'YYYY' - group_by_params = { - 'display_format': display_format, - 'interval': interval, - } qualified_groupby_field = "date_trunc('%s',%s)" % (interval, qualified_groupby_field) flist = "%s as %s " % (qualified_groupby_field, groupby) elif groupby_type == 'boolean': @@ -2759,15 +2755,15 @@ class BaseModel(object): _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')) + groupby_datetime, format=display_format, locale=context.get('lang', 'en_US')) domain_dt_begin = groupby_datetime - if group_by_params.get('interval') == 'quarter': + if interval == 'quarter': domain_dt_end = groupby_datetime + dateutil.relativedelta.relativedelta(months=3) - elif group_by_params.get('interval') == 'month': + elif interval == 'month': domain_dt_end = groupby_datetime + dateutil.relativedelta.relativedelta(months=1) - elif group_by_params.get('interval') == 'week': + elif interval == 'week': domain_dt_end = groupby_datetime + datetime.timedelta(days=7) - elif group_by_params.get('interval') == 'day': + elif interval == 'day': domain_dt_end = groupby_datetime + datetime.timedelta(days=1) else: domain_dt_end = groupby_datetime + dateutil.relativedelta.relativedelta(years=1)