From 2a0df08df9ee2ca1d7b473467d0b41700d5991e3 Mon Sep 17 00:00:00 2001 From: jbeficent Date: Wed, 28 Oct 2015 14:19:29 +0100 Subject: [PATCH] [IMP] cherry pick commits in V8 from a809796 to 09d69b9 and adapt the code to v7 --- mis_builder/__openerp__.py | 4 - .../{8.0.0.2 => 7.0.0.2}/pre-migration.py | 0 mis_builder/models/mis_builder.py | 165 ++++-------------- .../report/report_mis_report_instance.py | 67 ------- .../report/report_mis_report_instance.xml | 55 ------ mis_builder/static/src/img/icon.png | Bin 3464 -> 9455 bytes 6 files changed, 33 insertions(+), 258 deletions(-) rename mis_builder/migrations/{8.0.0.2 => 7.0.0.2}/pre-migration.py (100%) delete mode 100644 mis_builder/report/report_mis_report_instance.py delete mode 100644 mis_builder/report/report_mis_report_instance.xml diff --git a/mis_builder/__openerp__.py b/mis_builder/__openerp__.py index f57cb253..8a6a1d7c 100644 --- a/mis_builder/__openerp__.py +++ b/mis_builder/__openerp__.py @@ -29,14 +29,10 @@ 'summary': """ Build 'Management Information System' Reports and Dashboards """, -<<<<<<< HEAD 'description': """ """, - 'author': 'ACSONE SA/NV', -======= 'author': 'ACSONE SA/NV,' 'Odoo Community Association (OCA)', ->>>>>>> 98a321c... [FIX] Add OCA as author 'website': 'http://acsone.eu', 'depends': [ 'account', diff --git a/mis_builder/migrations/8.0.0.2/pre-migration.py b/mis_builder/migrations/7.0.0.2/pre-migration.py similarity index 100% rename from mis_builder/migrations/8.0.0.2/pre-migration.py rename to mis_builder/migrations/7.0.0.2/pre-migration.py diff --git a/mis_builder/models/mis_builder.py b/mis_builder/models/mis_builder.py index 5505b6ce..8de9219e 100644 --- a/mis_builder/models/mis_builder.py +++ b/mis_builder/models/mis_builder.py @@ -22,13 +22,9 @@ # ############################################################################## -<<<<<<< HEAD -from datetime import datetime, timedelta -from dateutil import parser -======= import datetime import dateutil ->>>>>>> 09d69b9... [IMP] mis_builder: improve eval context of query domain +from dateutil import parser import logging import re import time @@ -61,18 +57,13 @@ def _get_selection_label(selection, value): def _utc_midnight(d, tz_name, add_day=0): -<<<<<<< HEAD - d = datetime.strptime(d, tools.DEFAULT_SERVER_DATE_FORMAT) -======= - d = fields.Datetime.from_string(d) + datetime.timedelta(days=add_day) ->>>>>>> 09d69b9... [IMP] mis_builder: improve eval context of query domain + d = datetime.datetime.strptime(d, tools.DEFAULT_SERVER_DATE_FORMAT) utc_tz = pytz.timezone('UTC') if add_day: - d = d + timedelta(days=add_day) + d = d + datetime.timedelta(days=add_day) context_tz = pytz.timezone(tz_name) local_timestamp = context_tz.localize(d, is_dst=False) - return datetime.strftime(local_timestamp.astimezone(utc_tz), - tools.DEFAULT_SERVER_DATETIME_FORMAT) + return datetime.datetime.strftime(local_timestamp.astimezone(utc_tz)) def _python_var(var_str): @@ -83,9 +74,6 @@ def _is_valid_python_var(name): return re.match("[_A-Za-z][_a-zA-Z0-9]*$", name) -<<<<<<< HEAD -class MisReportKpi(orm.Model): -======= def _sum(l): if not l: return None @@ -110,8 +98,7 @@ def _max(l): return max(l) -class MisReportKpi(models.Model): ->>>>>>> 4cd4a14... [FIX] mis_builder: do not crash on aggregate queries with no results +class MisReportKpi(orm.Model): """ A KPI is an element (ie a line) of a MIS report. In addition to a name and description, it has an expression @@ -402,7 +389,6 @@ class MisReportInstancePeriod(orm.Model): are defined as an offset relative to a pivot date. """ -<<<<<<< HEAD def _get_dates(self, cr, uid, ids, field_names, arg, context=None): if isinstance(ids, (int, long)): ids = [ids] @@ -410,17 +396,20 @@ class MisReportInstancePeriod(orm.Model): for c in self.browse(cr, uid, ids, context=context): period_ids = None valid = True + date_from = False + date_to = False d = parser.parse(c.report_instance_id.pivot_date) if c.type == 'd': - date_from = d + timedelta(days=c.offset) - date_to = date_from + timedelta(days=c.duration - 1) + date_from = d + datetime.timedelta(days=c.offset) + date_to = date_from + datetime.timedelta(days=c.duration - 1) date_from = date_from.strftime( tools.DEFAULT_SERVER_DATE_FORMAT) date_to = date_to.strftime(tools.DEFAULT_SERVER_DATE_FORMAT) elif c.type == 'w': - date_from = d - timedelta(d.weekday()) - date_from = date_from + timedelta(days=c.offset * 7) - date_to = date_from + timedelta(days=(7 * c.duration) - 1) + date_from = d - datetime.timedelta(d.weekday()) + date_from = date_from + datetime.timedelta(days=c.offset * 7) + date_to = date_from + datetime.timedelta( + days=(7 * c.duration) - 1) date_from = date_from.strftime( tools.DEFAULT_SERVER_DATE_FORMAT) date_to = date_to.strftime(tools.DEFAULT_SERVER_DATE_FORMAT) @@ -428,41 +417,6 @@ class MisReportInstancePeriod(orm.Model): period_obj = self.pool['account.period'] current_period_ids = period_obj.search( cr, uid, -======= - @api.one - @api.depends('report_instance_id.pivot_date', 'type', 'offset', 'duration') - def _compute_dates(self): - self.date_from = False - self.date_to = False - self.period_from = False - self.period_to = False - self.valid = False - d = fields.Date.from_string(self.report_instance_id.pivot_date) - if self.type == 'd': - date_from = d + datetime.timedelta(days=self.offset) - date_to = date_from + \ - datetime.timedelta(days=self.duration - 1) - self.date_from = fields.Date.to_string(date_from) - self.date_to = fields.Date.to_string(date_to) - self.valid = True - elif self.type == 'w': - date_from = d - datetime.timedelta(d.weekday()) - date_from = date_from + datetime.timedelta(days=self.offset * 7) - date_to = date_from + \ - datetime.timedelta(days=(7 * self.duration) - 1) - self.date_from = fields.Date.to_string(date_from) - self.date_to = fields.Date.to_string(date_to) - self.valid = True - elif self.type == 'fp': - current_periods = self.env['account.period'].search( - [('special', '=', False), - ('date_start', '<=', d), - ('date_stop', '>=', d), - ('company_id', '=', - self.report_instance_id.company_id.id)]) - if current_periods: - all_periods = self.env['account.period'].search( ->>>>>>> 09d69b9... [IMP] mis_builder: improve eval context of query domain [('special', '=', False), ('date_start', '<=', d), ('date_stop', '>=', d), @@ -589,48 +543,42 @@ class MisReportInstancePeriod(orm.Model): def _fetch_queries(self, cr, uid, c, context): res = {} -<<<<<<< HEAD report = c.report_instance_id.report_id + query_obj = self.pool['mis.report.query'] for query in report.query_ids: obj = self.pool[query.model_id.model] - domain = query.domain and safe_eval(query.domain) or [] -======= - for query in self.report_instance_id.report_id.query_ids: - model = self.env[query.model_id.model] -<<<<<<< HEAD - domain = query.domain and safe_eval( - query.domain, - {'uid': self._uid, 'context': self._context}) or [] ->>>>>>> 4bc6080... Add ability to use ('user_id', '=', uid) in a domain -======= eval_context = { - 'env': self.env, 'time': time, 'datetime': datetime, 'dateutil': dateutil, # deprecated - 'uid': self.env.uid, - 'context': self.env.context, + 'uid': uid, + 'context': context, } + + if not c.date_from or not c.date_to: + raise orm.except_orm(_('Error!'), + _('Please define From and To dates for ' + 'period %s.') % c.name) domain = query.domain and \ safe_eval(query.domain, eval_context) or [] ->>>>>>> 09d69b9... [IMP] mis_builder: improve eval context of query domain + domain.extend(query_obj._get_additional_filter(cr, uid, + query.id, + context=context)) if query.date_field.ttype == 'date': domain.extend([(query.date_field.name, '>=', c.date_from), (query.date_field.name, '<=', c.date_to)]) else: + tz = context.get('tz', False) or 'UTC' datetime_from = _utc_midnight( - c.date_from, context.get('tz', 'UTC')) + c.date_from, tz) datetime_to = _utc_midnight( - c.date_to, context.get('tz', 'UTC'), add_day=1) + c.date_to, tz, add_day=1) domain.extend([(query.date_field.name, '>=', datetime_from), (query.date_field.name, '<', datetime_to)]) -<<<<<<< HEAD if obj._columns.get('company_id', False): domain.extend(['|', ('company_id', '=', False), ('company_id', '=', c.company_id.id)]) -======= ->>>>>>> 57a9bbb... [FIX] mis_builder: do not arbitrarily filter on company in queries field_names = [f.name for f in query.field_ids] if not query.aggregate: obj_ids = obj.search(cr, uid, domain, context=context) @@ -651,11 +599,11 @@ class MisReportInstancePeriod(orm.Model): cr, uid, obj_ids, field_names, context=context) s = AutoStruct(count=len(data)) if query.aggregate == 'min': - agg = _min + agg = min elif query.aggregate == 'max': - agg = _max + agg = max elif query.aggregate == 'avg': - agg = _avg + agg = lambda l: sum(l) / float(len(l)) for field_name in field_names: setattr(s, field_name, agg([d[field_name] for d in data])) @@ -834,57 +782,11 @@ class MisReportInstance(orm.Model): context=context) return res -<<<<<<< HEAD def preview(self, cr, uid, ids, context=None): assert len(ids) == 1 view_id = self.pool['ir.model.data'].get_object_reference( cr, uid, 'mis_builder', 'mis_report_instance_result_view_form')[1] -======= - name = fields.Char(required=True, - string='Name', translate=True) - description = fields.Char(required=False, - string='Description', translate=True) - date = fields.Date(string='Base date', - help='Report base date ' - '(leave empty to use current date)') - pivot_date = fields.Date(compute='_compute_pivot_date', - string="Pivot date") - report_id = fields.Many2one('mis.report', - required=True, - string='Report') - period_ids = fields.One2many('mis.report.instance.period', - 'report_instance_id', - required=True, - string='Periods') - target_move = fields.Selection([('posted', 'All Posted Entries'), - ('all', 'All Entries')], - string='Target Moves', - required=True, - default='posted') - company_id = fields.Many2one(comodel_name='res.company', - string='Company', - readonly=True, - related='root_account.company_id', - store=True) - root_account = fields.Many2one(comodel_name='account.account', - domain='[("parent_id", "=", False)]', - string="Account chart", - required=True) - landscape_pdf = fields.Boolean(string='Landscape PDF') - - def _format_date(self, lang_id, date): - # format date following user language - date_format = self.env['res.lang'].browse(lang_id).date_format - return datetime.datetime.strftime( - fields.Date.from_string(date), date_format) - - @api.multi - def preview(self): - assert len(self) == 1 - view_id = self.env.ref('mis_builder.' - 'mis_report_instance_result_view_form') ->>>>>>> 09d69b9... [IMP] mis_builder: improve eval context of query domain return { 'type': 'ir.actions.act_window', 'res_model': 'mis.report.instance', @@ -899,10 +801,9 @@ class MisReportInstance(orm.Model): # format date following user language tformat = self.pool['res.lang'].read( cr, uid, lang_id, ['date_format'])[0]['date_format'] - return datetime.strftime(datetime.strptime( - date, - tools.DEFAULT_SERVER_DATE_FORMAT), - tformat) + date = datetime.datetime.strptime(date, + tools.DEFAULT_SERVER_DATE_FORMAT) + return date.strftime(tformat) def compute(self, cr, uid, _id, context=None): assert isinstance(_id, (int, long)) diff --git a/mis_builder/report/report_mis_report_instance.py b/mis_builder/report/report_mis_report_instance.py deleted file mode 100644 index 9305081c..00000000 --- a/mis_builder/report/report_mis_report_instance.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# mis_builder module for Odoo, Management Information System Builder -# Copyright (C) 2014-2015 ACSONE SA/NV () -# -# This file is a part of mis_builder -# -# mis_builder is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License v3 or later -# as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# mis_builder is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License v3 or later for more details. -# -# You should have received a copy of the GNU Affero General Public License -# v3 or later along with this program. -# If not, see . -# -############################################################################## - -import logging - -from openerp import api, models - -_logger = logging.getLogger(__name__) - - -class ReportMisReportInstance(models.AbstractModel): - - _name = 'report.mis_builder.report_mis_report_instance' - - @api.multi - def render_html(self, data=None): - docs = self.env['mis.report.instance'].browse(self._ids) - docs_computed = {} - for doc in docs: - docs_computed[doc.id] = doc.compute() - docargs = { - 'doc_ids': self._ids, - 'doc_model': 'mis.report.instance', - 'docs': docs, - 'docs_computed': docs_computed, - } - return self.env['report'].\ - render('mis_builder.report_mis_report_instance', docargs) - - -class Report(models.Model): - _inherit = "report" - - @api.v7 - def get_pdf(self, cr, uid, ids, report_name, html=None, data=None, - context=None): - if ids: - report = self._get_report_from_name(cr, uid, report_name) - obj = self.pool[report.model].browse(cr, uid, ids, - context=context)[0] - context = context.copy() - if hasattr(obj, 'landscape_pdf') and obj.landscape_pdf: - context.update({'landscape': True}) - return super(Report, self).get_pdf(cr, uid, ids, report_name, - html=html, data=data, - context=context) diff --git a/mis_builder/report/report_mis_report_instance.xml b/mis_builder/report/report_mis_report_instance.xml deleted file mode 100644 index 3d8de2be..00000000 --- a/mis_builder/report/report_mis_report_instance.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - diff --git a/mis_builder/static/src/img/icon.png b/mis_builder/static/src/img/icon.png index 833ce9211046568a692f342c1d62fb10e134d4df..3a0328b516c4980e8e44cdb63fd945757ddd132d 100644 GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 3464 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