Browse Source

General code review

pull/59/head
Andrea 8 years ago
parent
commit
a652e08020
  1. 11
      bi_view_editor/README.rst
  2. 3
      bi_view_editor/__init__.py
  3. 8
      bi_view_editor/__openerp__.py
  4. 14
      bi_view_editor/hooks.py
  5. 2
      bi_view_editor/models/__init__.py
  6. 338
      bi_view_editor/models/bve_view.py
  7. 389
      bi_view_editor/models/ir_model.py
  8. 18
      bi_view_editor/security/rules.xml
  9. 20
      bi_view_editor/static/src/css/bve.css
  10. 249
      bi_view_editor/static/src/js/bve.js
  11. 2
      bi_view_editor/templates/assets_template.xml
  12. 109
      bi_view_editor/views/bve_view.xml

11
bi_view_editor/README.rst

@ -33,7 +33,8 @@ To graphically design your analysis data-set:
- Pick the interesting fields (Drag & Drop) - Pick the interesting fields (Drag & Drop)
- For each selected field, right-click on the Options column and select whether it's a row, column or measure - For each selected field, right-click on the Options column and select whether it's a row, column or measure
- Save and click "Generate BI View" - Save and click "Generate BI View"
- Click "Open BI View" to view the result
- If module Dashboard (board) is installed, the standard "Add to My Dashboard" functionality would be available
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot :alt: Try me on Runbot
@ -45,9 +46,15 @@ Known issues / Roadmap
* Non-stored fields are not supported * Non-stored fields are not supported
* Provide graph view for table relations * Provide graph view for table relations
* Extend the capabilities of the tree views (e.g. add sums) * Extend the capabilities of the tree views (e.g. add sums)
* Add possibility to store the BI view in user dashboard, like any other graph or cross table
* Provide a tutorial (eg. a working example of usage) * Provide a tutorial (eg. a working example of usage)
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/reporting-engine/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smash it by providing detailed and welcomed feedback.
Credits Credits
======= =======

3
bi_view_editor/__init__.py

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# © 2015-2016 ONESTEiN BV (<http://www.onestein.eu>)
# Copyright 2015-2017 Onestein (<http://www.onestein.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import models from . import models
from .hooks import uninstall_hook

8
bi_view_editor/__openerp__.py

@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# © 2015-2016 ONESTEiN BV (<http://www.onestein.eu>)
# Copyright 2015-2017 Onestein (<http://www.onestein.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{ {
'name': 'BI View Editor', 'name': 'BI View Editor',
'summary': '''Graphical BI views builder for Odoo 8''',
'summary': 'Graphical BI views builder for Odoo',
'images': ['static/description/main_screenshot.png'], 'images': ['static/description/main_screenshot.png'],
'author': 'ONESTEiN BV,Odoo Community Association (OCA)',
'author': 'Onestein,Odoo Community Association (OCA)',
'license': 'AGPL-3', 'license': 'AGPL-3',
'website': 'http://www.onestein.eu', 'website': 'http://www.onestein.eu',
'category': 'Reporting', 'category': 'Reporting',
@ -27,4 +27,6 @@
'js': [ 'js': [
'static/src/js/bve.js' 'static/src/js/bve.js'
], ],
'installable': True,
'uninstall_hook': 'uninstall_hook'
} }

14
bi_view_editor/hooks.py

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# Copyright 2015-2017 Onestein (<http://www.onestein.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
def uninstall_hook(cr, registry):
# delete dirty data that could cause problems
# while re-installing the module
cr.execute("""
delete from ir_model where model like 'x_bve.%'
""")
cr.execute("""
delete from bve_view where model_name like 'x_bve.%'
""")

2
bi_view_editor/models/__init__.py

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# © 2015-2016 ONESTEiN BV (<http://www.onestein.eu>)
# Copyright 2015-2017 Onestein (<http://www.onestein.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import bve_view from . import bve_view

338
bi_view_editor/models/bve_view.py

