From fb5ba3bce4ad94aadd267c54c25d439b36b5e21d Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 9 Sep 2020 12:52:32 +0200 Subject: [PATCH] pre-commit --- bi_view_editor/__manifest__.py | 42 +- bi_view_editor/hooks.py | 34 +- bi_view_editor/models/bve_view.py | 622 ++++++++++-------- bi_view_editor/models/bve_view_line.py | 101 ++- bi_view_editor/models/ir_model.py | 157 +++-- bi_view_editor/models/models.py | 9 +- bi_view_editor/security/rules.xml | 12 +- bi_view_editor/static/src/css/bve.css | 54 +- .../static/src/js/bi_view_editor.FieldList.js | 241 +++---- .../src/js/bi_view_editor.JoinNodeDialog.js | 35 +- .../static/src/js/bi_view_editor.ModelList.js | 157 +++-- .../static/src/js/bi_view_editor.js | 122 ++-- .../static/src/xml/bi_view_editor.xml | 177 +++-- bi_view_editor/templates/assets_template.xml | 33 +- bi_view_editor/tests/test_bi_view.py | 447 ++++++------- bi_view_editor/views/bve_view.xml | 182 +++-- .../wizard/wizard_ir_model_menu_create.py | 46 +- 17 files changed, 1373 insertions(+), 1098 deletions(-) diff --git a/bi_view_editor/__manifest__.py b/bi_view_editor/__manifest__.py index 49bb3094..0d617013 100644 --- a/bi_view_editor/__manifest__.py +++ b/bi_view_editor/__manifest__.py @@ -2,28 +2,24 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { - 'name': 'BI View Editor', - 'summary': 'Graphical BI views builder for Odoo', - 'images': ['static/description/main_screenshot.png'], - 'author': 'Onestein,Odoo Community Association (OCA)', - 'license': 'AGPL-3', - 'website': 'https://github.com/OCA/reporting-engine', - 'category': 'Reporting', - 'version': '12.0.1.0.0', - 'development_status': 'Beta', - 'depends': [ - 'web', + "name": "BI View Editor", + "summary": "Graphical BI views builder for Odoo", + "images": ["static/description/main_screenshot.png"], + "author": "Onestein,Odoo Community Association (OCA)", + "license": "AGPL-3", + "website": "https://github.com/OCA/reporting-engine", + "category": "Reporting", + "version": "12.0.1.0.0", + "development_status": "Beta", + "depends": ["web",], + "data": [ + "security/ir.model.access.csv", + "security/rules.xml", + "templates/assets_template.xml", + "views/bve_view.xml", ], - 'data': [ - 'security/ir.model.access.csv', - 'security/rules.xml', - 'templates/assets_template.xml', - 'views/bve_view.xml', - ], - 'qweb': [ - 'static/src/xml/bi_view_editor.xml' - ], - 'post_load': 'post_load', - 'uninstall_hook': 'uninstall_hook', - 'installable': True, + "qweb": ["static/src/xml/bi_view_editor.xml"], + "post_load": "post_load", + "uninstall_hook": "uninstall_hook", + "installable": True, } diff --git a/bi_view_editor/hooks.py b/bi_view_editor/hooks.py index dee77f94..6041677f 100644 --- a/bi_view_editor/hooks.py +++ b/bi_view_editor/hooks.py @@ -3,20 +3,17 @@ import logging -from odoo import SUPERUSER_ID -from odoo import api, modules - +from odoo import SUPERUSER_ID, api, modules from odoo.tools import existing_tables, topological_sort _logger = logging.getLogger(__name__) def _bi_view(_name): - return _name.startswith('x_bve.') + return _name.startswith("x_bve.") def post_load(): - def check_tables_exist(self, cr): """ Verify that all tables are present and try to initialize @@ -28,11 +25,11 @@ def post_load(): env = api.Environment(cr, SUPERUSER_ID, {}) table2model = { - model._table: name for name, model in env.items() + model._table: name + for name, model in env.items() if not model._abstract and not _bi_view(name) # here is the patch } - missing_tables = set(table2model).difference( - existing_tables(cr, table2model)) + missing_tables = set(table2model).difference(existing_tables(cr, table2model)) if missing_tables: missing = {table2model[table] for table in missing_tables} @@ -45,7 +42,8 @@ def post_load(): env[name].init() # check again, and log errors if tables are still missing missing_tables = set(table2model).difference( - existing_tables(cr, table2model)) + existing_tables(cr, table2model) + ) for table in missing_tables: _logger.error("Model %s has no table.", table2model[table]) @@ -55,18 +53,24 @@ def post_load(): def uninstall_hook(cr, registry): # delete dirty data that could cause problems # while re-installing the module - cr.execute(""" + cr.execute( + """ delete from ir_model where model like 'x_bve.%' - """) - cr.execute(""" + """ + ) + cr.execute( + """ delete from bve_view where model_name like 'x_bve.%' - """) - cr.execute(""" + """ + ) + cr.execute( + """ SELECT 'DROP VIEW ' || table_name FROM information_schema.views WHERE table_schema NOT IN ('pg_catalog', 'information_schema') AND table_name like 'x_bve_%' - """) + """ + ) results = list(cr.fetchall()) for result in results: cr.execute(result[0]) diff --git a/bi_view_editor/models/bve_view.py b/bi_view_editor/models/bve_view.py index 58ac76a8..4c6ba103 100644 --- a/bi_view_editor/models/bve_view.py +++ b/bi_view_editor/models/bve_view.py @@ -3,6 +3,7 @@ import base64 import json + import pydot from psycopg2.extensions import AsIs @@ -11,163 +12,160 @@ from odoo.exceptions import UserError, ValidationError class BveView(models.Model): - _name = 'bve.view' - _description = 'BI View Editor' + _name = "bve.view" + _description = "BI View Editor" - @api.depends('group_ids', 'group_ids.users') + @api.depends("group_ids", "group_ids.users") def _compute_users(self): for bve_view in self.sudo(): if bve_view.group_ids: - bve_view.user_ids = bve_view.group_ids.mapped('users') + bve_view.user_ids = bve_view.group_ids.mapped("users") else: - bve_view.user_ids = self.env['res.users'].sudo().search([]) + bve_view.user_ids = self.env["res.users"].sudo().search([]) - @api.depends('name') + @api.depends("name") def _compute_model_name(self): for bve_view in self: name = [x for x in bve_view.name.lower() if x.isalnum()] - model_name = ''.join(name).replace('_', '.').replace(' ', '.') - bve_view.model_name = 'x_bve.' + model_name + model_name = "".join(name).replace("_", ".").replace(" ", ".") + bve_view.model_name = "x_bve." + model_name def _compute_serialized_data(self): for bve_view in self: serialized_data = [] for line in bve_view.line_ids.sorted(key=lambda r: r.sequence): - serialized_data.append({ - 'sequence': line.sequence, - 'model_id': line.model_id.id, - 'id': line.field_id.id, - 'name': line.name, - 'model_name': line.model_id.name, - 'model': line.model_id.model, - 'type': line.ttype, - 'table_alias': line.table_alias, - 'description': line.description, - 'row': line.row, - 'column': line.column, - 'measure': line.measure, - 'list': line.in_list, - 'join_node': line.join_node, - 'relation': line.relation, - }) + serialized_data.append( + { + "sequence": line.sequence, + "model_id": line.model_id.id, + "id": line.field_id.id, + "name": line.name, + "model_name": line.model_id.name, + "model": line.model_id.model, + "type": line.ttype, + "table_alias": line.table_alias, + "description": line.description, + "row": line.row, + "column": line.column, + "measure": line.measure, + "list": line.in_list, + "join_node": line.join_node, + "relation": line.relation, + } + ) bve_view.data = json.dumps(serialized_data) def _inverse_serialized_data(self): for bve_view in self: line_ids = self._sync_lines_and_data(bve_view.data) - bve_view.write({'line_ids': line_ids}) + bve_view.write({"line_ids": line_ids}) name = fields.Char(required=True, copy=False) - model_name = fields.Char(compute='_compute_model_name', store=True) - note = fields.Text(string='Notes') - state = fields.Selection([ - ('draft', 'Draft'), - ('created', 'Created') - ], default='draft', copy=False) + model_name = fields.Char(compute="_compute_model_name", store=True) + note = fields.Text(string="Notes") + state = fields.Selection( + [("draft", "Draft"), ("created", "Created")], default="draft", copy=False + ) data = fields.Char( - compute='_compute_serialized_data', - inverse='_inverse_serialized_data', + compute="_compute_serialized_data", + inverse="_inverse_serialized_data", help="Use the special query builder to define the query " - "to generate your report dataset. " - "NOTE: To be edited, the query should be in 'Draft' status.") - line_ids = fields.One2many( - 'bve.view.line', - 'bve_view_id', - string='Lines') + "to generate your report dataset. " + "NOTE: To be edited, the query should be in 'Draft' status.", + ) + line_ids = fields.One2many("bve.view.line", "bve_view_id", string="Lines") field_ids = fields.One2many( - 'bve.view.line', - 'bve_view_id', - domain=['|', ('join_node', '=', -1), ('join_node', '=', False)], - string='Fields') + "bve.view.line", + "bve_view_id", + domain=["|", ("join_node", "=", -1), ("join_node", "=", False)], + string="Fields", + ) relation_ids = fields.One2many( - 'bve.view.line', - 'bve_view_id', - domain=[('join_node', '!=', -1), ('join_node', '!=', False)], - string='Relations') - action_id = fields.Many2one('ir.actions.act_window', string='Action') - view_id = fields.Many2one('ir.ui.view', string='View') + "bve.view.line", + "bve_view_id", + domain=[("join_node", "!=", -1), ("join_node", "!=", False)], + string="Relations", + ) + action_id = fields.Many2one("ir.actions.act_window", string="Action") + view_id = fields.Many2one("ir.ui.view", string="View") group_ids = fields.Many2many( - 'res.groups', - string='Groups', + "res.groups", + string="Groups", help="User groups allowed to see the generated report; " - "if NO groups are specified the report will be public " - "for everyone.") + "if NO groups are specified the report will be public " + "for everyone.", + ) user_ids = fields.Many2many( - 'res.users', - string='Users', - compute='_compute_users', - store=True) - query = fields.Text(compute='_compute_sql_query') + "res.users", string="Users", compute="_compute_users", store=True + ) + query = fields.Text(compute="_compute_sql_query") over_condition = fields.Text( - states={ - 'draft': [ - ('readonly', False), - ], - }, + states={"draft": [("readonly", False),],}, readonly=True, help="Condition to be inserted in the OVER part " - "of the ID's row_number function.\n" - "For instance, 'ORDER BY t1.id' would create " - "IDs ordered in the same way as t1's IDs; otherwise " - "IDs are assigned with no specific order.", + "of the ID's row_number function.\n" + "For instance, 'ORDER BY t1.id' would create " + "IDs ordered in the same way as t1's IDs; otherwise " + "IDs are assigned with no specific order.", ) - er_diagram_image = fields.Binary(compute='_compute_er_diagram_image') + er_diagram_image = fields.Binary(compute="_compute_er_diagram_image") _sql_constraints = [ - ('name_uniq', - 'unique(name)', - _('Custom BI View names must be unique!')), + ("name_uniq", "unique(name)", _("Custom BI View names must be unique!")), ] - @api.depends('line_ids') + @api.depends("line_ids") def _compute_er_diagram_image(self): for bve_view in self: - graph = pydot.Dot(graph_type='graph') + graph = pydot.Dot(graph_type="graph") table_model_map = {} for line in bve_view.field_ids: if line.table_alias not in table_model_map: table_alias_node = pydot.Node( - line.model_id.name + ' ' + line.table_alias, + line.model_id.name + " " + line.table_alias, style="filled", - shape='box', - fillcolor="#DDDDDD" + shape="box", + fillcolor="#DDDDDD", ) table_model_map[line.table_alias] = table_alias_node graph.add_node(table_model_map[line.table_alias]) field_node = pydot.Node( - line.table_alias + '.' + line.field_id.field_description, + line.table_alias + "." + line.field_id.field_description, label=line.description, style="filled", - fillcolor="green" + fillcolor="green", ) graph.add_node(field_node) - graph.add_edge(pydot.Edge( - table_model_map[line.table_alias], - field_node - )) + graph.add_edge( + pydot.Edge(table_model_map[line.table_alias], field_node) + ) for line in bve_view.relation_ids: field_description = line.field_id.field_description table_alias = line.table_alias diamond_node = pydot.Node( - line.ttype + ' ' + table_alias + '.' + field_description, - label=table_alias + '.' + field_description, + line.ttype + " " + table_alias + "." + field_description, + label=table_alias + "." + field_description, style="filled", - shape='diamond', - fillcolor="#D2D2FF" + shape="diamond", + fillcolor="#D2D2FF", ) graph.add_node(diamond_node) - graph.add_edge(pydot.Edge( - table_model_map[table_alias], - diamond_node, - labelfontcolor="#D2D2FF", - color="blue" - )) - graph.add_edge(pydot.Edge( - diamond_node, - table_model_map[line.join_node], - labelfontcolor="black", - color="blue" - )) + graph.add_edge( + pydot.Edge( + table_model_map[table_alias], + diamond_node, + labelfontcolor="#D2D2FF", + color="blue", + ) + ) + graph.add_edge( + pydot.Edge( + diamond_node, + table_model_map[line.join_node], + labelfontcolor="black", + color="blue", + ) + ) try: png_base64_image = base64.b64encode(graph.create_png()) bve_view.er_diagram_image = png_base64_image @@ -179,9 +177,9 @@ class BveView(models.Model): def _get_field_def(line): field_type = line.view_field_type - return '' % (line.name, field_type) + return ''.format(line.name, field_type) - bve_field_lines = self.field_ids.filtered('view_field_type') + bve_field_lines = self.field_ids.filtered("view_field_type") return list(map(_get_field_def, bve_field_lines)) def _create_tree_view_arch(self): @@ -189,134 +187,161 @@ class BveView(models.Model): def _get_field_attrs(line): attr = line.list_attr - res = attr and '%s="%s"' % (attr, line.description) or '' - return '' % (line.name, res) + res = attr and '{}="{}"'.format(attr, line.description) or "" + return ''.format(line.name, res) bve_field_lines = self.field_ids.filtered(lambda l: l.in_list) return list(map(_get_field_attrs, bve_field_lines)) def _create_bve_view(self): self.ensure_one() - View = self.env['ir.ui.view'].sudo() + View = self.env["ir.ui.view"].sudo() # delete old views - View.search([('model', '=', self.model_name)]).unlink() + View.search([("model", "=", self.model_name)]).unlink() # create views - View.create([{ - 'name': 'Pivot Analysis', - 'type': 'pivot', - 'model': self.model_name, - 'priority': 16, - 'arch': """ + View.create( + [ + { + "name": "Pivot Analysis", + "type": "pivot", + "model": self.model_name, + "priority": 16, + "arch": """ {} - """.format("".join(self._create_view_arch())) - }, { - 'name': 'Graph Analysis', - 'type': 'graph', - 'model': self.model_name, - 'priority': 16, - 'arch': """ + """.format( + "".join(self._create_view_arch()) + ), + }, + { + "name": "Graph Analysis", + "type": "graph", + "model": self.model_name, + "priority": 16, + "arch": """ {} - """.format("".join(self._create_view_arch())) - }, { - 'name': 'Search BI View', - 'type': 'search', - 'model': self.model_name, - 'priority': 16, - 'arch': """ + """.format( + "".join(self._create_view_arch()) + ), + }, + { + "name": "Search BI View", + "type": "search", + "model": self.model_name, + "priority": 16, + "arch": """ {} - """.format("".join(self._create_view_arch())) - }]) + """.format( + "".join(self._create_view_arch()) + ), + }, + ] + ) # create Tree view - tree_view = View.create({ - 'name': 'Tree Analysis', - 'type': 'tree', - 'model': self.model_name, - 'priority': 16, - 'arch': """ + tree_view = View.create( + { + "name": "Tree Analysis", + "type": "tree", + "model": self.model_name, + "priority": 16, + "arch": """ {} - """.format("".join(self._create_tree_view_arch())) - }) + """.format( + "".join(self._create_tree_view_arch()) + ), + } + ) # set the Tree view as the default one - action = self.env['ir.actions.act_window'].sudo().create({ - 'name': self.name, - 'res_model': self.model_name, - 'type': 'ir.actions.act_window', - 'view_type': 'form', - 'view_mode': 'tree,graph,pivot', - 'view_id': tree_view.id, - 'context': "{'service_name': '%s'}" % self.name, - }) - - self.write({ - 'action_id': action.id, - 'view_id': tree_view.id, - 'state': 'created' - }) + action = ( + self.env["ir.actions.act_window"] + .sudo() + .create( + { + "name": self.name, + "res_model": self.model_name, + "type": "ir.actions.act_window", + "view_type": "form", + "view_mode": "tree,graph,pivot", + "view_id": tree_view.id, + "context": "{'service_name': '%s'}" % self.name, + } + ) + ) + + self.write( + {"action_id": action.id, "view_id": tree_view.id, "state": "created"} + ) def _build_access_rules(self, model): self.ensure_one() if not self.group_ids: - self.env['ir.model.access'].sudo().create({ - 'name': 'read access to ' + self.model_name, - 'model_id': model.id, - 'perm_read': True, - }) + self.env["ir.model.access"].sudo().create( + { + "name": "read access to " + self.model_name, + "model_id": model.id, + "perm_read": True, + } + ) else: # read access only to model - access_vals = [{ - 'name': 'read access to ' + self.model_name, - 'model_id': model.id, - 'group_id': group.id, - 'perm_read': True - } for group in self.group_ids] - self.env['ir.model.access'].sudo().create(access_vals) + access_vals = [ + { + "name": "read access to " + self.model_name, + "model_id": model.id, + "group_id": group.id, + "perm_read": True, + } + for group in self.group_ids + ] + self.env["ir.model.access"].sudo().create(access_vals) def _create_sql_view(self): self.ensure_one() - view_name = self.model_name.replace('.', '_') - query = self.query and self.query.replace('\n', ' ') + view_name = self.model_name.replace(".", "_") + query = self.query and self.query.replace("\n", " ") # robustness in case something went wrong - self._cr.execute('DROP TABLE IF EXISTS %s', (AsIs(view_name), )) + self._cr.execute("DROP TABLE IF EXISTS %s", (AsIs(view_name),)) # create postgres view try: with self.env.cr.savepoint(): - self.env.cr.execute('CREATE or REPLACE VIEW %s as (%s)', ( - AsIs(view_name), AsIs(query), )) + self.env.cr.execute( + "CREATE or REPLACE VIEW %s as (%s)", (AsIs(view_name), AsIs(query),) + ) except Exception as e: raise UserError( - _("Error creating the view '{query}':\n{error}") - .format( - query=query, - error=e)) + _("Error creating the view '{query}':\n{error}").format( + query=query, error=e + ) + ) - @api.depends('line_ids', 'state', 'over_condition') + @api.depends("line_ids", "state", "over_condition") def _compute_sql_query(self): for bve_view in self: tables_map = {} - select_str = '\n CAST(row_number() OVER ({}) as integer) AS id' \ - .format(bve_view.over_condition or '') + select_str = "\n CAST(row_number() OVER ({}) as integer) AS id".format( + bve_view.over_condition or "" + ) for line in bve_view.field_ids: table = line.table_alias select = line.field_id.name as_name = line.name - select_str += ',\n {}.{} AS {}'.format(table, select, as_name) + select_str += ",\n {}.{} AS {}".format(table, select, as_name) if line.table_alias not in tables_map: table = self.env[line.field_id.model_id.model]._table @@ -339,45 +364,53 @@ class BveView(models.Model): from_str += " LEFT" if line.left_join else "" from_str += " JOIN {} ON {}.id = {}.{}".format( table_format, - line.join_node, line.table_alias, line.field_id.name + line.join_node, + line.table_alias, + line.field_id.name, ) if line.join_node not in seen: from_str += "\n" seen.add(line.join_node) from_str += " LEFT" if line.left_join else "" from_str += " JOIN {} AS {} ON {}.{} = {}.id".format( - tables_map[line.join_node], line.join_node, - line.table_alias, line.field_id.name, line.join_node + tables_map[line.join_node], + line.join_node, + line.table_alias, + line.field_id.name, + line.join_node, ) bve_view.query = """SELECT %s\n\nFROM %s - """ % (AsIs(select_str), AsIs(from_str),) + """ % ( + AsIs(select_str), + AsIs(from_str), + ) def action_translations(self): self.ensure_one() - if self.state != 'created': + if self.state != "created": return self = self.sudo() - model = self.env['ir.model'].search([('model', '=', self.model_name)]) - IrTranslation = self.env['ir.translation'] - IrTranslation.translate_fields('ir.model', model.id) + model = self.env["ir.model"].search([("model", "=", self.model_name)]) + IrTranslation = self.env["ir.translation"] + IrTranslation.translate_fields("ir.model", model.id) for field in model.field_id: - IrTranslation.translate_fields('ir.model.fields', field.id) + IrTranslation.translate_fields("ir.model.fields", field.id) return { - 'name': 'Translations', - 'res_model': 'ir.translation', - 'type': 'ir.actions.act_window', - 'view_mode': 'tree', - 'view_id': self.env.ref('base.view_translation_dialog_tree').id, - 'target': 'current', - 'flags': {'search_view': True, 'action_buttons': True}, - 'domain': [ - '|', - '&', - ('res_id', 'in', model.field_id.ids), - ('name', '=', 'ir.model.fields,field_description'), - '&', - ('res_id', '=', model.id), - ('name', '=', 'ir.model,name') + "name": "Translations", + "res_model": "ir.translation", + "type": "ir.actions.act_window", + "view_mode": "tree", + "view_id": self.env.ref("base.view_translation_dialog_tree").id, + "target": "current", + "flags": {"search_view": True, "action_buttons": True}, + "domain": [ + "|", + "&", + ("res_id", "in", model.field_id.ids), + ("name", "=", "ir.model.fields,field_description"), + "&", + ("res_id", "=", model.id), + ("name", "=", "ir.model,name"), ], } @@ -396,12 +429,19 @@ class BveView(models.Model): # create model and fields bve_fields = self.line_ids.filtered(lambda l: not l.join_node) - model = self.env['ir.model'].sudo().with_context(bve=True).create({ - 'name': self.name, - 'model': self.model_name, - 'state': 'manual', - 'field_id': [(0, 0, f) for f in bve_fields._prepare_field_vals()], - }) + model = ( + self.env["ir.model"] + .sudo() + .with_context(bve=True) + .create( + { + "name": self.name, + "model": self.model_name, + "state": "manual", + "field_id": [(0, 0, f) for f in bve_fields._prepare_field_vals()], + } + ) + ) # give access rights self._build_access_rules(model) @@ -415,57 +455,69 @@ class BveView(models.Model): if not self.group_ids: return - for line_model in self.line_ids.mapped('model_id'): - res_count = self.env['ir.model.access'].sudo().search([ - ('model_id', '=', line_model.id), - ('perm_read', '=', True), - '|', - ('group_id', '=', False), - ('group_id', 'in', self.group_ids.ids), - ], limit=1) + for line_model in self.line_ids.mapped("model_id"): + res_count = ( + self.env["ir.model.access"] + .sudo() + .search( + [ + ("model_id", "=", line_model.id), + ("perm_read", "=", True), + "|", + ("group_id", "=", False), + ("group_id", "in", self.group_ids.ids), + ], + limit=1, + ) + ) if not res_count: - access_records = self.env['ir.model.access'].sudo().search([ - ('model_id', '=', line_model.id), - ('perm_read', '=', True), - ]) - group_list = '' - for group in access_records.mapped('group_id'): - group_list += ' * %s\n' % (group.full_name, ) + access_records = ( + self.env["ir.model.access"] + .sudo() + .search( + [("model_id", "=", line_model.id), ("perm_read", "=", True),] + ) + ) + group_list = "" + for group in access_records.mapped("group_id"): + group_list += " * {}\n".format(group.full_name) msg_title = _( 'The model "%s" cannot be accessed by users with the ' - 'selected groups only.' % (line_model.name, )) - msg_details = _( - 'At least one of the following groups must be added:') - raise UserError(_( - '%s\n\n%s\n%s' % (msg_title, msg_details, group_list,) - )) + "selected groups only." % (line_model.name,) + ) + msg_details = _("At least one of the following groups must be added:") + raise UserError( + _("{}\n\n{}\n{}".format(msg_title, msg_details, group_list)) + ) def _check_invalid_lines(self): self.ensure_one() if not self.line_ids: - raise ValidationError(_('No data to process.')) + raise ValidationError(_("No data to process.")) if any(not line.model_id for line in self.line_ids): invalid_lines = self.line_ids.filtered(lambda l: not l.model_id) - missing_models = set(invalid_lines.mapped('model_name')) - missing_models = ', '.join(missing_models) - raise ValidationError(_( - 'Following models are missing: %s.\n' - 'Probably some modules were uninstalled.' % (missing_models,) - )) + missing_models = set(invalid_lines.mapped("model_name")) + missing_models = ", ".join(missing_models) + raise ValidationError( + _( + "Following models are missing: %s.\n" + "Probably some modules were uninstalled." % (missing_models,) + ) + ) if any(not line.field_id for line in self.line_ids): invalid_lines = self.line_ids.filtered(lambda l: not l.field_id) - missing_fields = set(invalid_lines.mapped('field_name')) - missing_fields = ', '.join(missing_fields) - raise ValidationError(_( - 'Following fields are missing: %s.' % (missing_fields,) - )) + missing_fields = set(invalid_lines.mapped("field_name")) + missing_fields = ", ".join(missing_fields) + raise ValidationError( + _("Following fields are missing: {}.".format(missing_fields)) + ) def open_view(self): self.ensure_one() self._check_invalid_lines() [action] = self.action_id.read() - action['display_name'] = _('BI View') + action["display_name"] = _("BI View") return action @api.multi @@ -479,10 +531,8 @@ class BveView(models.Model): has_menus = False if self.action_id: - action = 'ir.actions.act_window,%d' % (self.action_id.id,) - menus = self.env['ir.ui.menu'].search([ - ('action', '=', action) - ]) + action = "ir.actions.act_window,%d" % (self.action_id.id,) + menus = self.env["ir.ui.menu"].search([("action", "=", action)]) has_menus = True if menus else False menus.unlink() @@ -490,26 +540,26 @@ class BveView(models.Model): self.sudo().action_id.view_id.unlink() self.sudo().action_id.unlink() - self.env['ir.ui.view'].sudo().search( - [('model', '=', self.model_name)]).unlink() - models_to_delete = self.env['ir.model'].sudo().search([ - ('model', '=', self.model_name)]) + self.env["ir.ui.view"].sudo().search([("model", "=", self.model_name)]).unlink() + models_to_delete = ( + self.env["ir.model"].sudo().search([("model", "=", self.model_name)]) + ) if models_to_delete: models_to_delete.unlink() - table_name = self.model_name.replace('.', '_') + table_name = self.model_name.replace(".", "_") tools.drop_view_if_exists(self.env.cr, table_name) - self.state = 'draft' + self.state = "draft" if has_menus: - return {'type': 'ir.actions.client', 'tag': 'reload'} + return {"type": "ir.actions.client", "tag": "reload"} def unlink(self): - if self.filtered(lambda v: v.state == 'created'): + if self.filtered(lambda v: v.state == "created"): raise UserError( - _('You cannot delete a created view! ' - 'Reset the view to draft first.')) + _("You cannot delete a created view! " "Reset the view to draft first.") + ) return super().unlink() @api.model @@ -521,63 +571,69 @@ class BveView(models.Model): table_model_map = {} for item in fields_info: - if item.get('join_node', -1) == -1: - table_model_map[item['table_alias']] = item['model_id'] + if item.get("join_node", -1) == -1: + table_model_map[item["table_alias"]] = item["model_id"] for sequence, field_info in enumerate(fields_info, start=1): join_model_id = False - join_node = field_info.get('join_node', -1) + join_node = field_info.get("join_node", -1) if join_node != -1 and table_model_map.get(join_node): join_model_id = int(table_model_map[join_node]) - line_ids += [(0, False, { - 'sequence': sequence, - 'model_id': field_info['model_id'], - 'table_alias': field_info['table_alias'], - 'description': field_info['description'], - 'field_id': field_info['id'], - 'ttype': field_info['type'], - 'row': field_info['row'], - 'column': field_info['column'], - 'measure': field_info['measure'], - 'in_list': field_info['list'], - 'relation': field_info.get('relation'), - 'join_node': field_info.get('join_node'), - 'join_model_id': join_model_id, - })] + line_ids += [ + ( + 0, + False, + { + "sequence": sequence, + "model_id": field_info["model_id"], + "table_alias": field_info["table_alias"], + "description": field_info["description"], + "field_id": field_info["id"], + "ttype": field_info["type"], + "row": field_info["row"], + "column": field_info["column"], + "measure": field_info["measure"], + "in_list": field_info["list"], + "relation": field_info.get("relation"), + "join_node": field_info.get("join_node"), + "join_model_id": join_model_id, + }, + ) + ] return line_ids - @api.constrains('line_ids') + @api.constrains("line_ids") def _constraint_line_ids(self): models_with_tables = self.env.registry.models.keys() for view in self: nodes = view.line_ids.filtered(lambda n: n.join_node) - nodes_models = nodes.mapped('table_alias') - nodes_models += nodes.mapped('join_node') + nodes_models = nodes.mapped("table_alias") + nodes_models += nodes.mapped("join_node") not_nodes = view.line_ids.filtered(lambda n: not n.join_node) - not_nodes_models = not_nodes.mapped('table_alias') - err_msg = _('Inconsistent lines.') + not_nodes_models = not_nodes.mapped("table_alias") + err_msg = _("Inconsistent lines.") if set(nodes_models) - set(not_nodes_models): raise ValidationError(err_msg) if len(set(not_nodes_models) - set(nodes_models)) > 1: raise ValidationError(err_msg) - models = view.line_ids.mapped('model_id') + models = view.line_ids.mapped("model_id") if models.filtered(lambda m: m.model not in models_with_tables): - raise ValidationError(_('Abstract models not supported.')) + raise ValidationError(_("Abstract models not supported.")) @api.model def get_clean_list(self, data_dict): serialized_data = json.loads(data_dict) table_alias_list = set() for item in serialized_data: - if item.get('join_node', -1) in [-1, False]: - table_alias_list.add(item['table_alias']) + if item.get("join_node", -1) in [-1, False]: + table_alias_list.add(item["table_alias"]) for item in serialized_data: - if item.get('join_node', -1) not in [-1, False]: - if item['table_alias'] not in table_alias_list: + if item.get("join_node", -1) not in [-1, False]: + if item["table_alias"] not in table_alias_list: serialized_data.remove(item) - elif item['join_node'] not in table_alias_list: + elif item["join_node"] not in table_alias_list: serialized_data.remove(item) return json.dumps(serialized_data) diff --git a/bi_view_editor/models/bve_view_line.py b/bi_view_editor/models/bve_view_line.py index 17fc0d53..01eb6974 100644 --- a/bi_view_editor/models/bve_view_line.py +++ b/bi_view_editor/models/bve_view_line.py @@ -6,19 +6,19 @@ from odoo.exceptions import ValidationError class BveViewLine(models.Model): - _name = 'bve.view.line' - _description = 'BI View Editor Lines' + _name = "bve.view.line" + _description = "BI View Editor Lines" - name = fields.Char(compute='_compute_name') + name = fields.Char(compute="_compute_name") sequence = fields.Integer(default=1) - bve_view_id = fields.Many2one('bve.view', ondelete='cascade') - model_id = fields.Many2one('ir.model', string='Model') - model_name = fields.Char(compute='_compute_model_name', store=True) + bve_view_id = fields.Many2one("bve.view", ondelete="cascade") + model_id = fields.Many2one("ir.model", string="Model") + model_name = fields.Char(compute="_compute_model_name", store=True) table_alias = fields.Char() - join_model_id = fields.Many2one('ir.model', string='Join Model') - field_id = fields.Many2one('ir.model.fields', string='Field') - field_name = fields.Char(compute='_compute_model_field_name', store=True) - ttype = fields.Char(string='Type') + join_model_id = fields.Many2one("ir.model", string="Join Model") + field_id = fields.Many2one("ir.model.fields", string="Field") + field_name = fields.Char(compute="_compute_model_field_name", store=True) + ttype = fields.Char(string="Type") description = fields.Char(translate=True) relation = fields.Char() join_node = fields.Char() @@ -28,88 +28,87 @@ class BveViewLine(models.Model): column = fields.Boolean() measure = fields.Boolean() in_list = fields.Boolean() - list_attr = fields.Selection([ - ('sum', 'Sum'), - ('avg', 'Average'), - ], string='List Attribute', default='sum') - view_field_type = fields.Char(compute='_compute_view_field_type') + list_attr = fields.Selection( + [("sum", "Sum"), ("avg", "Average"),], string="List Attribute", default="sum" + ) + view_field_type = fields.Char(compute="_compute_view_field_type") - @api.depends('row', 'column', 'measure') + @api.depends("row", "column", "measure") def _compute_view_field_type(self): for line in self: - row = line.row and 'row' - column = line.column and 'col' - measure = line.measure and 'measure' + row = line.row and "row" + column = line.column and "col" + measure = line.measure and "measure" line.view_field_type = row or column or measure - @api.constrains('row', 'column', 'measure') + @api.constrains("row", "column", "measure") def _constrains_options_check(self): - measure_types = ['float', 'integer', 'monetary'] + measure_types = ["float", "integer", "monetary"] for line in self.filtered(lambda l: l.row or l.column): if line.join_model_id or line.ttype in measure_types: - err_msg = _('This field cannot be a row or a column.') + err_msg = _("This field cannot be a row or a column.") raise ValidationError(err_msg) for line in self.filtered(lambda l: l.measure): if line.join_model_id or line.ttype not in measure_types: - err_msg = _('This field cannot be a measure.') + err_msg = _("This field cannot be a measure.") raise ValidationError(err_msg) - @api.constrains('table_alias', 'field_id') + @api.constrains("table_alias", "field_id") def _constrains_unique_fields_check(self): seen = set() - for line in self.mapped('bve_view_id.field_ids'): - if (line.table_alias, line.field_id.id, ) not in seen: - seen.add((line.table_alias, line.field_id.id, )) + for line in self.mapped("bve_view_id.field_ids"): + if (line.table_alias, line.field_id.id,) not in seen: + seen.add((line.table_alias, line.field_id.id,)) else: - raise ValidationError(_('Field %s/%s is duplicated.\n' - 'Please remove the duplications.') % ( - line.field_id.model, line.field_id.name - )) + raise ValidationError( + _("Field %s/%s is duplicated.\n" "Please remove the duplications.") + % (line.field_id.model, line.field_id.name) + ) - @api.depends('field_id', 'sequence') + @api.depends("field_id", "sequence") def _compute_name(self): for line in self.filtered(lambda l: l.field_id): field_name = line.field_id.name - line.name = 'x_bve_%s_%s' % (line.table_alias, field_name,) + line.name = "x_bve_{}_{}".format(line.table_alias, field_name) - @api.depends('model_id') + @api.depends("model_id") def _compute_model_name(self): for line in self.filtered(lambda l: l.model_id): line.model_name = line.model_id.model - @api.depends('field_id') + @api.depends("field_id") def _compute_model_field_name(self): for line in self.filtered(lambda l: l.field_id): field_name = line.description model_name = line.model_name - line.field_name = '%s (%s)' % (field_name, model_name, ) + line.field_name = "{} ({})".format(field_name, model_name) def _prepare_field_vals(self): vals_list = [] for line in self: field = line.field_id vals = { - 'name': line.name, - 'complete_name': field.complete_name, - 'model': line.bve_view_id.model_name, - 'relation': field.relation, - 'field_description': line.description, - 'ttype': field.ttype, - 'selection': field.selection, - 'size': field.size, - 'state': 'manual', - 'readonly': True, - 'groups': [(6, 0, field.groups.ids)], + "name": line.name, + "complete_name": field.complete_name, + "model": line.bve_view_id.model_name, + "relation": field.relation, + "field_description": line.description, + "ttype": field.ttype, + "selection": field.selection, + "size": field.size, + "state": "manual", + "readonly": True, + "groups": [(6, 0, field.groups.ids)], } - if vals['ttype'] == 'monetary': - vals.update({'ttype': 'float'}) - if field.ttype == 'selection' and not field.selection: + if vals["ttype"] == "monetary": + vals.update({"ttype": "float"}) + if field.ttype == "selection" and not field.selection: model_obj = self.env[field.model_id.model] selection = model_obj._fields[field.name].selection if callable(selection): selection_domain = selection(model_obj) else: selection_domain = selection - vals.update({'selection': str(selection_domain)}) + vals.update({"selection": str(selection_domain)}) vals_list.append(vals) return vals_list diff --git a/bi_view_editor/models/ir_model.py b/bi_view_editor/models/ir_model.py index 52278f08..5e80ad2c 100644 --- a/bi_view_editor/models/ir_model.py +++ b/bi_view_editor/models/ir_model.py @@ -5,98 +5,88 @@ from collections import defaultdict from odoo import api, models, registry -NO_BI_MODELS = [ - 'fetchmail.server' -] +NO_BI_MODELS = ["fetchmail.server"] -NO_BI_TTYPES = [ - 'many2many', - 'one2many', - 'html', - 'binary', - 'reference' -] +NO_BI_TTYPES = ["many2many", "one2many", "html", "binary", "reference"] def dict_for_field(field): return { - 'id': field.id, - 'name': field.name, - 'description': field.field_description, - 'type': field.ttype, - 'relation': field.relation, - 'custom': False, - 'model_id': field.model_id.id, - 'model': field.model_id.model, - 'model_name': field.model_id.name + "id": field.id, + "name": field.name, + "description": field.field_description, + "type": field.ttype, + "relation": field.relation, + "custom": False, + "model_id": field.model_id.id, + "model": field.model_id.model, + "model_name": field.model_id.name, } def dict_for_model(model): - return { - 'id': model.id, - 'name': model.name, - 'model': model.model - } + return {"id": model.id, "name": model.name, "model": model.model} class IrModel(models.Model): - _inherit = 'ir.model' + _inherit = "ir.model" @api.model def _filter_bi_models(self, model): - def _check_name(model_model): if model_model in NO_BI_MODELS: return 1 return 0 def _check_startswith(model_model): - if model_model.startswith('workflow') or \ - model_model.startswith('ir.') or \ - model_model.startswith('base_'): + if ( + model_model.startswith("workflow") + or model_model.startswith("ir.") + or model_model.startswith("base_") + ): return 1 return 0 def _check_contains(model_model): - if 'mail' in model_model or \ - 'report' in model_model or \ - 'edi.' in model_model: + if ( + "mail" in model_model + or "report" in model_model + or "edi." in model_model + ): return 1 return 0 def _check_unknown(model_name): - if model_name == 'Unknown' or '.' in model_name: + if model_name == "Unknown" or "." in model_name: return 1 return 0 - model_model = model['model'] - model_name = model['name'] + model_model = model["model"] + model_name = model["name"] count_check = 0 count_check += _check_name(model_model) count_check += _check_startswith(model_model) count_check += _check_contains(model_model) count_check += _check_unknown(model_name) if not count_check: - return self.env['ir.model.access'].check( - model['model'], 'read', False) + return self.env["ir.model.access"].check(model["model"], "read", False) return False def get_model_list(self, model_table_map): if not model_table_map: return [] - domain = [('model_id', 'in', list(model_table_map.keys())), - ('store', '=', True), - ('ttype', '=', 'many2one')] - fields = self.env['ir.model.fields'].sudo().search(domain) + domain = [ + ("model_id", "in", list(model_table_map.keys())), + ("store", "=", True), + ("ttype", "=", "many2one"), + ] + fields = self.env["ir.model.fields"].sudo().search(domain) model_list = [] for field in fields: for table_alias in model_table_map[field.model_id.id]: - model_list.append(dict( - dict_for_field(field), - table_alias=table_alias, - join_node=-1, - )) + model_list.append( + dict(dict_for_field(field), table_alias=table_alias, join_node=-1,) + ) return model_list def get_relation_list(self, model_table_map): @@ -106,32 +96,31 @@ class IrModel(models.Model): for model in self.sudo().browse(model_table_map.keys()): model_names.update({model.model: model.id}) - domain = [('relation', 'in', list(model_names.keys())), - ('store', '=', True), - ('ttype', '=', 'many2one')] - fields = self.env['ir.model.fields'].sudo().search(domain) + domain = [ + ("relation", "in", list(model_names.keys())), + ("store", "=", True), + ("ttype", "=", "many2one"), + ] + fields = self.env["ir.model.fields"].sudo().search(domain) relation_list = [] for field in fields: model_id = model_names[field.relation] for join_node in model_table_map[model_id]: - relation_list.append(dict( - dict_for_field(field), - join_node=join_node, - table_alias=-1 - )) + relation_list.append( + dict(dict_for_field(field), join_node=join_node, table_alias=-1) + ) return relation_list @api.model def _get_related_models_domain(self, model_table_map): - domain = [('transient', '=', False)] + domain = [("transient", "=", False)] if model_table_map: model_list = self.get_model_list(model_table_map) relation_list = self.get_relation_list(model_table_map) - model_ids = [f['model_id'] for f in relation_list + model_list] + model_ids = [f["model_id"] for f in relation_list + model_list] model_ids += list(model_table_map.keys()) - relations = [f['relation'] for f in model_list] - domain += [ - '|', ('id', 'in', model_ids), ('model', 'in', relations)] + relations = [f["relation"] for f in model_list] + domain += ["|", ("id", "in", model_ids), ("model", "in", relations)] return domain @api.model @@ -140,7 +129,7 @@ class IrModel(models.Model): joined with the already selected models. """ domain = self._get_related_models_domain(model_table_map) - return self.sudo().search(domain, order='name asc') + return self.sudo().search(domain, order="name asc") @api.model def get_models(self, table_model_map=None): @@ -166,6 +155,7 @@ class IrModel(models.Model): Return all possible join nodes to add new_field to the query containing model_ids. """ + def remove_duplicate_nodes(join_nodes): seen = set() nodes_list = [] @@ -181,24 +171,24 @@ class IrModel(models.Model): keys = [] model_table_map = defaultdict(list) for field in field_data: - model_table_map[field['model_id']].append(field['table_alias']) - if field.get('join_node', -1) != -1: - keys.append((field['table_alias'], field['id'])) + model_table_map[field["model_id"]].append(field["table_alias"]) + if field.get("join_node", -1) != -1: + keys.append((field["table_alias"], field["id"])) # nodes in current model - existing_aliases = model_table_map[new_field['model_id']] - join_nodes = [{'table_alias': alias} for alias in existing_aliases] + existing_aliases = model_table_map[new_field["model_id"]] + join_nodes = [{"table_alias": alias} for alias in existing_aliases] # nodes in past selected models for field in self.get_model_list(model_table_map): - if new_field['model'] == field['relation']: - if (field['table_alias'], field['id']) not in keys: + if new_field["model"] == field["relation"]: + if (field["table_alias"], field["id"]) not in keys: join_nodes.append(field) # nodes in new model for field in self.get_relation_list(model_table_map): - if new_field['model_id'] == field['model_id']: - if (field['table_alias'], field['id']) not in keys: + if new_field["model_id"] == field["model_id"]: + if (field["table_alias"], field["id"]) not in keys: join_nodes.append(field) return remove_duplicate_nodes(join_nodes) @@ -207,29 +197,36 @@ class IrModel(models.Model): def get_fields(self, model_id): self = self.with_context(lang=self.env.user.lang) - fields = self.env['ir.model.fields'].sudo().search([ - ('model_id', '=', model_id), - ('store', '=', True), - ('name', 'not in', models.MAGIC_COLUMNS), - ('ttype', 'not in', NO_BI_TTYPES) - ], order='field_description desc') + fields = ( + self.env["ir.model.fields"] + .sudo() + .search( + [ + ("model_id", "=", model_id), + ("store", "=", True), + ("name", "not in", models.MAGIC_COLUMNS), + ("ttype", "not in", NO_BI_TTYPES), + ], + order="field_description desc", + ) + ) fields_dict = list(map(dict_for_field, fields)) return fields_dict @api.model def create(self, vals): - if self.env.context and self.env.context.get('bve'): - vals['state'] = 'base' + if self.env.context and self.env.context.get("bve"): + vals["state"] = "base" res = super().create(vals) # this sql update is necessary since a write method here would # be not working (an orm constraint is restricting the modification # of the state field while updating ir.model) q = "UPDATE ir_model SET state = 'manual' WHERE id = %s" - self.env.cr.execute(q, (res.id, )) + self.env.cr.execute(q, (res.id,)) # # update registry - if self.env.context.get('bve'): + if self.env.context.get("bve"): # setup models; this reloads custom models in registry self.pool.setup_models(self._cr) diff --git a/bi_view_editor/models/models.py b/bi_view_editor/models/models.py index ae95bf5b..1d9dd709 100644 --- a/bi_view_editor/models/models.py +++ b/bi_view_editor/models/models.py @@ -11,7 +11,7 @@ _logger = logging.getLogger(__name__) @api.model def _bi_view(_name): - return _name.startswith('x_bve.') + return _name.startswith("x_bve.") _auto_init_orig = models.BaseModel._auto_init @@ -36,7 +36,7 @@ models.BaseModel._auto_init = _auto_init class Base(models.AbstractModel): - _inherit = 'base' + _inherit = "base" @api.model def _setup_complete(self): @@ -50,10 +50,9 @@ class Base(models.AbstractModel): if not _bi_view(self._name): return super()._read_group_process_groupby(gb, query) - split = gb.split(':') + split = gb.split(":") if split[0] not in self._fields: - raise UserError( - _('No data to be displayed.')) + raise UserError(_("No data to be displayed.")) return super()._read_group_process_groupby(gb, query) @api.model diff --git a/bi_view_editor/security/rules.xml b/bi_view_editor/security/rules.xml index b417a13a..ff8592f0 100644 --- a/bi_view_editor/security/rules.xml +++ b/bi_view_editor/security/rules.xml @@ -1,11 +1,11 @@ - + - bve_view read access - - - ['|',('user_ids','=',False),('user_ids','in',user.id)] + + + ['|',('user_ids','=',False),('user_ids','in',user.id)] - diff --git a/bi_view_editor/static/src/css/bve.css b/bi_view_editor/static/src/css/bve.css index 8290cd13..04191e29 100644 --- a/bi_view_editor/static/src/css/bve.css +++ b/bi_view_editor/static/src/css/bve.css @@ -1,9 +1,10 @@ .oe_form_field_bi_editor { /*box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);*/ - border: 1px solid #DDDDDD; + border: 1px solid #dddddd; } -.oe_form_field_bi_editor .header, .oe_form_field_bi_editor .footer { +.oe_form_field_bi_editor .header, +.oe_form_field_bi_editor .footer { width: 100%; height: 50px; background-color: #7c7bad; @@ -11,11 +12,12 @@ } .oe_form_field_bi_editor .footer { - background-color: #FFF; - border-top: 1px solid #DDDDDD; + background-color: #fff; + border-top: 1px solid #dddddd; } -.oe_form_field_bi_editor .header .left, .oe_form_field_bi_editor .footer .left { +.oe_form_field_bi_editor .header .left, +.oe_form_field_bi_editor .footer .left { width: 75%; float: left; line-height: 50px; @@ -23,7 +25,8 @@ padding-top: 13px; } -.oe_form_field_bi_editor .header .right, .oe_form_field_bi_editor .footer .right { +.oe_form_field_bi_editor .header .right, +.oe_form_field_bi_editor .footer .right { width: 25%; float: right; padding-top: 13px; @@ -39,7 +42,7 @@ float: left; width: 30%; box-sizing: border-box; - border-right: 1px solid #DDDDDD; + border-right: 1px solid #dddddd; } .oe_form_field_bi_editor .body .left .search-bar { @@ -84,19 +87,19 @@ } .oe_form_field_bi_editor .body .left .class-list.readonly { - opacity: .35; + opacity: 0.35; } .oe_form_field_bi_editor .body .left .class-list .class.readonly { cursor: default; } -.oe_form_field_bi_editor .body .left .class-list .class:hover { - background-color: #7C7BAD; - color: #FFF; +.oe_form_field_bi_editor .body .left .class-list .class:hover { + background-color: #7c7bad; + color: #fff; } -.oe_form_field_bi_editor .body .left .class-list .field { +.oe_form_field_bi_editor .body .left .class-list .field { font-weight: normal; padding-left: 20px; padding-top: 3px; @@ -113,12 +116,12 @@ overflow-x: hidden; } -.oe_form_field_bi_editor .body .right .field-list { +.oe_form_field_bi_editor .body .right .field-list { width: 100%; } -.oe_form_field_bi_editor .body .right .field-list th, -.oe_form_field_bi_editor .body .right .field-list td { +.oe_form_field_bi_editor .body .right .field-list th, +.oe_form_field_bi_editor .body .right .field-list td { padding-left: 10px; padding-top: 6px; padding-bottom: 6px; @@ -130,26 +133,27 @@ background-color: transparent; border: none; background-image: none; - padding: 0; - cursor: pointer; + padding: 0; + cursor: pointer; } .oe_form_field_bi_editor .body .right .field-list tbody tr:hover { - background-color: #DDD; + background-color: #ddd; } .oe_form_field_bi_editor .body .right .field-list tbody tr.join-node { - background-color: #D2D2FF; + background-color: #d2d2ff; text-align: center; - border-top: 1px solid #DDDDDD; + border-top: 1px solid #dddddd; } -.oe_form_field_bi_editor .context-menu, .oe_form_field_bi_editor .context-menu ul { +.oe_form_field_bi_editor .context-menu, +.oe_form_field_bi_editor .context-menu ul { z-index: 1000; position: fixed; background-color: #fff; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); - border: 1px solid #DDDDDD; + border: 1px solid #dddddd; list-style-type: none; padding: 0; width: 175px; @@ -174,8 +178,8 @@ } .oe_form_field_bi_editor .context-menu li:hover { - background-color: #7C7BAD; - color: #FFF; + background-color: #7c7bad; + color: #fff; } .oe_form_field_bi_editor .context-menu ul { @@ -196,4 +200,4 @@ .oe_bi_view_editor_join_node_dialog li { cursor: pointer; -} \ No newline at end of file +} diff --git a/bi_view_editor/static/src/js/bi_view_editor.FieldList.js b/bi_view_editor/static/src/js/bi_view_editor.FieldList.js index 2525e223..e99baddb 100644 --- a/bi_view_editor/static/src/js/bi_view_editor.FieldList.js +++ b/bi_view_editor/static/src/js/bi_view_editor.FieldList.js @@ -1,57 +1,55 @@ /* Copyright 2015-2019 Onestein () * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ -odoo.define('bi_view_editor.FieldList', function (require) { +odoo.define("bi_view_editor.FieldList", function(require) { "use strict"; - var core = require('web.core'); + var core = require("web.core"); var qweb = core.qweb; - var Widget = require('web.Widget'); + var Widget = require("web.Widget"); var FieldListContextMenu = Widget.extend({ - start: function () { + start: function() { var res = this._super.apply(this, arguments); - this.$el.mouseleave(function () { - $(this).addClass('d-none'); + this.$el.mouseleave(function() { + $(this).addClass("d-none"); }); return res; }, - open: function (x, y) { + open: function(x, y) { this.$el.css({ - 'left': x + 'px', - 'top': y + 'px', + left: x + "px", + top: y + "px", }); - this.$el.removeClass('d-none'); + this.$el.removeClass("d-none"); return _.extend({}, window.Backbone.Events); }, }); var FieldListFieldContextMenu = FieldListContextMenu.extend({ - template: 'bi_view_editor.FieldList.FieldContextMenu', - open: function (x, y, field) { - this.$el.find('.checkbox-column').prop('checked', field.column); - this.$el.find('.checkbox-row').prop('checked', field.row); - this.$el.find('.checkbox-measure').prop('checked', field.measure); - this.$el.find('.checkbox-list').prop('checked', field.list); + template: "bi_view_editor.FieldList.FieldContextMenu", + open: function(x, y, field) { + this.$el.find(".checkbox-column").prop("checked", field.column); + this.$el.find(".checkbox-row").prop("checked", field.row); + this.$el.find(".checkbox-measure").prop("checked", field.measure); + this.$el.find(".checkbox-list").prop("checked", field.list); var measureable = field.type === "float" || field.type === "integer" || - field.type === "monetary" - ; - - this.$el.find('.checkbox-column').attr('disabled', measureable); - this.$el.find('.checkbox-row').attr('disabled', measureable); - this.$el.find('.checkbox-measure').attr('disabled', !measureable); - this.$el.find('.checkbox-list').attr('disabled', false); + field.type === "monetary"; + this.$el.find(".checkbox-column").attr("disabled", measureable); + this.$el.find(".checkbox-row").attr("disabled", measureable); + this.$el.find(".checkbox-measure").attr("disabled", !measureable); + this.$el.find(".checkbox-list").attr("disabled", false); var events = this._super(x, y, field); - this.$el.find('input').unbind('change'); - this.$el.find('input').change(function () { + this.$el.find("input").unbind("change"); + this.$el.find("input").change(function() { var $checkbox = $(this); - var property = $checkbox.attr('data-for'); - field[property] = $checkbox.is(':checked'); - events.trigger('change', field); + var property = $checkbox.attr("data-for"); + field[property] = $checkbox.is(":checked"); + events.trigger("change", field); }); return events; @@ -59,67 +57,71 @@ odoo.define('bi_view_editor.FieldList', function (require) { }); var FieldListJoinContextMenu = FieldListContextMenu.extend({ - template: 'bi_view_editor.FieldList.JoinContextMenu', - open: function (x, y, node) { - this.$el.find('.checkbox-join-left').prop('checked', node.join_left); + template: "bi_view_editor.FieldList.JoinContextMenu", + open: function(x, y, node) { + this.$el.find(".checkbox-join-left").prop("checked", node.join_left); var events = this._super(x, y, node); - this.$el.find('input').unbind('change'); - this.$el.find('input').change(function () { + this.$el.find("input").unbind("change"); + this.$el.find("input").change(function() { var $checkbox = $(this); - var property = $checkbox.attr('data-for'); - node[property] = $checkbox.is(':checked'); - events.trigger('change', node); + var property = $checkbox.attr("data-for"); + node[property] = $checkbox.is(":checked"); + events.trigger("change", node); }); return events; }, }); var FieldList = Widget.extend({ - template: 'bi_view_editor.FieldList', + template: "bi_view_editor.FieldList", events: { - 'click .delete-button': 'removeClicked', - 'keyup input[name="description"]': 'keyupDescription', + "click .delete-button": "removeClicked", + 'keyup input[name="description"]': "keyupDescription", }, - start: function () { + start: function() { var res = this._super.apply(this, arguments); this.contextmenu = new FieldListFieldContextMenu(this); this.contextmenu.appendTo(this.$el); this.contextmenu_join = new FieldListJoinContextMenu(this); this.contextmenu_join.appendTo(this.$el); - this.$table = this.$el.find('tbody'); + this.$table = this.$el.find("tbody"); this.mode = null; return res; }, - setMode: function (mode) { - if (mode === 'readonly') { - this.$el.find('input[type="text"]').attr('disabled', true); - this.$el.find(".delete-button").addClass('d-none'); + setMode: function(mode) { + if (mode === "readonly") { + this.$el.find('input[type="text"]').attr("disabled", true); + this.$el.find(".delete-button").addClass("d-none"); } else { - this.$el.find('input[type="text"]').removeAttr('disabled'); - this.$el.find(".delete-button").removeClass('d-none'); + this.$el.find('input[type="text"]').removeAttr("disabled"); + this.$el.find(".delete-button").removeClass("d-none"); } this.mode = mode; }, - get: function () { - return $.makeArray(this.$el.find("tbody tr").map(function () { - var field = $(this).data('field'); - field.description = $(this).find('input[name="description"]').val(); - return field; - })); - }, - getModelIds: function () { + get: function() { + return $.makeArray( + this.$el.find("tbody tr").map(function() { + var field = $(this).data("field"); + field.description = $(this) + .find('input[name="description"]') + .val(); + return field; + }) + ); + }, + getModelIds: function() { var model_ids = {}; - this.$el.find("tbody tr").each(function () { - var data = $(this).data('field'); + this.$el.find("tbody tr").each(function() { + var data = $(this).data("field"); model_ids[data.table_alias] = data.model_id; }); return model_ids; }, - getModelData: function () { + getModelData: function() { var model_data = {}; - this.$el.find("tbody tr").each(function () { - var data = $(this).data('field'); + this.$el.find("tbody tr").each(function() { + var data = $(this).data("field"); model_data[data.table_alias] = { model_id: data.model_id, model_name: data.model_name, @@ -127,95 +129,114 @@ odoo.define('bi_view_editor.FieldList', function (require) { }); return model_data; }, - add: function (field) { + add: function(field) { var self = this; - field.row = typeof field.row === 'undefined' ? false : field.row; - field.column = typeof field.column === 'undefined' ? false : field.column; - field.measure = typeof field.measure === 'undefined' ? false : field.measure; - field.list = typeof field.list === 'undefined' ? true : field.list; - field._id = typeof field._id === 'undefined' ? _.uniqueId('node_') : field._id; + field.row = typeof field.row === "undefined" ? false : field.row; + field.column = typeof field.column === "undefined" ? false : field.column; + field.measure = + typeof field.measure === "undefined" ? false : field.measure; + field.list = typeof field.list === "undefined" ? true : field.list; + field._id = + typeof field._id === "undefined" ? _.uniqueId("node_") : field._id; if (field.join_node) { - field.join_left = typeof field.join_left === 'undefined' ? false : field.join_left; + field.join_left = + typeof field.join_left === "undefined" ? false : field.join_left; } var i = 0; var name = field.name; - while (this.get().filter(function (item) { - return item.name === field.name; - }).length > 0) { - field.name = name + '_' + i; + while ( + this.get().filter(function(item) { + return item.name === field.name; + }).length > 0 + ) { + field.name = name + "_" + i; i++; } // Render table row - var $html = $(qweb.render(field.join_node ? 'bi_view_editor.JoinListItem' : 'bi_view_editor.FieldListItem', { - 'field': field, - })).data('field', field).contextmenu(function (e) { - var $item = $(this); - if (self.mode === 'readonly') { - return; - } - e.preventDefault(); - self.openContextMenu($item, e.pageX, e.pageY); - }); + var $html = $( + qweb.render( + field.join_node + ? "bi_view_editor.JoinListItem" + : "bi_view_editor.FieldListItem", + { + field: field, + } + ) + ) + .data("field", field) + .contextmenu(function(e) { + var $item = $(this); + if (self.mode === "readonly") { + return; + } + e.preventDefault(); + self.openContextMenu($item, e.pageX, e.pageY); + }); - this.$el.find('tbody').append($html); + this.$el.find("tbody").append($html); }, - remove: function (id) { + remove: function(id) { var $item = this.$el.find('tr[data-id="' + id + '"]'); $item.remove(); - this.trigger('removed', id); + this.trigger("removed", id); }, - set: function (fields) { + set: function(fields) { var set_fields = fields; if (!set_fields) { set_fields = []; } - this.$el.find('tbody tr').remove(); + this.$el.find("tbody tr").remove(); for (var i = 0; i < set_fields.length; i++) { this.add(set_fields[i]); } }, - openContextMenu: function ($item, x, y) { - var field = $item.data('field'); - var contextmenu = field.join_node ? this.contextmenu_join : this.contextmenu; + openContextMenu: function($item, x, y) { + var field = $item.data("field"); + var contextmenu = field.join_node + ? this.contextmenu_join + : this.contextmenu; // Temporary disable contextmenu for join node (until left join is implemented) if (field.join_node) { return; } - contextmenu.open(x - 20, y - 20, $item.data('field')).on('change', function (f) { - $item.data('field', f); - this.refreshItem($item); - this.trigger('updated'); - }.bind(this)); - }, - refreshItem: function ($item) { - var data = $item.data('field'); - var $attributes = $item.find('span[data-for], img[data-for]'); - $.each($attributes, function () { + contextmenu.open(x - 20, y - 20, $item.data("field")).on( + "change", + function(f) { + $item.data("field", f); + this.refreshItem($item); + this.trigger("updated"); + }.bind(this) + ); + }, + refreshItem: function($item) { + var data = $item.data("field"); + var $attributes = $item.find("span[data-for], img[data-for]"); + $.each($attributes, function() { var $attribute = $(this); - var value = data[$attribute.attr('data-for')]; + var value = data[$attribute.attr("data-for")]; if (value) { - $attribute.removeClass('d-none'); + $attribute.removeClass("d-none"); } else { - $attribute.addClass('d-none'); + $attribute.addClass("d-none"); } }); }, - removeClicked: function (e) { + removeClicked: function(e) { var $button = $(e.currentTarget); - var id = $button.attr('data-id'); + var id = $button.attr("data-id"); this.remove(id); }, - keyupDescription: function () { - this.trigger('updated'); + keyupDescription: function() { + this.trigger("updated"); }, }); return { - 'FieldList': FieldList, - 'FieldListContextMenu': FieldListContextMenu, - 'FieldListFieldContextMenu': FieldListFieldContextMenu, - 'FieldListJoinContextMenu': FieldListJoinContextMenu, + FieldList: FieldList, + FieldListContextMenu: FieldListContextMenu, + FieldListFieldContextMenu: FieldListFieldContextMenu, + FieldListJoinContextMenu: FieldListJoinContextMenu, }; }); diff --git a/bi_view_editor/static/src/js/bi_view_editor.JoinNodeDialog.js b/bi_view_editor/static/src/js/bi_view_editor.JoinNodeDialog.js index bbb1ec8f..5549efbb 100644 --- a/bi_view_editor/static/src/js/bi_view_editor.JoinNodeDialog.js +++ b/bi_view_editor/static/src/js/bi_view_editor.JoinNodeDialog.js @@ -1,48 +1,51 @@ /* Copyright 2015-2019 Onestein () * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ -odoo.define('bi_view_editor.JoinNodeDialog', function (require) { +odoo.define("bi_view_editor.JoinNodeDialog", function(require) { "use strict"; var Dialog = require("web.Dialog"); - var core = require('web.core'); + var core = require("web.core"); var qweb = core.qweb; var _t = core._t; var JoinNodeDialog = Dialog.extend({ xmlDependencies: Dialog.prototype.xmlDependencies.concat([ - '/bi_view_editor/static/src/xml/bi_view_editor.xml', + "/bi_view_editor/static/src/xml/bi_view_editor.xml", ]), events: { "click li": "choiceClicked", }, - init: function (parent, options, choices, model_data) { + init: function(parent, options, choices, model_data) { this.choices = choices; // Prepare data for view for (var i = 0; i < choices.length; i++) { if (choices[i].join_node !== -1 && choices[i].table_alias !== -1) { - choices[i].model_name = model_data[choices[i].table_alias].model_name; + choices[i].model_name = + model_data[choices[i].table_alias].model_name; } choices[i].index = i; } var defaults = _.defaults(options || {}, { title: _t("Join..."), - dialogClass: 'oe_act_window', - $content: qweb.render('bi_view_editor.JoinNodeDialog', { - 'choices': choices, + dialogClass: "oe_act_window", + $content: qweb.render("bi_view_editor.JoinNodeDialog", { + choices: choices, }), - buttons: [{ - text: _t("Cancel"), - classes: "btn-default o_form_button_cancel", - close: true, - }], + buttons: [ + { + text: _t("Cancel"), + classes: "btn-default o_form_button_cancel", + close: true, + }, + ], }); this._super(parent, defaults); }, - choiceClicked: function (e) { - this.trigger('chosen', { - choice: this.choices[$(e.currentTarget).attr('data-index')], + choiceClicked: function(e) { + this.trigger("chosen", { + choice: this.choices[$(e.currentTarget).attr("data-index")], }); this.close(); }, diff --git a/bi_view_editor/static/src/js/bi_view_editor.ModelList.js b/bi_view_editor/static/src/js/bi_view_editor.ModelList.js index 588cea8d..d8db3f9c 100644 --- a/bi_view_editor/static/src/js/bi_view_editor.ModelList.js +++ b/bi_view_editor/static/src/js/bi_view_editor.ModelList.js @@ -1,87 +1,92 @@ /* Copyright 2015-2019 Onestein () * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ -odoo.define('bi_view_editor.ModelList', function (require) { +odoo.define("bi_view_editor.ModelList", function(require) { "use strict"; - var Widget = require('web.Widget'); - var core = require('web.core'); + var Widget = require("web.Widget"); + var core = require("web.core"); var qweb = core.qweb; var ModelList = Widget.extend({ - template: 'bi_view_editor.ModelList', + template: "bi_view_editor.ModelList", events: { - 'keyup .search-bar > input': 'filterChanged', + "keyup .search-bar > input": "filterChanged", }, - init: function (parent) { + init: function(parent) { var res = this._super(parent); this.active_models = []; this.cache_fields = {}; - this.current_filter = ''; + this.current_filter = ""; this.mode = null; return res; }, - setMode: function (mode) { - if (mode === 'readonly') { - this.$el.find('.search-bar').attr('disabled', true); - this.$el.find('.class-list, .class').addClass('readonly'); + setMode: function(mode) { + if (mode === "readonly") { + this.$el.find(".search-bar").attr("disabled", true); + this.$el.find(".class-list, .class").addClass("readonly"); } else { - this.$el.find('.search-bar').attr('disabled', false); - this.$el.find('.class-list, .class').removeClass('readonly'); + this.$el.find(".search-bar").attr("disabled", false); + this.$el.find(".class-list, .class").removeClass("readonly"); } this.mode = mode; }, - isActive: function (id) { + isActive: function(id) { return this.active_models.indexOf(id) !== -1; }, - removeAsActive: function (id) { + removeAsActive: function(id) { var i = this.active_models.indexOf(id); this.active_models.splice(i, 1); }, - addAsActive: function (id) { + addAsActive: function(id) { this.active_models.push(id); }, - loadModels: function (model_ids) { + loadModels: function(model_ids) { return this._rpc({ - model: 'ir.model', - method: 'get_models', + model: "ir.model", + method: "get_models", args: model_ids ? [model_ids] : [], }); }, - loadFields: function (model_id) { + loadFields: function(model_id) { if (!(model_id in this.cache_fields)) { var deferred = this._rpc({ - model: 'ir.model', - method: 'get_fields', + model: "ir.model", + method: "get_fields", args: [model_id], }); this.cache_fields[model_id] = deferred; } return this.cache_fields[model_id]; }, - populateModels: function (models) { + populateModels: function(models) { var self = this; - this.$el.find(".class-list").html(''); + this.$el.find(".class-list").html(""); - _.each(models, function (model) { - var $html = $(qweb.render('bi_view_editor.ModelListItem', { - 'id': model.id, - 'model': model.model, - 'name': model.name, - })); - $html.find('.class').data('model', model).click(function () { - self.modelClicked($(this)); - }); + _.each(models, function(model) { + var $html = $( + qweb.render("bi_view_editor.ModelListItem", { + id: model.id, + model: model.model, + name: model.name, + }) + ); + $html + .find(".class") + .data("model", model) + .click(function() { + self.modelClicked($(this)); + }); self.$el.find(".class-list").append($html); if (self.isActive(model.id)) { - self.loadFields(model.id).done(function (fields) { + self.loadFields(model.id).done(function(fields) { self.populateFields(fields, model.id); }); } }); }, - populateFields: function (fields, model_id) { + populateFields: function(fields, model_id) { var self = this; if (!model_id && fields.length === 0) { return; @@ -91,59 +96,72 @@ odoo.define('bi_view_editor.ModelList', function (require) { data_model_id = fields[0].model_id; } var $model_item = this.$el.find(".class[data-id='" + data_model_id + "']"); - _.each(fields, function (field) { - var $field = $(qweb.render('bi_view_editor.ModelListFieldItem', { - name: field.name, - description: field.description, - })).data('field', field).click(function () { - self.fieldClicked($(this)); - }).draggable({ - 'revert': 'invalid', - 'scroll': false, - 'helper': 'clone', - 'appendTo': 'body', - 'containment': 'window', - }); + _.each(fields, function(field) { + var $field = $( + qweb.render("bi_view_editor.ModelListFieldItem", { + name: field.name, + description: field.description, + }) + ) + .data("field", field) + .click(function() { + self.fieldClicked($(this)); + }) + .draggable({ + revert: "invalid", + scroll: false, + helper: "clone", + appendTo: "body", + containment: "window", + }); $model_item.after($field); - }); }, - modelClicked: function ($el) { - if (this.mode === 'readonly') { + modelClicked: function($el) { + if (this.mode === "readonly") { return; } - var model = $el.data('model'); - $el.parent().find('.field').remove(); + var model = $el.data("model"); + $el.parent() + .find(".field") + .remove(); if (this.isActive(model.id)) { this.removeAsActive(model.id); } else { this.addAsActive(model.id); - this.loadFields(model.id).done(function (fields) { - this.populateFields(fields, model.id); - }.bind(this)); + this.loadFields(model.id).done( + function(fields) { + this.populateFields(fields, model.id); + }.bind(this) + ); } }, - fieldClicked: function ($el) { - if (this.mode === 'readonly') { + fieldClicked: function($el) { + if (this.mode === "readonly") { return; } - this.trigger('field_clicked', $el.data('field')); + this.trigger("field_clicked", $el.data("field")); }, - filterChanged: function (e) { + filterChanged: function(e) { var $input = $(e.target); this.filter($input.val()); }, - filter: function (value) { + filter: function(value) { this.active_models = []; - this.$el.find('.field').remove(); - var val = typeof value === 'undefined' ? this.current_filter : value.toLowerCase(); - this.$el.find(".class").each(function () { - var data = $(this).data('model'); - if (data.name.toLowerCase().indexOf(val) === -1 && - data.model.toLowerCase().indexOf(val) === -1) { - $(this).addClass('d-none'); + this.$el.find(".field").remove(); + var val = + typeof value === "undefined" + ? this.current_filter + : value.toLowerCase(); + this.$el.find(".class").each(function() { + var data = $(this).data("model"); + if ( + data.name.toLowerCase().indexOf(val) === -1 && + data.model.toLowerCase().indexOf(val) === -1 + ) { + $(this).addClass("d-none"); } else { - $(this).removeClass('d-none'); + $(this).removeClass("d-none"); } }); this.current_filter = val; @@ -151,5 +169,4 @@ odoo.define('bi_view_editor.ModelList', function (require) { }); return ModelList; - }); diff --git a/bi_view_editor/static/src/js/bi_view_editor.js b/bi_view_editor/static/src/js/bi_view_editor.js index 5f0342fa..528f2d24 100644 --- a/bi_view_editor/static/src/js/bi_view_editor.js +++ b/bi_view_editor/static/src/js/bi_view_editor.js @@ -1,48 +1,48 @@ /* Copyright 2015-2019 Onestein () * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ -odoo.define('bi_view_editor', function (require) { +odoo.define("bi_view_editor", function(require) { "use strict"; - var JoinNodeDialog = require('bi_view_editor.JoinNodeDialog'); - var ModelList = require('bi_view_editor.ModelList'); - var FieldList = require('bi_view_editor.FieldList').FieldList; + var JoinNodeDialog = require("bi_view_editor.JoinNodeDialog"); + var ModelList = require("bi_view_editor.ModelList"); + var FieldList = require("bi_view_editor.FieldList").FieldList; - var AbstractField = require('web.AbstractField'); - var Data = require('web.data'); - var field_registry = require('web.field_registry'); + var AbstractField = require("web.AbstractField"); + var Data = require("web.data"); + var field_registry = require("web.field_registry"); var BiViewEditor = AbstractField.extend({ template: "bi_view_editor.Frame", events: { "click .clear-btn": "clear", }, - start: function () { + start: function() { var self = this; var res = this._super.apply(this, arguments); // Init ModelList this.model_list = new ModelList(this); this.model_list.appendTo(this.$(".body > .left")); - this.model_list.on('field_clicked', this, function (field) { + this.model_list.on("field_clicked", this, function(field) { self.addField(_.extend({}, field)); }); // Init FieldList this.field_list = new FieldList(this); this.field_list.appendTo(this.$(".body > .right")); - this.field_list.on('removed', this, this.fieldListRemoved); - this.field_list.on('updated', this, this.fieldListChanged); + this.field_list.on("removed", this, this.fieldListRemoved); + this.field_list.on("updated", this, this.fieldListChanged); this.$el.find(".body > .right").droppable({ accept: "div.class-list div.field", - drop: function (event, ui) { - self.addField(_.extend({}, ui.draggable.data('field'))); - ui.draggable.draggable('option', 'revert', false); + drop: function(event, ui) { + self.addField(_.extend({}, ui.draggable.data("field"))); + ui.draggable.draggable("option", "revert", false); }, }); - this.on("change:effective_readonly", this, function () { + this.on("change:effective_readonly", this, function() { this.updateMode(); }); this.renderValue(); @@ -50,61 +50,65 @@ odoo.define('bi_view_editor', function (require) { this.updateMode(); return res; }, - clear: function () { - if (this.mode !== 'readonly') { + clear: function() { + if (this.mode !== "readonly") { this.field_list.set([]); this.loadAndPopulateModelList(); this._setValue(this.field_list.get()); } }, - fieldListChanged: function () { + fieldListChanged: function() { this._setValue(this.field_list.get()); }, - fieldListRemoved: function () { + fieldListRemoved: function() { console.log(this.field_list.get()); this._setValue(this.field_list.get()); var model = new Data.DataSet(this, "bve.view"); - model.call('get_clean_list', [this.value]).then(function (result) { - this.field_list.set(JSON.parse(result)); - this._setValue(this.field_list.get()); - }.bind(this)); + model.call("get_clean_list", [this.value]).then( + function(result) { + this.field_list.set(JSON.parse(result)); + this._setValue(this.field_list.get()); + }.bind(this) + ); this.loadAndPopulateModelList(); }, - renderValue: function () { + renderValue: function() { this.field_list.set(JSON.parse(this.value)); }, - updateMode: function () { - if (this.mode === 'readonly') { - this.$el.find('.clear-btn').addClass('d-none'); + updateMode: function() { + if (this.mode === "readonly") { + this.$el.find(".clear-btn").addClass("d-none"); this.$el.find(".body .right").droppable("option", "disabled", true); } else { - this.$el.find('.clear-btn').removeClass('d-none'); - this.$el.find('.body .right').droppable('option', 'disabled', false); + this.$el.find(".clear-btn").removeClass("d-none"); + this.$el.find(".body .right").droppable("option", "disabled", false); } this.field_list.setMode(this.mode); this.model_list.setMode(this.mode); }, - loadAndPopulateModelList: function () { + loadAndPopulateModelList: function() { var model_ids = null; if (this.field_list.get().length > 0) { model_ids = this.field_list.getModelIds(); } - this.model_list.loadModels(model_ids).done(function (models) { - this.model_list.populateModels(models); - }.bind(this)); + this.model_list.loadModels(model_ids).done( + function(models) { + this.model_list.populateModels(models); + }.bind(this) + ); }, - getTableAlias: function (field) { - if (typeof field.table_alias === 'undefined') { + getTableAlias: function(field) { + if (typeof field.table_alias === "undefined") { var model_ids = this.field_list.getModelIds(); var n = 1; - while (typeof model_ids["t" + n] !== 'undefined') { + while (typeof model_ids["t" + n] !== "undefined") { n++; } return "t" + n; } return field.table_alias; }, - addFieldAndJoinNode: function (field, join_node) { + addFieldAndJoinNode: function(field, join_node) { if (join_node.join_node === -1 || join_node.table_alias === -1) { field.table_alias = this.getTableAlias(field); if (join_node.join_node === -1) { @@ -121,31 +125,37 @@ odoo.define('bi_view_editor', function (require) { this.loadAndPopulateModelList(); this._setValue(this.field_list.get()); }, - addField: function (field) { + addField: function(field) { var data = _.extend({}, field); var model = new Data.DataSet(this, "ir.model"); var field_data = this.field_list.get(); - model.call('get_join_nodes', [field_data, data]).then(function (result) { - if (result.length === 1) { - this.addFieldAndJoinNode(data, result[0]); - } else if (result.length > 1) { - var dialog = new JoinNodeDialog(this, {}, result, this.field_list.getModelData()); - dialog.open().on('chosen', this, function (e) { - this.addFieldAndJoinNode(data, e.choice); - }); - } else { - data.table_alias = this.getTableAlias(data); - this.field_list.add(data); - this.loadAndPopulateModelList(); - this._setValue(this.field_list.get()); - } - }.bind(this)); + model.call("get_join_nodes", [field_data, data]).then( + function(result) { + if (result.length === 1) { + this.addFieldAndJoinNode(data, result[0]); + } else if (result.length > 1) { + var dialog = new JoinNodeDialog( + this, + {}, + result, + this.field_list.getModelData() + ); + dialog.open().on("chosen", this, function(e) { + this.addFieldAndJoinNode(data, e.choice); + }); + } else { + data.table_alias = this.getTableAlias(data); + this.field_list.add(data); + this.loadAndPopulateModelList(); + this._setValue(this.field_list.get()); + } + }.bind(this) + ); }, - _parseValue: function (value) { + _parseValue: function(value) { return JSON.stringify(value); }, }); - field_registry.add('BVEEditor', BiViewEditor); - + field_registry.add("BVEEditor", BiViewEditor); }); diff --git a/bi_view_editor/static/src/xml/bi_view_editor.xml b/bi_view_editor/static/src/xml/bi_view_editor.xml index a5e6aaec..a48bfd53 100644 --- a/bi_view_editor/static/src/xml/bi_view_editor.xml +++ b/bi_view_editor/static/src/xml/bi_view_editor.xml @@ -1,81 +1,98 @@ - +