[IMP] groupby domains & unformated dates
* ensure groupby domains are normalised, that's significantly more
convenient for subsequent manipulations and combinations (e.g. OR-ing
domains of multiple groups)
* split off "raw" groupby which provides ISO-8601 date and datetime
ranges[0] (half-open) as values for grouped date/datetime fields
for instance when grouping weekwise on a date field, the group
including May 11th, 1930 would have the following value for
the (grouped) date field: `("1930-05-05/1930-05-12", "W19 1930")`[1]
this means callers can access the programmatic date range value
* fix website_mail_channel and website_blog to *not* go and randomly dig
into the domains assuming a bunch of unspecified stuff about its
formatting (such as it not being normalized)
[0] https://en.wikipedia.org/wiki/ISO_8601#Time_intervals
[1] actual week number turns out to be locale-dependent...
This commit is contained in:
@@ -50,18 +50,28 @@ class WebsiteBlog(http.Controller):
|
||||
_post_comment_per_page = 10
|
||||
|
||||
def nav_list(self, blog=None):
|
||||
blog_post_obj = request.registry['blog.post']
|
||||
domain = blog and [('blog_id', '=', blog.id)] or []
|
||||
groups = blog_post_obj.read_group(
|
||||
request.cr, request.uid, domain, ['name', 'create_date'],
|
||||
groupby="create_date", orderby="create_date desc", context=request.context)
|
||||
blog_post_obj = request.env['blog.post']
|
||||
groups = blog_post_obj._read_group_raw(
|
||||
blog and [('blog_id', '=', blog.id)] or [],
|
||||
['name', 'create_date'],
|
||||
groupby=["create_date"], orderby="create_date desc")
|
||||
for group in groups:
|
||||
begin_date = datetime.datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
|
||||
end_date = datetime.datetime.strptime(group['__domain'][1][2], tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
|
||||
group['date_begin'] = '%s' % datetime.date.strftime(begin_date, tools.DEFAULT_SERVER_DATE_FORMAT)
|
||||
group['date_end'] = '%s' % datetime.date.strftime(end_date, tools.DEFAULT_SERVER_DATE_FORMAT)
|
||||
(r, label) = group['create_date']
|
||||
start, end = r.split('/')
|
||||
group['create_date'] = label
|
||||
group['date_begin'] = self._to_date(start)
|
||||
group['date_end'] = self._to_date(end)
|
||||
return groups
|
||||
|
||||
def _to_date(self, dt):
|
||||
""" create_date is a datetime so start and end are datetime strings,
|
||||
but we just want date strings
|
||||
"""
|
||||
return (datetime.datetime
|
||||
.strptime(dt, tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
.date() # may be unnecessary?
|
||||
.strftime(tools.DEFAULT_SERVER_DATE_FORMAT))
|
||||
|
||||
@http.route([
|
||||
'/blog',
|
||||
'/blog/page/<int:page>',
|
||||
|
||||
@@ -14,17 +14,27 @@ class MailGroup(http.Controller):
|
||||
_replies_per_page = 10
|
||||
|
||||
def _get_archives(self, group_id):
|
||||
MailMessage = request.registry['mail.message']
|
||||
groups = MailMessage.read_group(
|
||||
request.cr, request.uid, [('model', '=', 'mail.channel'), ('res_id', '=', group_id)], ['subject', 'date'],
|
||||
groupby="date", orderby="date desc", context=request.context)
|
||||
MailMessage = request.env['mail.message']
|
||||
groups = MailMessage._read_group_raw(
|
||||
[('model', '=', 'mail.channel'), ('res_id', '=', group_id)], ['subject', 'date'],
|
||||
groupby=["date"], orderby="date desc")
|
||||
for group in groups:
|
||||
begin_date = datetime.datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
|
||||
end_date = datetime.datetime.strptime(group['__domain'][1][2], tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
|
||||
group['date_begin'] = '%s' % datetime.date.strftime(begin_date, tools.DEFAULT_SERVER_DATE_FORMAT)
|
||||
group['date_end'] = '%s' % datetime.date.strftime(end_date, tools.DEFAULT_SERVER_DATE_FORMAT)
|
||||
(r, label) = group['date']
|
||||
start, end = r.split('/')
|
||||
group['date'] = label
|
||||
group['date_begin'] = self._to_date(start)
|
||||
group['date_end'] = self._to_date(end)
|
||||
return groups
|
||||
|
||||
def _to_date(self, dt):
|
||||
""" date is (of course) a datetime so start and end are datetime
|
||||
strings, but we just want date strings
|
||||
"""
|
||||
return (datetime.datetime
|
||||
.strptime(dt, tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
.date() # may be unnecessary?
|
||||
.strftime(tools.DEFAULT_SERVER_DATE_FORMAT))
|
||||
|
||||
@http.route("/groups", type='http', auth="public", website=True)
|
||||
def view(self, **post):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
|
||||
+78
-28
@@ -2058,37 +2058,68 @@ class BaseModel(object):
|
||||
value = pytz.timezone(context['tz']).localize(value)
|
||||
return value
|
||||
|
||||
def _read_group_get_domain(self, groupby, value):
|
||||
"""
|
||||
Helper method to construct the domain corresponding to a groupby and
|
||||
a given value. This is mostly relevant for date/datetime.
|
||||
"""
|
||||
if groupby['type'] in ('date', 'datetime') and value:
|
||||
dt_format = DEFAULT_SERVER_DATETIME_FORMAT if groupby['type'] == 'datetime' else DEFAULT_SERVER_DATE_FORMAT
|
||||
domain_dt_begin = value
|
||||
domain_dt_end = value + groupby['interval']
|
||||
if groupby['tz_convert']:
|
||||
domain_dt_begin = domain_dt_begin.astimezone(pytz.utc)
|
||||
domain_dt_end = domain_dt_end.astimezone(pytz.utc)
|
||||
return [(groupby['field'], '>=', domain_dt_begin.strftime(dt_format)),
|
||||
(groupby['field'], '<', domain_dt_end.strftime(dt_format))]
|
||||
if groupby['type'] == 'many2one' and value:
|
||||
value = value[0]
|
||||
return [(groupby['field'], '=', value)]
|
||||
|
||||
def _read_group_format_result(self, data, annotated_groupbys, groupby, groupby_dict, domain, context):
|
||||
def _read_group_format_result(self, data, annotated_groupbys, groupby, domain, context):
|
||||
"""
|
||||
Helper method to format the data contained in the dictionary data by
|
||||
adding the domain corresponding to its values, the groupbys in the
|
||||
context and by properly formatting the date/datetime values.
|
||||
"""
|
||||
domain_group = [dom for gb in annotated_groupbys for dom in self._read_group_get_domain(gb, data[gb['groupby']])]
|
||||
for k,v in data.iteritems():
|
||||
gb = groupby_dict.get(k)
|
||||
if gb and gb['type'] in ('date', 'datetime') and v:
|
||||
data[k] = babel.dates.format_date(v, format=gb['display_format'], locale=context.get('lang', 'en_US'))
|
||||
context and by properly formatting the date/datetime values.
|
||||
|
||||
data['__domain'] = domain_group + domain
|
||||
:param data: a single group
|
||||
:param annotated_groupbys: expanded grouping metainformation
|
||||
:param groupby: original grouping metainformation
|
||||
:param domain: original domain for read_group
|
||||
"""
|
||||
|
||||
sections = []
|
||||
for gb in annotated_groupbys:
|
||||
ftype = gb['type']
|
||||
value = data[gb['groupby']]
|
||||
|
||||
# full domain for this groupby spec
|
||||
d = None
|
||||
if value:
|
||||
if ftype == 'many2one':
|
||||
value = value[0]
|
||||
elif ftype in ('date', 'datetime'):
|
||||
locale = context.get('lang', 'en_US')
|
||||
fmt = DEFAULT_SERVER_DATETIME_FORMAT if ftype == 'datetime' else DEFAULT_SERVER_DATE_FORMAT
|
||||
tzinfo = None
|
||||
range_start = value
|
||||
range_end = value + gb['interval']
|
||||
# value from postgres is in local tz (so range is
|
||||
# considered in local tz e.g. "day" is [00:00, 00:00[
|
||||
# local rather than UTC which could be [11:00, 11:00]
|
||||
# local) but domain and raw value should be in UTC
|
||||
if gb['tz_convert']:
|
||||
tzinfo = range_start.tzinfo
|
||||
range_start = range_start.astimezone(pytz.utc)
|
||||
range_end = range_end.astimezone(pytz.utc)
|
||||
|
||||
range_start = range_start.strftime(fmt)
|
||||
range_end = range_end.strftime(fmt)
|
||||
if ftype == 'datetime':
|
||||
label = babel.dates.format_datetime(
|
||||
value, format=gb['display_format'],
|
||||
tzinfo=tzinfo, locale=locale
|
||||
)
|
||||
else:
|
||||
label = babel.dates.format_date(
|
||||
value, format=gb['display_format'],
|
||||
locale=locale
|
||||
)
|
||||
data[gb['groupby']] = ('%s/%s' % (range_start, range_end), label)
|
||||
d = [
|
||||
'&',
|
||||
(gb['field'], '>=', range_start),
|
||||
(gb['field'], '<', range_end),
|
||||
]
|
||||
|
||||
if d is None:
|
||||
d = [(gb['field'], '=', value)]
|
||||
sections.append(d)
|
||||
sections.append(domain)
|
||||
|
||||
data['__domain'] = expression.AND(sections)
|
||||
if len(groupby) - len(annotated_groupbys) >= 1:
|
||||
data['__context'] = { 'group_by': groupby[len(annotated_groupbys):]}
|
||||
del data['id']
|
||||
@@ -2126,6 +2157,25 @@ class BaseModel(object):
|
||||
:raise AccessError: * if user has no read rights on the requested object
|
||||
* if user tries to bypass access rules for read on the requested object
|
||||
"""
|
||||
if context is None:
|
||||
context = context
|
||||
result = self._read_group_raw(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby, lazy=lazy)
|
||||
|
||||
groupby = [groupby] if isinstance(groupby, basestring) else groupby
|
||||
dt = [
|
||||
f for f in groupby
|
||||
if self._fields[f.split(':')[0]].type in ('date', 'datetime')
|
||||
]
|
||||
|
||||
# iterate on all results and replace the "full" date/datetime value
|
||||
# (range, label) by just the formatted label, in-place
|
||||
for group in result:
|
||||
for df in dt:
|
||||
if df in group:
|
||||
group[df] = group[df][1]
|
||||
return result
|
||||
|
||||
def _read_group_raw(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False, lazy=True):
|
||||
if context is None:
|
||||
context = {}
|
||||
self.check_access_rights(cr, uid, 'read')
|
||||
@@ -2215,7 +2265,7 @@ class BaseModel(object):
|
||||
d.update(data_dict[d['id']])
|
||||
|
||||
data = map(lambda r: {k: self._read_group_prepare_data(k,v, groupby_dict, context) for k,v in r.iteritems()}, fetched_data)
|
||||
result = [self._read_group_format_result(d, annotated_groupbys, groupby, groupby_dict, domain, context) for d in data]
|
||||
result = [self._read_group_format_result(d, annotated_groupbys, groupby, domain, context) for d in data]
|
||||
if lazy and groupby_fields[0] in self._group_by_full:
|
||||
# Right now, read_group only fill results in lazy mode (by default).
|
||||
# If you need to have the empty groups in 'eager' mode, then the
|
||||
|
||||
Reference in New Issue
Block a user