@ -1,68 +1,63 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# © 2015-2016 ONESTEiN BV (<http://www.onestein.eu>)
# Copyright 2015-2017 Onestein (<http://www.onestein.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import json import json
from openerp import tools
from openerp import SUPERUSER_ID
from openerp import models, fields, api
from openerp import api, fields, models, tools
from openerp.exceptions import Warning as UserError from openerp.exceptions import Warning as UserError
from openerp.modules.registry import RegistryManager
from openerp.tools.translate import _ from openerp.tools.translate import _
class BveView(models.Model): class BveView(models.Model):
_name = 'bve.view' _name = 'bve.view'
_description = "BI View Editor"
_description = 'BI View Editor'
@api.depends('group_ids') @api.depends('group_ids')
@api.multi @api.multi
def _compute_users(self): def _compute_users(self):
for bve_view in self: for bve_view in self:
if bve_view.sudo().group_ids:
bve_view.user_ids = self.env['res.users'].sudo().browse(
list(set([u.id for group in bve_view.sudo().group_ids
for u in group.users])))
group_ids = bve_view.sudo().group_ids
if group_ids:
bve_view.user_ids = group_ids.mapped('users')
else: else:
bve_view.user_ids = self.env['res.users'].sudo().search([]) bve_view.user_ids = self.env['res.users'].sudo().search([])
name = fields.Char(size=128, string="Name", required=True)
model_name = fields.Char(size=128, string="Model Name")
name = fields.Char(required=True, copy=False)
model_name = fields.Char()
note = fields.Text(string="Notes")
note = fields.Text(string='Notes')
state = fields.Selection( state = fields.Selection(
[('draft', 'Draft'), [('draft', 'Draft'),
('created', 'Created')], ('created', 'Created')],
string="State",
default="draft")
default='draft',
copy=False)
data = fields.Text( data = fields.Text(
string="Data",
help="Use the special query builder to define the query " help="Use the special query builder to define the query "
"to generate your report dataset. " "to generate your report dataset. "
"NOTE: Te be edited, the query should be in 'Draft' status.") "NOTE: Te be edited, the query should be in 'Draft' status.")
action_id = fields.Many2one('ir.actions.act_window', string="Action")
view_id = fields.Many2one('ir.ui.view', string="View")
action_id = fields.Many2one('ir.actions.act_window', string='Action')
view_id = fields.Many2one('ir.ui.view', string='View')
group_ids = fields.Many2many( group_ids = fields.Many2many(
'res.groups', 'res.groups',
string="Groups",
string='Groups',
help="User groups allowed to see the generated report; " help="User groups allowed to see the generated report; "
"if NO groups are specified the report will be public " "if NO groups are specified the report will be public "
"for everyone.") "for everyone.")
user_ids = fields.Many2many( user_ids = fields.Many2many(
'res.users', 'res.users',
string="Users",
string='Users',
compute=_compute_users, compute=_compute_users,
store=True) store=True)
_sql_constraints = [ _sql_constraints = [
('name_uniq', ('name_uniq',
'unique(name)', 'unique(name)',
'Custom BI View names must be unique!'),
_('Custom BI View names must be unique!')),
] ]
@api.multi @api.multi
@ -72,20 +67,11 @@ class BveView(models.Model):
raise UserError( raise UserError(
_('You cannot delete a created view! ' _('You cannot delete a created view! '
'Reset the view to draft first.')) 'Reset the view to draft first.'))
super(BveView, self).unlink()
@api.multi
def action_edit_query(self):
return {
'type': 'ir.actions.client',
'tag': 'bi_view_editor.open',
'target': 'new',
'params': {'bve_view_id': self.id}
}
return super(BveView, self).unlink()
@api.multi @api.multi
def action_reset(self): def action_reset(self):
self.ensure_one()
if self.action_id: if self.action_id:
if self.action_id.view_id: if self.action_id.view_id:
self.action_id.view_id.sudo().unlink() self.action_id.view_id.sudo().unlink()
@ -96,53 +82,129 @@ class BveView(models.Model):
for model in models: for model in models:
model.sudo().unlink() model.sudo().unlink()
table_name = self.model_name.replace(".", "_")
table_name = self.model_name.replace('.', '_')
tools.drop_view_if_exists(self.env.cr, table_name) tools.drop_view_if_exists(self.env.cr, table_name)
self.write({
'state': 'draft'
})
return True
def _create_graph_view(self):
fields_info = json.loads(self.data)
view_fields = ["""<field name="x_{}" type="{}" />""".format(
field_info['name'],
(field_info['row'] and 'row') or
(field_info['column'] and 'col') or
(field_info['measure'] and 'measure'))
for field_info in fields_info if field_info['row'] or
field_info['column'] or field_info['measure']]
return view_fields
self.state = 'draft'
def _create_tree_view(self):
fields_info = json.loads(self.data)
view_fields = ["""<field name="x_{}" type="{}" />""".format(
field_info['name'],
(field_info['row'] and 'row') or
(field_info['column'] and 'col') or
(field_info['measure'] and 'measure'))
for field_info in fields_info if field_info['row'] or
field_info['column'] or field_info['measure']]
@api.multi
def _create_view_arch(self):
self.ensure_one()
def _get_field_def(field_name, def_type):
return """<field name="x_{}" type="{}" />""".format(
field_name, def_type
)
def _get_field_type(field_info):
row = field_info['row'] and 'row'
column = field_info['column'] and 'col'
measure = field_info['measure'] and 'measure'
return row or column or measure
fields_info = json.loads(self._get_format_data(self.data))
view_fields = []
for field_info in fields_info:
field_name = field_info['name']
def_type = _get_field_type(field_info)
if def_type:
field_def = _get_field_def(field_name, def_type)
view_fields.append(field_def)
return view_fields return view_fields
@api.model
def _get_format_data(self, data):
data = data.replace('\'', '"')
data = data.replace(': u"', ':"')
return data
@api.multi @api.multi
def action_create(self): def action_create(self):
self.ensure_one()
self._create_bve_object()
self._create_bve_view()
@api.multi
def _create_bve_view(self):
self.ensure_one()
# create views
View = self.env['ir.ui.view']
old_views = View.sudo().search([('model', '=', self.model_name)])
old_views.sudo().unlink()
view_vals = [{
'name': 'Pivot Analysis',
'type': 'pivot',
'model': self.model_name,
'priority': 16,
'arch': """<?xml version="1.0"?>
<pivot string="Pivot Analysis"> {} </pivot>
""".format("".join(self._create_view_arch()))
}, {
'name': 'Graph Analysis',
'type': 'graph',
'model': self.model_name,
'priority': 16,
'arch': """<?xml version="1.0"?>
<graph string="Graph Analysis"
type="bar"
stacked="True"> {} </graph>
""".format("".join(self._create_view_arch()))
}]
for vals in view_vals:
View.sudo().create(vals)
# create Tree view
tree_view = View.sudo().create(
{'name': 'Tree Analysis',
'type': 'tree',
'model': self.model_name,
'priority': 16,
'arch': """<?xml version="1.0"?>
<tree string="List Analysis" create="false"> {} </tree>
""".format("".join(self._create_view_arch()))
})
# set the Tree view as the default one
action_vals = {
'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,
}
ActWindow = self.env['ir.actions.act_window']
action_id = ActWindow.sudo().create(action_vals)
self.write({
'action_id': action_id.id,
'view_id': tree_view.id,
'state': 'created'
})
@api.multi
def _create_bve_object(self):
self.ensure_one()
def _get_fields_info(fields_data): def _get_fields_info(fields_data):
fields_info = [] fields_info = []
for field_data in fields_data: for field_data in fields_data:
field = self.env['ir.model.fields'].browse(field_data["id"])
field = self.env['ir.model.fields'].browse(field_data['id'])
vals = { vals = {
"table": self.env[field.model_id.model]._table,
"table_alias": field_data["table_alias"],
"select_field": field.name,
"as_field": "x_" + field_data["name"],
"join": False,
"model": field.model_id.model
'table': self.env[field.model_id.model]._table,
'table_alias': field_data['table_alias'],
'select_field': field.name,
'as_field': 'x_' + field_data['name'],
'join': False,
'model': field.model_id.model
} }
if field_data.get("join_node"):
vals.update({"join": field_data["join_node"]})
if field_data.get('join_node'):
vals.update({'join': field_data['join_node']})
fields_info.append(vals) fields_info.append(vals)
return fields_info return fields_info
@ -150,17 +212,19 @@ class BveView(models.Model):
data = self.data data = self.data
if not data: if not data:
raise UserError(_('No data to process.')) raise UserError(_('No data to process.'))
info = _get_fields_info(json.loads(data))
fields = [("{}.{}".format(f["table_alias"],
f["select_field"]),
f["as_field"]) for f in info if 'join_node' not in f]
tables = set([(f["table"], f["table_alias"]) for f in info])
formatted_data = json.loads(self._get_format_data(data))
info = _get_fields_info(formatted_data)
fields = [("{}.{}".format(f['table_alias'],
f['select_field']),
f['as_field']) for f in info if 'join_node' not in f]
tables = set([(f['table'], f['table_alias']) for f in info])
join_nodes = [ join_nodes = [
(f["table_alias"],
f["join"],
f["select_field"]) for f in info if f["join"] is not False]
(f['table_alias'],
f['join'],
f['select_field']) for f in info if f['join'] is not False]
table_name = self.model_name.replace(".", "_")
table_name = self.model_name.replace('.', '_')
tools.drop_view_if_exists(self.env.cr, table_name) tools.drop_view_if_exists(self.env.cr, table_name)
basic_fields = [ basic_fields = [
@ -186,19 +250,19 @@ class BveView(models.Model):
self.env.cr.execute(q) self.env.cr.execute(q)
def _prepare_field(field_data): def _prepare_field(field_data):
if not field_data["custom"]:
field = self.env['ir.model.fields'].browse(field_data["id"])
if not field_data['custom']:
field = self.env['ir.model.fields'].browse(field_data['id'])
vals = { vals = {
"name": "x_" + field_data["name"],
"complete_name": field.complete_name,
'name': 'x_' + field_data['name'],
'complete_name': field.complete_name,
'model': self.model_name, 'model': self.model_name,
'relation': field.relation, 'relation': field.relation,
"field_description": field_data.get(
"description", field.field_description),
"ttype": field.ttype,
"selection": field.selection,
"size": field.size,
'state': "manual"
'field_description': field_data.get(
'description', field.field_description),
'ttype': field.ttype,
'selection': field.selection,
'size': field.size,
'state': 'manual'
} }
if vals['ttype'] == 'monetary': if vals['ttype'] == 'monetary':
vals.update({'ttype': 'float'}) vals.update({'ttype': 'float'})
@ -206,24 +270,26 @@ class BveView(models.Model):
model_obj = self.env[field.model_id.model] model_obj = self.env[field.model_id.model]
selection = model_obj._columns[field.name].selection selection = model_obj._columns[field.name].selection
selection_domain = str(selection) selection_domain = str(selection)
vals.update({"selection": selection_domain})
vals.update({'selection': selection_domain})
return vals return vals
def _prepare_object(): def _prepare_object():
data = json.loads(self._get_format_data(self.data))
return { return {
'name': self.name, 'name': self.name,
'model': self.model_name, 'model': self.model_name,
'field_id': [ 'field_id': [
(0, 0, _prepare_field(field)) (0, 0, _prepare_field(field))
for field in json.loads(self.data)
for field in data
if 'join_node' not in field] if 'join_node' not in field]
} }
def _build_object(): def _build_object():
res_id = self.env['ir.model'].sudo().create(_prepare_object())
vals = _prepare_object()
Model = self.env['ir.model']
res_id = Model.sudo().with_context(bve=True).create(vals)
return res_id return res_id
# read access
def group_ids_with_access(model_name, access_mode): def group_ids_with_access(model_name, access_mode):
self.env.cr.execute('''SELECT self.env.cr.execute('''SELECT
g.id g.id
@ -239,11 +305,12 @@ class BveView(models.Model):
return [x[0] for x in self.env.cr.fetchall()] return [x[0] for x in self.env.cr.fetchall()]
def _build_access_rules(obj): def _build_access_rules(obj):
info = json.loads(self.data)
models = list(set([f["model"] for f in info]))
info = json.loads(self._get_format_data(self.data))
models = list(set([f['model'] for f in info]))
read_groups = set.intersection(*[set( read_groups = set.intersection(*[set(
group_ids_with_access(model, 'read')) for model in models]) group_ids_with_access(model, 'read')) for model in models])
# read access
for group in read_groups: for group in read_groups:
self.env['ir.model.access'].sudo().create({ self.env['ir.model.access'].sudo().create({
'name': 'read access to ' + self.model_name, 'name': 'read access to ' + self.model_name,
@ -252,101 +319,36 @@ class BveView(models.Model):
'perm_read': True, 'perm_read': True,
}) })
# edit access
# read and write access
for group in self.group_ids: for group in self.group_ids:
self.env['ir.model.access'].sudo().create({ self.env['ir.model.access'].sudo().create({
'name': 'read access to ' + self.model_name,
'name': 'read-write access to ' + self.model_name,
'model_id': obj.id, 'model_id': obj.id,
'group_id': group.id, 'group_id': group.id,
'perm_read': True, 'perm_read': True,
'perm_write': True, 'perm_write': True,
}) })
return
self.model_name = "x_bve." + ''.join(
self.model_name = 'x_bve.' + ''.join(
[x for x in self.name.lower() [x for x in self.name.lower()
if x.isalnum()]).replace("_", ".").replace(" ", ".")
if x.isalnum()]).replace('_', '.').replace(' ', '.')
_build_query() _build_query()
obj = _build_object() obj = _build_object()
_build_access_rules(obj) _build_access_rules(obj)
self.env.cr.commit()
self.env.registry = RegistryManager.new(self.env.cr.dbname)
RegistryManager.signal_registry_change(self.env.cr.dbname)
self.pool = self.env.registry
ui_view_obj = self.pool.get('ir.ui.view')
view_ids = ui_view_obj.search(
self.env.cr, SUPERUSER_ID, [('model', '=', self.model_name)],
context={})
[ui_view_obj.unlink(self.env.cr, SUPERUSER_ID, view_id, context={})
for view_id in view_ids]
view_ids = []
view_id = self.pool.get('ir.ui.view').create(
self.env.cr, SUPERUSER_ID,
{'name': "Pivot Analysis",
'type': 'pivot',
'model': self.model_name,
'priority': 16,
'arch': """<?xml version="1.0"?>
<pivot string="Pivot Analysis"> {} </pivot>
""".format("".join(self._create_graph_view()))
}, context={})
view_ids.append(view_id)
view_id = self.pool.get('ir.ui.view').create(
self.env.cr, SUPERUSER_ID,
{'name': "Graph Analysis",
'type': 'graph',
'model': self.model_name,
'priority': 16,
'arch': """<?xml version="1.0"?>
<graph string="Graph Analysis"
type="bar"
stacked="True"> {} </graph>
""".format("".join(self._create_graph_view()))
}, context={})
view_ids.append(view_id)
view_id = self.pool.get('ir.ui.view').create(
self.env.cr, SUPERUSER_ID,
{'name': "Tree Analysis",
'type': 'tree',
'model': self.model_name,
'priority': 16,
'arch': """<?xml version="1.0"?>
<tree string="List Analysis" create="false"> {} </tree>
""".format("".join(self._create_tree_view()))
}, context={})
view_ids.append(view_id)
action_vals = {'name': self.name,
'res_model': self.model_name,
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'tree,graph,pivot',
'view_id': view_ids and view_ids[0] or 0,
'context': "{'service_name': '%s'}" % self.name,
}
act_window = self.env['ir.actions.act_window']
action_id = act_window.sudo().create(action_vals)
self.write({
'action_id': action_id.id,
'view_id': view_id,
'state': 'created'
})
return True
@api.multi @api.multi
def open_view(self): def open_view(self):
self.ensure_one()
return { return {
'name': _('BI View'),
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
'res_model': self.model_name, 'res_model': self.model_name,
'view_type': 'form', 'view_type': 'form',
'view_mode': 'tree,graph,pivot', 'view_mode': 'tree,graph,pivot',
} }
@api.multi
def copy(self, default=None):
self.ensure_one()
default = dict(default or {}, name=_("%s (copy)") % self.name)
return super(BveView, self).copy(default=default)

389
bi_view_editor/models/ir_model.py

@ -1,143 +1,233 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# © 2015-2016 ONESTEiN BV (<http://www.onestein.eu>)
# Copyright 2015-2017 Onestein (<http://www.onestein.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, api
from openerp import api, models
from openerp.modules.registry import RegistryManager from openerp.modules.registry import RegistryManager
NO_BI_MODELS = [ NO_BI_MODELS = [
"temp.range",
"account.statement.operation.template",
"fetchmail.server"
'temp.range',
'account.statement.operation.template',
'fetchmail.server'
] ]
NO_BI_FIELDS = [ NO_BI_FIELDS = [
"id",
"create_uid",
"create_date",
"write_uid",
"write_date"
'id',
'create_uid',
'create_date',
'write_uid',
'write_date'
] ]
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
}
NO_BI_TTYPES = [
'many2many',
'one2many',
'html',
'binary',
'reference'
]
def dict_for_model(model):
def dict_for_field(field):
return { return {
"id": model.id,
"name": model.name,
"model": model.model
'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
} }
class IrModel(models.Model): class IrModel(models.Model):
_inherit = 'ir.model' _inherit = 'ir.model'
@api.model
def _filter_bi_fields(self, ir_model_field_obj): def _filter_bi_fields(self, ir_model_field_obj):
name = ir_model_field_obj.name name = ir_model_field_obj.name
model = ir_model_field_obj.model_id model = ir_model_field_obj.model_id
model_name = model.model model_name = model.model
if name in self.env[model_name]._columns:
f = self.env[model_name]._columns[name]
Model = self.env[model_name]
if name in Model._columns:
f = Model._columns[name]
return f._classic_write return f._classic_write
return False return False
@api.model @api.model
def _filter_bi_models(self, model): def _filter_bi_models(self, model):
model_name = model["model"]
if model_name in NO_BI_MODELS or \
model_name.startswith("workflow") or \
model_name.startswith("ir.") or \
model["name"] == "Unknow" or \
"report" in model_name or \
model_name.startswith("base_") or \
"_" in model_name or \
"." in model["name"] or \
"mail" in model_name or \
"edi." in model_name:
return False
return self.env['ir.model.access'].check(
model["model"], 'read', False)
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_'):
return 1
return 0
def _check_contains(model_model):
if 'mail' in model_model or \
'_' in model_model or \
'report' in model_model or \
'edi.' in model_model:
return 1
return 0
def _check_unknow(model_name):
if model_name == 'Unknow' or '.' in model_name:
return 1
return 0
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_unknow(model_name)
if not count_check:
return self.env['ir.model.access'].check(
model['model'], 'read', False)
return False
@api.model @api.model
def get_related_fields(self, model_ids): def get_related_fields(self, model_ids):
""" Return list of field dicts for all fields that can be """ Return list of field dicts for all fields that can be
joined with models in model_ids joined with models in model_ids
""" """
model_names = dict([(model.id, model.model)
for model in self.env['ir.model'].sudo().search(
[('id', 'in', model_ids.values())])])
filter_bi_fields = self._filter_bi_fields
if filter_bi_fields:
rfields = [
dict(dict_for_field(field),
join_node=-1,
table_alias=model[0])
for field in filter(
filter_bi_fields,
self.env['ir.model.fields'].sudo().search(
[('model_id', 'in', model_ids.values()),
('ttype', 'in', ['many2one'])]))
for model in model_ids.items()
if model[1] == field.model_id.id
]
lfields = [
dict(dict_for_field(field),
join_node=model[0],
table_alias=-1)
for field in filter(
filter_bi_fields,
self.env['ir.model.fields'].sudo().search(
[('relation', 'in', model_names.values()),
('ttype', 'in', ['many2one'])]))
for model in model_ids.items()
if model_names[model[1]] == field['relation']
]
return [dict(field, join_node=model[0])
for field in lfields
if model_names[model[1]] == field['relation']] + [
dict(field, table_alias=model[0])
for model in model_ids.items()
for field in rfields if model[1] == field['model_id']]
Model = self.env['ir.model']
domain = [('id', 'in', model_ids.values())]
models = Model.sudo().search(domain)
model_names = {}
for model in models:
model_names.update({model.id: model.model})
related_fields = self._get_related_fields_list(model_ids, model_names)
return related_fields
@api.model
def _get_related_fields_list(self, model_ids, model_names):
def _get_right_fields(model_ids, model_names):
Fields = self.env['ir.model.fields']
rfields = []
domain = [('model_id', 'in', model_ids.values()),
('ttype', 'in', ['many2one'])]
for field in filter(
self._filter_bi_fields,
Fields.sudo().search(domain)):
for model in model_ids.items():
if model[1] == field.model_id.id:
rfields.append(
dict(dict_for_field(field),
join_node=-1,
table_alias=model[0])
)
return rfields
def _get_left_fields(model_ids, model_names):
Fields = self.env['ir.model.fields']
lfields = []
domain = [('relation', 'in', model_names.values()),
('ttype', 'in', ['many2one'])]
for field in filter(
self._filter_bi_fields,
Fields.sudo().search(domain)):
for model in model_ids.items():
if model_names[model[1]] == field['relation']:
lfields.append(
dict(dict_for_field(field),
join_node=model[0],
table_alias=-1)
)
return lfields
def _get_relation_list(model_ids, model_names, lfields):
relation_list = []
for model in model_ids.items():
for field in lfields:
if model_names[model[1]] == field['relation']:
relation_list.append(
dict(field, join_node=model[0])
)
return relation_list
def _get_model_list(model_ids, rfields):
model_list = []
for model in model_ids.items():
for field in rfields:
if model[1] == field['model_id']:
model_list.append(
dict(field, table_alias=model[0])
)
return model_list
lfields = _get_left_fields(model_ids, model_names)
rfields = _get_right_fields(model_ids, model_names)
relation_list = _get_relation_list(model_ids, model_names, lfields)
model_list = _get_model_list(model_ids, rfields)
related_fields = relation_list + model_list
return related_fields
@api.model @api.model
def get_related_models(self, model_ids): def get_related_models(self, model_ids):
""" Return list of model dicts for all models that can be """ Return list of model dicts for all models that can be
joined with models in model_ids joined with models in model_ids
""" """
def _get_field(fields, orig, target):
field_list = []
for f in fields:
if f[orig] == -1:
field_list.append(f[target])
return field_list
def _get_list_id(model_ids, fields):
list_model = model_ids.values()
list_model += _get_field(fields, 'table_alias', 'model_id')
return list_model
def _get_list_relation(fields):
list_model = _get_field(fields, 'join_node', 'relation')
return list_model
models_list = []
related_fields = self.get_related_fields(model_ids) related_fields = self.get_related_fields(model_ids)
return sorted(filter(
self._filter_bi_models,
[{"id": model.id, "name": model.name, "model": model.model}
for model in self.env['ir.model'].sudo().search(
['|',
('id', 'in', model_ids.values() + [
f['model_id']
for f in related_fields if f['table_alias'] == -1]),
('model', 'in', [
f['relation']
for f in related_fields if f['join_node'] == -1])])]),
key=lambda x: x['name'])
list_id = _get_list_id(model_ids, related_fields)
list_model = _get_list_relation(related_fields)
domain = ['|',
('id', 'in', list_id),
('model', 'in', list_model)]
models = self.env['ir.model'].sudo().search(domain)
for model in models:
models_list.append({
'id': model.id,
'name': model.name,
'model': model.model
})
return sorted(
filter(self._filter_bi_models, models_list),
key=lambda x: x['name']
)
@api.model @api.model
def get_models(self): def get_models(self):
""" Return list of model dicts for all available models. """ Return list of model dicts for all available models.
""" """
def dict_for_model(model):
return {
'id': model.id,
'name': model.name,
'model': model.model
}
models_domain = [('transient', '=', False)] models_domain = [('transient', '=', False)]
return sorted(filter( return sorted(filter(
self._filter_bi_models, self._filter_bi_models,
@ -152,18 +242,31 @@ class IrModel(models.Model):
Return all possible join nodes to add new_field to the query Return all possible join nodes to add new_field to the query
containing model_ids. containing model_ids.
""" """
model_ids = dict([(field['table_alias'],
field['model_id']) for field in field_data])
def _get_model_ids(field_data):
model_ids = dict([(field['table_alias'],
field['model_id']) for field in field_data])
return model_ids
def _get_join_nodes_dict(model_ids, new_field):
join_nodes = []
for alias, model_id in model_ids.items():
if model_id == new_field['model_id']:
join_nodes.append({'table_alias': alias})
for dict_field in self.get_related_fields(model_ids):
condition = [
dict_field['join_node'] == -1,
dict_field['table_alias'] == -1
]
relation = (new_field['model'] == dict_field['relation'])
model = (new_field['model_id'] == dict_field['model_id'])
if (relation and condition[0]) or (model and condition[1]):
join_nodes.append(dict_field)
return join_nodes
model_ids = _get_model_ids(field_data)
keys = [(field['table_alias'], field['id']) keys = [(field['table_alias'], field['id'])
for field in field_data if field.get('join_node', -1) != -1] for field in field_data if field.get('join_node', -1) != -1]
join_nodes = ([{'table_alias': alias}
for alias, model_id in model_ids.items()
if model_id == new_field['model_id']] + [
d for d in self.get_related_fields(model_ids)
if (d['relation'] == new_field['model'] and
d['join_node'] == -1) or
(d['model_id'] == new_field['model_id'] and
d['table_alias'] == -1)])
join_nodes = _get_join_nodes_dict(model_ids, new_field)
return filter( return filter(
lambda x: 'id' not in x or lambda x: 'id' not in x or
(x['table_alias'], x['id']) not in keys, join_nodes) (x['table_alias'], x['id']) not in keys, join_nodes)
@ -172,47 +275,53 @@ class IrModel(models.Model):
def get_fields(self, model_id): def get_fields(self, model_id):
bi_field_domain = [ bi_field_domain = [
('model_id', '=', model_id), ('model_id', '=', model_id),
("name", "not in", NO_BI_FIELDS),
('ttype', 'not in', [
'many2many', "one2many", "html", "binary", "reference"])
('name', 'not in', NO_BI_FIELDS),
('ttype', 'not in', NO_BI_TTYPES)
] ]
filter_bi_fields = self._filter_bi_fields
fields_obj = self.env['ir.model.fields']
fields = filter(filter_bi_fields,
fields_obj.sudo().search(bi_field_domain))
return sorted([{"id": field.id,
"model_id": model_id,
"name": field.name,
"description": field.field_description,
"type": field.ttype,
"custom": False,
"model": field.model_id.model,
"model_name": field.model_id.name}
for field in fields], key=lambda x: x['description'],
reverse=True)
def create(self, cr, user, vals, context=None):
if context is None:
context = {}
if context and context.get('bve'):
vals['state'] = 'base'
res = super(IrModel, self).create(cr, user, vals, context)
if vals.get('state', 'base') == 'bve':
vals['state'] = 'manual'
Fields = self.env['ir.model.fields']
fields = filter(
self._filter_bi_fields,
Fields.sudo().search(bi_field_domain)
)
fields_dict = []
for field in fields:
fields_dict.append(
{'id': field.id,
'model_id': model_id,
'name': field.name,
'description': field.field_description,
'type': field.ttype,
'custom': False,
'model': field.model_id.model,
'model_name': field.model_id.name
}
)
sorted_fields = sorted(
fields_dict,
key=lambda x: x['description'],
reverse=True
)
return sorted_fields
# add model in registry
self.instanciate(cr, user, vals['model'], context)
self.pool.setup_models(cr, partial=(not self.pool.ready))
RegistryManager.signal_registry_change(cr.dbname)
@api.model
def create(self, vals):
if self._context and self._context.get('bve'):
vals['state'] = 'base'
res = super(IrModel, self).create(vals)
# Following commented line (write method) is not working anymore
# as in Odoo V9 a new orm constraint is restricting the modification
# of the state while updating ir.model
# self.write(cr, user, [res], {'state': 'manual'})
# 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' q = ("""UPDATE ir_model SET state = 'manual'
WHERE id = """ + str(res))
WHERE id = """ + str(res.id))
self.env.cr.execute(q)
# update registry
if self._context.get('bve'):
# setup models; this reloads custom models in registry
self.pool.setup_models(self._cr, partial=(not self.pool.ready))
cr.execute(q)
# signal that registry has changed
RegistryManager.signal_registry_change(self.env.cr.dbname)
return res return res

18
bi_view_editor/security/rules.xml

@ -1,13 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<odoo>
<record model="ir.rule" id="bve_view_rule">
<field name="name">bve_view read access</field>
<field name="model_id" search="[('model','=','bve.view')]" model="ir.model"/>
<field name="global" eval="True"/>
<field name="domain_force"> ['|',('user_ids','=',False),('user_ids','in',user.id)]</field>
</record>
<record id="bve_view_rule" model="ir.rule">
<field name="name">bve_view read access</field>
<field name="model_id" search="[('model','=','bve.view')]" model="ir.model"/>
<field name="global" eval="True"/>
<field name="domain_force"> ['|',('user_ids','=',False),('user_ids','in',user.id)]</field>
</record>
</data>
</openerp>
</odoo>

20
bi_view_editor/static/src/css/bve.css

@ -32,7 +32,7 @@
} }
.oe_form_field_bi_editor .body { .oe_form_field_bi_editor .body {
padding-bottom: 0px;
padding-bottom: 0;
} }
.oe_form_field_bi_editor .body .left { .oe_form_field_bi_editor .body .left {
@ -50,15 +50,15 @@
.oe_form_field_bi_editor .body .left .search-bar input { .oe_form_field_bi_editor .body .left .search-bar input {
width: 100%; width: 100%;
border-radius: 0px;
border-left: 0px;
border-right: 0px;
border-top: 0px;
border-radius: 0;
border-left: 0;
border-right: 0;
border-top: 0;
padding-left: 18px; padding-left: 18px;
padding-top: 4px; padding-top: 4px;
position: absolute; position: absolute;
left: 0px;
top: 0px;
left: 0;
top: 0;
z-index: 1; z-index: 1;
} }
@ -70,7 +70,7 @@
} }
.oe_form_field_bi_editor .body .left .class-list { .oe_form_field_bi_editor .body .left .class-list {
height: 400px; /* FIXME */
height: 400px;
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;
} }
@ -100,7 +100,7 @@
width: 70%; width: 70%;
float: left; float: left;
box-sizing: border-box; box-sizing: border-box;
height: 423px; /* FIXME */
height: 423px;
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;
} }
@ -138,7 +138,7 @@
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
border: 1px solid #DDDDDD; border: 1px solid #DDDDDD;
list-style-type: none; list-style-type: none;
padding: 0px;
padding: 0;
width: 175px; width: 175px;
} }

249
bi_view_editor/static/src/js/bve.js

@ -8,7 +8,6 @@ openerp.bi_view_editor = function (instance, local) {
this._super.apply(this, arguments); this._super.apply(this, arguments);
}, },
start: function() { start: function() {
var self = this;
this._super(); this._super();
this.on("change:effective_readonly", this, function() { this.on("change:effective_readonly", this, function() {
this.display_field(); this.display_field();
@ -49,20 +48,20 @@ openerp.bi_view_editor = function (instance, local) {
} }
}, },
filter: function(val) { filter: function(val) {
val = (typeof val != 'undefined') ? val.toLowerCase() : this.currentFilter;
val = (typeof val !== 'undefined') ? val.toLowerCase() : this.currentFilter;
this.currentFilter = val; this.currentFilter = val;
this.$el.find(".class-list .class-container").each(function() { this.$el.find(".class-list .class-container").each(function() {
var modelData = $(this).find(".class").data('model-data'); var modelData = $(this).find(".class").data('model-data');
//TODO: filter on all model fields (name, technical name, etc) //TODO: filter on all model fields (name, technical name, etc)
if(typeof modelData == 'undefined' || (modelData.name.toLowerCase().indexOf(val) == -1 && modelData.model.toLowerCase().indexOf(val) == -1))
if(typeof modelData === 'undefined' || (modelData.name.toLowerCase().indexOf(val) === -1 && modelData.model.toLowerCase().indexOf(val) === -1))
$(this).hide(); $(this).hide();
else else
$(this).show(); $(this).show();
}); });
}, },
get_field_icons: function(field) { get_field_icons: function(field) {
icons = "";
var icons = "";
if(field.column) if(field.column)
icons += "<span class='fa fa-columns' title='Column'></span> "; icons += "<span class='fa fa-columns' title='Column'></span> ";
if(field.row) if(field.row)
@ -79,7 +78,7 @@ openerp.bi_view_editor = function (instance, local) {
this.set_fields(JSON.parse(this.get('value'))); this.set_fields(JSON.parse(this.get('value')));
}, },
load_classes: function(scrollTo) { load_classes: function(scrollTo) {
scrollTo = (typeof scrollTo == 'undefined') ? false : scrollTo;
scrollTo = (typeof scrollTo === 'undefined') ? false : scrollTo;
var self = this; var self = this;
var model = new instance.web.Model("ir.model"); var model = new instance.web.Model("ir.model");
if (this.$el.find(".field-list tbody tr").length > 0) { if (this.$el.find(".field-list tbody tr").length > 0) {
@ -98,98 +97,93 @@ openerp.bi_view_editor = function (instance, local) {
self.$el.find(".class-list .class").remove(); self.$el.find(".class-list .class").remove();
self.$el.find(".class-list .field").remove(); self.$el.find(".class-list .field").remove();
var css = this.get('effective_readonly') ? 'cursor: default' : 'cursor: pointer'; var css = this.get('effective_readonly') ? 'cursor: default' : 'cursor: pointer';
function addField() {
if (!self.get("effective_readonly")) {
self.add_field($(this));
}
}
function clickHandler(evt) {
if(self.get("effective_readonly")) return;
var classel = $(this);
if (classel.data('bve-processed')) {
classel.parent().find('.field').remove();
classel.data('bve-processed', false);
var index = self.activeModelMenus.indexOf(classel.data('model-data').id);
if(index !== -1) self.activeModelMenus.splice(index, 1);
} else {
self.activeModelMenus.push(classel.data('model-data').id);
model.call("get_fields", [classel.data('model-data').id], { context: new instance.web.CompoundContext() }).then(function(result) {
for (var i = 0; i < result.length; i++) {
classel.find("#bve-field-" + result[i].name).remove();
self._render_field(self, i, result, classel, addField)
}
});
$(this).data('bve-processed', true);
}
}
function renderFields(result) {
console.log(result);
var item = self.$el.find(".class-list #bve-class-" + result[0].model_id);
for (var o = 0; o < result.length; o++) {
self._render_field(self, o, result, item, addField)
}
item.data('bve-processed', true);
}
for (var i = 0; i < result.length; i++) { for (var i = 0; i < result.length; i++) {
var item = $("<div style=\"" + css + "\" class=\"class\" title=\"" + result[i].model + "\" id=\"bve-class-" + result[i].id + "\">" + result[i].name + "</div>") var item = $("<div style=\"" + css + "\" class=\"class\" title=\"" + result[i].model + "\" id=\"bve-class-" + result[i].id + "\">" + result[i].name + "</div>")
.data('model-data', result[i]) .data('model-data', result[i])
.click(function (evt) {
if(self.get("effective_readonly")) return;
var classel = $(this);
if (classel.data('bve-processed')) {
classel.parent().find('.field').remove();
classel.data('bve-processed', false);
var index = self.activeModelMenus.indexOf(classel.data('model-data').id);
if(index != -1) self.activeModelMenus.splice(index, 1);
} else {
self.activeModelMenus.push(classel.data('model-data').id);
model.call("get_fields", [classel.data('model-data').id], { context: new instance.web.CompoundContext() }).then(function(result) {
for (var i = 0; i < result.length; i++) {
classel.find("#bve-field-" + result[i].name).remove();
if(self.$el.find(".field-list tbody [name=label-" + result[i].id + "]").length > 0) continue;
classel.after($("<div class=\"field\" title=\"" + result[i].name + "\" id=\"bve-field-" + result[i].name + "\">" + result[i].description + "</div>")
.data('field-data', result[i])
.click(function () {
if (!self.get("effective_readonly")) {
self.add_field($(this));
}
})
.draggable({
'revert': 'invalid',
'scroll': false,
'helper': 'clone',
'appendTo': 'body',
'containment': 'window'
})
);
}
});
$(this).data('bve-processed', true);
}
})
.click(clickHandler)
.wrap("<div class=\"class-container\"></div>").parent(); .wrap("<div class=\"class-container\"></div>").parent();
self.$el.find(".class-list").append(item); self.$el.find(".class-list").append(item);
var index = self.activeModelMenus.indexOf(item.find(".class").data('model-data').id); var index = self.activeModelMenus.indexOf(item.find(".class").data('model-data').id);
if(index != -1 && !self.get("effective_readonly")) {
model.call("get_fields", [self.activeModelMenus[index]], { context: new instance.web.CompoundContext() }).then(function(result) {
console.log(result);
var item = self.$el.find(".class-list #bve-class-" + result[0].model_id);
for (var o = 0; o < result.length; o++) {
if(self.$el.find(".field-list tbody [name=label-" + result[o].id + "]").length > 0) continue;
item.after($("<div class=\"field\" title=\"" + result[o].name + "\" id=\"bve-field-" + result[o].name + "\">" + result[o].description + "</div>")
.data('field-data', result[o])
.click(function () {
if (!self.get("effective_readonly")) {
self.add_field($(this));
}
})
.draggable({
'revert': 'invalid',
'scroll': false,
'helper': 'clone',
'appendTo': 'body',
'containment': 'window'
}));
}
item.data('bve-processed', true);
});
if(index !== -1 && !self.get("effective_readonly")) {
model.call("get_fields", [self.activeModelMenus[index]], { context: new instance.web.CompoundContext() }).then(renderFields);
} }
self.filter(); self.filter();
} }
}, },
_render_field(_self, _index, _result, _item, _addField) {
if(_self.$el.find(".field-list tbody [name=label-" + _result[_index].id + "]").length > 0) return;
_item.after($("<div class=\"field\" title=\"" + _result[_index].name + "\" id=\"bve-field-" + _result[_index].name + "\">" + _result[_index].description + "</div>")
.data('field-data', _result[_index])
.click(_addField)
.draggable({
'revert': 'invalid',
'scroll': false,
'helper': 'clone',
'appendTo': 'body',
'containment': 'window'
})
);
},
set_checkbox: function(check, identifier, _contextMenu) {
if(check)
_contextMenu.find(identifier).attr('checked', true);
else
_contextMenu.find(identifier).attr('checked', false);
},
_false_if_undefined: function(to_check) {
if (typeof check === 'undefined') return false;
return check;
},
add_field_to_table: function(data, options) { add_field_to_table: function(data, options) {
var self = this; var self = this;
if (typeof data.row == 'undefined') {
data.row = false;
}
if (typeof data.column == 'undefined') {
data.column = false;
}
if (typeof data.measure == 'undefined') {
data.measure = false;
}
data.row = self._false_if_undefined(data.row);
data.column = self._false_if_undefined(data.column);
data.measure = self._false_if_undefined(data.measure);
var n = 1; var n = 1;
var name = data.name; var name = data.name;
while ($.grep(self.get_fields(), function (el) { return el.name == data.name;}).length > 0) {
function checkNameMatches(el) { return el.name === data.name;}
while ($.grep(self.get_fields(), checkNameMatches).length > 0) {
data.name = name + '_' + n; data.name = name + '_' + n;
n += 1; n += 1;
} }
var classes = ""; var classes = "";
if (typeof data.join_node != 'undefined') {
if (typeof data.join_node !== 'undefined') {
classes = "join-node displaynone"; classes = "join-node displaynone";
} }
var delete_button = ""; var delete_button = "";
@ -200,7 +194,7 @@ openerp.bi_view_editor = function (instance, local) {
} }
self.$el.find(".field-list tbody") self.$el.find(".field-list tbody")
.append($("<tr class=\"" + classes + "\"><td><input " + disabled + "title=\"" + data.name + " (" + data.model + ") "+ "\" type=\"text\" name=\"label-" + data.id + "\" value=\"" + data.description + "\"/></td><td>" + data.model_name + "</td><td>" + self.get_field_icons(data) + "</td><td>" + delete_button + "</td></tr>")
.append($("<tr class=\"" + classes + "\"><td><input " + disabled + "title=\"" + data.name + " (" + data.model + ")\" type=\"text\" name=\"label-" + data.id + "\" value=\"" + data.description + "\"/></td><td>" + data.model_name + "</td><td>" + self.get_field_icons(data) + "</td><td>" + delete_button + "</td></tr>")
.data('field-data', data) .data('field-data', data)
.contextmenu(function(e) { .contextmenu(function(e) {
e.preventDefault(); e.preventDefault();
@ -221,54 +215,29 @@ openerp.bi_view_editor = function (instance, local) {
$(this).find("ul").hide(); $(this).find("ul").hide();
}); });
//Set checkboxes
if(currentFieldData.column)
contextMenu.find('#column-checkbox').attr('checked', true);
else
contextMenu.find('#column-checkbox').attr('checked', false);
if(currentFieldData.row)
contextMenu.find('#row-checkbox').attr('checked', true);
else
contextMenu.find('#row-checkbox').attr('checked', false);
if(currentFieldData.measure)
contextMenu.find('#measure-checkbox').attr('checked', true);
else
contextMenu.find('#measure-checkbox').attr('checked', false);
//Set checkboxes
self.set_checkbox(currentFieldData.column, '#column-checkbox', contextMenu);
self.set_checkbox(currentFieldData.row, '#row-checkbox', contextMenu);
self.set_checkbox(currentFieldData.measure, '#measure-checkbox', contextMenu);
if(currentFieldData.type == "float" || currentFieldData.type == "integer" || currentFieldData.type == "monetary") {
contextMenu.find('#column-checkbox').attr('disabled', true);
contextMenu.find('#row-checkbox').attr('disabled', true);
contextMenu.find('#measure-checkbox').attr('disabled', false);
}
else {
contextMenu.find('#column-checkbox').attr('disabled', false);
contextMenu.find('#row-checkbox').attr('disabled', false);
contextMenu.find('#measure-checkbox').attr('disabled', true);
}
var to_disable = false;
if(currentFieldData.type === "float" || currentFieldData.type === "integer" || currentFieldData.type === "monetary") to_disable = true;
var identifiers = [['#column-checkbox', 'column', to_disable], ['#row-checkbox', 'row', to_disable], ['#measure-checkbox', 'measure', !to_disable]];
identifiers.forEach(function (element) {
contextMenu.find(element[0]).attr('disabled', element[2]);
});
//Add change events //Add change events
contextMenu.find('#column-checkbox').unbind("change");
contextMenu.find('#column-checkbox').change(function() {
currentFieldData.column = $(this).is(":checked");
target.data('field-data', currentFieldData);
self.update_field_view(target);
self.internal_set_value(JSON.stringify(self.get_fields()));
});
contextMenu.find('#row-checkbox').unbind("change");
contextMenu.find('#row-checkbox').change(function() {
currentFieldData.row = $(this).is(":checked");
target.data('field-data', currentFieldData);
self.update_field_view(target);
self.internal_set_value(JSON.stringify(self.get_fields()));
});
contextMenu.find('#measure-checkbox').unbind("change");
contextMenu.find('#measure-checkbox').change(function() {
currentFieldData.measure = $(this).is(":checked");
target.data('field-data', currentFieldData);
self.update_field_view(target);
self.internal_set_value(JSON.stringify(self.get_fields()));
identifiers.forEach(function (element) {
contextMenu.find(element[0]).unbind("change");
contextMenu.find(element[0]).change(function() {
currentFieldData[element[1]] = $(this).is(":checked");
target.data('field-data', currentFieldData);
self.update_field_view(target);
self.internal_set_value(JSON.stringify(self.get_fields()));
});
}); });
contextMenu.show(); contextMenu.show();
@ -302,7 +271,7 @@ openerp.bi_view_editor = function (instance, local) {
this.$el.find(".field-list tbody tr").each(function (idx, el) { this.$el.find(".field-list tbody tr").each(function (idx, el) {
var d = $(this).data('field-data'); var d = $(this).data('field-data');
if (typeof d.join_node != 'undefined' && aliases.indexOf(d.join_node) === -1) {
if (typeof d.join_node !== 'undefined' && aliases.indexOf(d.join_node) === -1) {
$(this).remove(); $(this).remove();
} }
}); });
@ -324,28 +293,28 @@ openerp.bi_view_editor = function (instance, local) {
return model_data; return model_data;
}, },
get_table_alias: function(field) { get_table_alias: function(field) {
if (typeof field.table_alias != 'undefined') {
if (typeof field.table_alias !== 'undefined') {
return field.table_alias; return field.table_alias;
} else { } else {
var model_ids = this.get_model_ids(); var model_ids = this.get_model_ids();
var n = 0; var n = 0;
while (typeof model_ids["t" + n] != 'undefined') n++;
while (typeof model_ids["t" + n] !== 'undefined') n++;
return "t" + n; return "t" + n;
} }
}, },
add_field_and_join_node: function(field, join_node) { add_field_and_join_node: function(field, join_node) {
var self = this; var self = this;
if (join_node.join_node == -1) {
field.table_alias = self.get_table_alias(field);
join_node.join_node = field.table_alias;
self.add_field_to_table(join_node);
} else if (join_node.table_alias == -1) {
var go_to_else = true;
if (join_node.join_node === -1 || join_node.table_alias === -1){
go_to_else = false;
field.table_alias = self.get_table_alias(field); field.table_alias = self.get_table_alias(field);
join_node.table_alias = field.table_alias;
if (join_node.join_node === -1) join_node.join_node = field.table_alias;
else join_node.table_alias = field.table_alias;
self.add_field_to_table(join_node); self.add_field_to_table(join_node);
} else {
field.table_alias = join_node.table_alias;
} }
else field.table_alias = join_node.table_alias;
self.add_field_to_table(field); self.add_field_to_table(field);
self.internal_set_value(JSON.stringify(self.get_fields())); self.internal_set_value(JSON.stringify(self.get_fields()));
self.load_classes(field); self.load_classes(field);
@ -358,7 +327,7 @@ openerp.bi_view_editor = function (instance, local) {
var self = this; var self = this;
model.call('get_join_nodes', [field_data, data], {context: new instance.web.CompoundContext()}).then(function(result) { model.call('get_join_nodes', [field_data, data], {context: new instance.web.CompoundContext()}).then(function(result) {
if (result.length == 1) {
if (result.length === 1) {
self.add_field_and_join_node(data, result[0]); self.add_field_and_join_node(data, result[0]);
self.internal_set_value(JSON.stringify(self.get_fields())); self.internal_set_value(JSON.stringify(self.get_fields()));
//self.load_classes(data); //self.load_classes(data);
@ -367,7 +336,7 @@ openerp.bi_view_editor = function (instance, local) {
pop.display_popup(result, self.get_model_data(), self.add_field_and_join_node.bind(self), data); pop.display_popup(result, self.get_model_data(), self.add_field_and_join_node.bind(self), data);
} else { } else {
// first field and table only. // first field and table only.
table_alias = self.get_table_alias(data);
var table_alias = self.get_table_alias(data);
data.table_alias = table_alias; data.table_alias = table_alias;
self.add_field_to_table(data); self.add_field_to_table(data);
self.internal_set_value(JSON.stringify(self.get_fields())); self.internal_set_value(JSON.stringify(self.get_fields()));
@ -396,7 +365,6 @@ openerp.bi_view_editor = function (instance, local) {
}); });
instance.web.form.widgets.add('BVEEditor', 'instance.bi_view_editor.BVEEditor'); instance.web.form.widgets.add('BVEEditor', 'instance.bi_view_editor.BVEEditor');
local.JoinNodePopup = instance.web.Widget.extend({ local.JoinNodePopup = instance.web.Widget.extend({
template: "JoinNodePopup", template: "JoinNodePopup",
start: function() { start: function() {
@ -410,14 +378,14 @@ openerp.bi_view_editor = function (instance, local) {
joinnodes.empty(); joinnodes.empty();
for (var i=0; i<choices.length; i++) { for (var i=0; i<choices.length; i++) {
var description = ""; var description = "";
if (choices[i].join_node != -1 && choices[i].table_alias != -1) {
if (choices[i].join_node !== -1 && choices[i].table_alias !== -1) {
description = "Use the field on table " + model_data[choices[i].table_alias].model_name; description = "Use the field on table " + model_data[choices[i].table_alias].model_name;
} else { } else {
if (choices[i].join_node == -1) {
description = "Join using the field '" + choices[i].description + "' from model '" + choices[i].model_name + "'";
} else {
description = "Join using the field '" + choices[i].description + "' from new model '" + choices[i].model_name + "'";
var new_str = "";
if (choices[i].join_node !== -1) {
new_str = "new ";
} }
description = "Join using the field '" + choices[i].description + "' from " + new_str + "model '" + choices[i].model_name + "'";
} }
joinnodes.append($("<a>" + description+ "</a>") joinnodes.append($("<a>" + description+ "</a>")
.data('idx', i) .data('idx', i)
@ -439,4 +407,5 @@ openerp.bi_view_editor = function (instance, local) {
this.start(); this.start();
} }
}); });
}; };

2
bi_view_editor/templates/assets_template.xml

@ -2,7 +2,7 @@
<openerp> <openerp>
<data> <data>
<template id="bve_js" name="bi view editor assets" inherit_id="web.assets_backend">
<template id="assets_backend" name="bi_view_editor assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside"> <xpath expr="." position="inside">
<link rel="stylesheet" href="/bi_view_editor/static/src/css/bve.css"/> <link rel="stylesheet" href="/bi_view_editor/static/src/css/bve.css"/>
<script type="text/javascript" src="/bi_view_editor/static/src/js/bve.js"></script> <script type="text/javascript" src="/bi_view_editor/static/src/js/bve.js"></script>

109
bi_view_editor/views/bve_view.xml

@ -1,70 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<odoo>
<record id="view_bi_view_editor_view_tree" model="ir.ui.view">
<field name="name">bve.view.tree</field>
<field name="model">bve.view</field>
<field name="arch" type="xml">
<tree string="Custom BI View">
<field name="name"/>
</tree>
</field>
</record>
<record id="view_bi_view_editor_view_tree" model="ir.ui.view">
<field name="model">bve.view</field>
<field name="arch" type="xml">
<tree string="Custom BI View">
<field name="name"/>
</tree>
</field>
</record>
<record id="view_bi_view_editor_view_form" model="ir.ui.view">
<field name="name">bve.view.form</field>
<field name="model">bve.view</field>
<field name="arch" type="xml">
<form string="Custom Object">
<header>
<button name="action_reset" type="object" states="created" string="Reset to Draft"/>
<button name="action_create" type="object" states="draft" string="Generate BI View" class="oe_highlight" context="{'bve':True}"/>
<button name="open_view" type="object" states="created" string="Open BI View" class="oe_highlight"/>
<field name="state" widget="statusbar" statusbar_visible="draft,created" statusbar_colors='{"draft":"blue","created":"blue"}'/>
</header>
<sheet>
<h1>
<record id="view_bi_view_editor_view_form" model="ir.ui.view">
<field name="model">bve.view</field>
<field name="arch" type="xml">
<form string="Custom Object">
<header>
<button name="action_reset" type="object" states="created" string="Reset to Draft"/>
<button name="action_create" type="object" states="draft" string="Generate BI View" class="oe_highlight"/>
<button name="open_view" type="object" states="created" string="Open BI View" class="oe_highlight"/>
<field name="state" widget="statusbar" statusbar_visible="draft,created" statusbar_colors='{"draft":"blue","created":"blue"}'/>
</header>
<sheet>
<h1>
<field name="name" attrs="{'readonly': [('state','=','created')]}" colspan="4"/> <field name="name" attrs="{'readonly': [('state','=','created')]}" colspan="4"/>
</h1>
<notebook>
</h1>
<notebook>
<page string="Query"> <page string="Query">
<field name="data" widget="BVEEditor" nolabel="1" attrs="{'readonly': [('state','=','created')]}"/>
<group>
<field name="data" widget="BVEEditor" nolabel="1" attrs="{'readonly': [('state','=','created')]}"/>
</group>
</page> </page>
<page string="Security"> <page string="Security">
<field nolabel="1" name="group_ids" />
<field nolabel="1" name="group_ids" />
</page> </page>
<page string="Notes"> <page string="Notes">
<field name="note" nolabel="1" colspan="4"/>
<field name="note" nolabel="1" colspan="4"/>
</page> </page>
</notebook>
</sheet>
</form>
</field>
</record>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="action_bi_view_editor_view_form" model="ir.actions.act_window">
<field name="name">Custom BI Views</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">bve.view</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
<record id="action_bi_view_editor_view_form" model="ir.actions.act_window">
<field name="name">Custom BI Views</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">bve.view</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a Custom Query Object. Click to create a Custom Query Object.
</p><p>
</p>
<p>
</p>
</field>
</record>
</p>
</field>
</record>
<menuitem id="menu_bi_view_editor_custom_reports"
name="Custom Reports"
parent="base.menu_board_root"
sequence="0"/>
<menuitem id="menu_bi_view_editor_view"
parent="menu_bi_view_editor_custom_reports"
action="action_bi_view_editor_view_form"/>
<menuitem id="menu_bi_view_editor_custom_reports"
name="Custom Reports"
parent="base.menu_board_root"
sequence="0"/>
<menuitem id="menu_bi_view_editor_view"
parent="menu_bi_view_editor_custom_reports"
action="action_bi_view_editor_view_form"/>
</data>
</openerp>
</odoo>
Loading…
Cancel
Save