Porting bi_view_editor to V10

pull/106/head
Andrea 2017-03-06 09:48:49 +01:00
parent 4dc279bde6
commit 879d3f6bfd
9 changed files with 401 additions and 362 deletions

View File

@ -38,7 +38,7 @@ To graphically design your analysis data-set:
.. 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
:target: https://runbot.odoo-community.org/runbot/143/9.0 :target: https://runbot.odoo-community.org/runbot/143/10.0
Known issues / Roadmap Known issues / Roadmap
====================== ======================

View File

@ -10,7 +10,7 @@
'license': 'AGPL-3', 'license': 'AGPL-3',
'website': 'http://www.onestein.eu', 'website': 'http://www.onestein.eu',
'category': 'Reporting', 'category': 'Reporting',
'version': '9.0.1.0.0', 'version': '10.0.1.0.0',
'depends': [ 'depends': [
'base', 'base',
'web' 'web'
@ -24,9 +24,6 @@
'qweb': [ 'qweb': [
'templates/qweb_template.xml', 'templates/qweb_template.xml',
], ],
'js': [ 'installable': True,
'static/src/js/bve.js'
],
'installable': False,
'uninstall_hook': 'uninstall_hook' 'uninstall_hook': 'uninstall_hook'
} }

View File

@ -2,5 +2,6 @@
# Copyright 2015-2017 Onestein (<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 bve_view from . import bve_view
from . import ir_model from . import ir_model

View File

@ -4,9 +4,9 @@
import json import json
from openerp import api, fields, models, tools from odoo import api, fields, models, tools
from openerp.exceptions import Warning as UserError from odoo.exceptions import Warning as UserError
from openerp.tools.translate import _ from odoo.tools.translate import _
class BveView(models.Model): class BveView(models.Model):
@ -23,11 +23,17 @@ class BveView(models.Model):
else: 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.multi
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
name = fields.Char(required=True, copy=False) name = fields.Char(required=True, copy=False)
model_name = fields.Char() model_name = fields.Char(compute='_compute_model_name', store=True)
note = fields.Text(string='Notes') note = fields.Text(string='Notes')
state = fields.Selection( state = fields.Selection(
[('draft', 'Draft'), [('draft', 'Draft'),
('created', 'Created')], ('created', 'Created')],
@ -37,21 +43,18 @@ class BveView(models.Model):
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') action_id = fields.Many2one('ir.actions.act_window', string='Action')
view_id = fields.Many2one('ir.ui.view', string='View') 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 = [
@ -60,40 +63,23 @@ class BveView(models.Model):
_('Custom BI View names must be unique!')), _('Custom BI View names must be unique!')),
] ]
@api.multi @api.model
def unlink(self): def _get_format_data(self, data):
for view in self: data = data.replace('\'', '"')
if view.state == 'created': data = data.replace(': u"', ':"')
raise UserError( return data
_('You cannot delete a created view! '
'Reset the view to draft first.'))
return super(BveView, self).unlink()
@api.multi
def action_reset(self):
self.ensure_one()
if self.action_id:
if self.action_id.view_id:
self.action_id.view_id.sudo().unlink()
self.action_id.sudo().unlink()
models = self.env['ir.model'].sudo().search(
[('model', '=', self.model_name)])
for model in models:
model.sudo().unlink()
table_name = self.model_name.replace('.', '_')
tools.drop_view_if_exists(self.env.cr, table_name)
self.state = 'draft'
@api.multi @api.multi
def _create_view_arch(self): def _create_view_arch(self):
self.ensure_one() self.ensure_one()
def _get_field_def(field_name, def_type): def _get_field_def(name, type=False):
if not type:
return """<field name="x_{}" />""".format(
name
)
return """<field name="x_{}" type="{}" />""".format( return """<field name="x_{}" type="{}" />""".format(
field_name, def_type name, type
) )
def _get_field_type(field_info): def _get_field_type(field_info):
@ -102,28 +88,32 @@ class BveView(models.Model):
measure = field_info['measure'] and 'measure' measure = field_info['measure'] and 'measure'
return row or column or measure return row or column or measure
fields_info = json.loads(self._get_format_data(self.data)) def _get_field_list(fields_info):
view_fields = [] view_fields = []
all_fields = []
for field_info in fields_info: for field_info in fields_info:
field_name = field_info['name'] field_name = field_info['name']
field_type = field_info['type']
def_type = _get_field_type(field_info) def_type = _get_field_type(field_info)
if def_type:
field_def = _get_field_def(field_name, def_type) field_def = _get_field_def(field_name, def_type)
if def_type:
view_fields.append(field_def) view_fields.append(field_def)
if field_type not in ['many2one', 'one2many', 'many2many']:
all_fields.append(field_def)
return view_fields, all_fields
fields_info = json.loads(self._get_format_data(self.data))
is_tree_view = self._context.get('no_empty')
view_fields, all_fields = _get_field_list(fields_info)
if not view_fields and is_tree_view:
view_fields = all_fields
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 _create_tree_view_arch(self):
self.ensure_one() self.ensure_one()
return self.with_context(no_empty=True)._create_view_arch()
self._create_bve_object()
self._create_bve_view()
@api.multi @api.multi
def _create_bve_view(self): def _create_bve_view(self):
@ -140,7 +130,9 @@ class BveView(models.Model):
'model': self.model_name, 'model': self.model_name,
'priority': 16, 'priority': 16,
'arch': """<?xml version="1.0"?> 'arch': """<?xml version="1.0"?>
<pivot string="Pivot Analysis"> {} </pivot> <pivot string="Pivot Analysis">
{}
</pivot>
""".format("".join(self._create_view_arch())) """.format("".join(self._create_view_arch()))
}, { }, {
'name': 'Graph Analysis', 'name': 'Graph Analysis',
@ -149,8 +141,19 @@ class BveView(models.Model):
'priority': 16, 'priority': 16,
'arch': """<?xml version="1.0"?> 'arch': """<?xml version="1.0"?>
<graph string="Graph Analysis" <graph string="Graph Analysis"
type="bar" type="bar" stacked="True">
stacked="True"> {} </graph> {}
</graph>
""".format("".join(self._create_view_arch()))
}, {
'name': 'Search BI View',
'type': 'search',
'model': self.model_name,
'priority': 16,
'arch': """<?xml version="1.0"?>
<search string="Search BI View">
{}
</search>
""".format("".join(self._create_view_arch())) """.format("".join(self._create_view_arch()))
}] }]
@ -164,8 +167,10 @@ class BveView(models.Model):
'model': self.model_name, 'model': self.model_name,
'priority': 16, 'priority': 16,
'arch': """<?xml version="1.0"?> 'arch': """<?xml version="1.0"?>
<tree string="List Analysis" create="false"> {} </tree> <tree string="List Analysis" create="false">
""".format("".join(self._create_view_arch())) {}
</tree>
""".format("".join(self._create_tree_view_arch()))
}) })
# set the Tree view as the default one # set the Tree view as the default one
@ -188,10 +193,52 @@ class BveView(models.Model):
}) })
@api.multi @api.multi
def _create_bve_object(self): def _build_access_rules(self, model):
self.ensure_one() self.ensure_one()
def _get_fields_info(fields_data): def group_ids_with_access(model_name, access_mode):
self.env.cr.execute('''SELECT
g.id
FROM
ir_model_access a
JOIN ir_model m ON (a.model_id=m.id)
JOIN res_groups g ON (a.group_id=g.id)
LEFT JOIN ir_module_category c ON (c.id=g.category_id)
WHERE
m.model=%s AND
a.active IS True AND
a.perm_''' + access_mode, (model_name,))
return [x[0] for x in self.env.cr.fetchall()]
info = json.loads(self._get_format_data(self.data))
models = list(set([f['model'] for f in info]))
read_groups = set.intersection(*[set(
group_ids_with_access(model_name, 'read')
) for model_name in models])
# read access
for group in read_groups:
self.env['ir.model.access'].sudo().create({
'name': 'read access to ' + self.model_name,
'model_id': model.id,
'group_id': group,
'perm_read': True,
})
# read and write access
for group in self.group_ids:
self.env['ir.model.access'].sudo().create({
'name': 'read-write access to ' + self.model_name,
'model_id': model.id,
'group_id': group.id,
'perm_read': True,
'perm_write': True,
})
@api.model
def _create_sql_view(self):
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'])
@ -208,31 +255,43 @@ class BveView(models.Model):
fields_info.append(vals) fields_info.append(vals)
return fields_info return fields_info
def _build_query(): def get_join_nodes(info):
data = self.data
if not data:
raise UserError(_('No data to process.'))
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['table_alias'],
f['join'], f['join'],
f['select_field']) for f in info if f['join'] is not False] f['select_field']) for f in info if f['join'] is not False]
return join_nodes
def get_tables(info):
tables = set([(f['table'], f['table_alias']) for f in info])
return tables
def get_fields(info):
fields = [("{}.{}".format(f['table_alias'],
f['select_field']),
f['as_field']) for f in info if 'join_node' not in f]
return fields
def check_empty_data(data):
if not data:
raise UserError(_('No data to process.'))
check_empty_data(self.data)
formatted_data = json.loads(self._get_format_data(self.data))
info = get_fields_info(formatted_data)
fields = get_fields(info)
tables = get_tables(info)
join_nodes = get_join_nodes(info)
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)
# this line is only for robustness in case something goes wrong
self._cr.execute('DROP TABLE IF EXISTS "%s"' % table_name)
basic_fields = [ basic_fields = [
("t0.id", "id"), ("t0.id", "id")
("t0.write_uid", "write_uid"),
("t0.write_date", "write_date"),
("t0.create_uid", "create_uid"),
("t0.create_date", "create_date")
] ]
q = """CREATE or REPLACE VIEW %s as ( q = """CREATE or REPLACE VIEW %s as (
@ -249,6 +308,10 @@ class BveView(models.Model):
self.env.cr.execute(q) self.env.cr.execute(q)
@api.multi
def action_create(self):
self.ensure_one()
def _prepare_field(field_data): def _prepare_field(field_data):
if not field_data['custom']: if not field_data['custom']:
field = self.env['ir.model.fields'].browse(field_data['id']) field = self.env['ir.model.fields'].browse(field_data['id'])
@ -268,87 +331,69 @@ class BveView(models.Model):
vals.update({'ttype': 'float'}) vals.update({'ttype': 'float'})
if field.ttype == 'selection' and not field.selection: if field.ttype == 'selection' and not field.selection:
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._fields[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(): # create sql view
self._create_sql_view()
# create model and fields
data = json.loads(self._get_format_data(self.data)) data = json.loads(self._get_format_data(self.data))
return { model_vals = {
'name': self.name, 'name': self.name,
'model': self.model_name, 'model': self.model_name,
'state': 'manual',
'field_id': [ 'field_id': [
(0, 0, _prepare_field(field)) (0, 0, _prepare_field(field))
for field in data for field in data
if 'join_node' not in field] if 'join_node' not in field]
} }
model = self.env['ir.model'].sudo().create(model_vals)
def _build_object(): # give access rights
vals = _prepare_object() self._build_access_rules(model)
Model = self.env['ir.model']
res_id = Model.sudo().with_context(bve=True).create(vals)
return res_id
def group_ids_with_access(model_name, access_mode): # create tree, graph and pivot views
self.env.cr.execute('''SELECT self._create_bve_view()
g.id
FROM
ir_model_access a
JOIN ir_model m ON (a.model_id=m.id)
JOIN res_groups g ON (a.group_id=g.id)
LEFT JOIN ir_module_category c ON (c.id=g.category_id)
WHERE
m.model=%s AND
a.active IS True AND
a.perm_''' + access_mode, (model_name,))
return [x[0] for x in self.env.cr.fetchall()]
def _build_access_rules(obj):
info = json.loads(self._get_format_data(self.data))
models = list(set([f['model'] for f in info]))
read_groups = set.intersection(*[set(
group_ids_with_access(model, 'read')) for model in models])
# read access
for group in read_groups:
self.env['ir.model.access'].sudo().create({
'name': 'read access to ' + self.model_name,
'model_id': obj.id,
'group_id': group,
'perm_read': True,
})
# read and write access
for group in self.group_ids:
self.env['ir.model.access'].sudo().create({
'name': 'read-write access to ' + self.model_name,
'model_id': obj.id,
'group_id': group.id,
'perm_read': True,
'perm_write': True,
})
self.model_name = 'x_bve.' + ''.join(
[x for x in self.name.lower()
if x.isalnum()]).replace('_', '.').replace(' ', '.')
_build_query()
obj = _build_object()
_build_access_rules(obj)
@api.multi @api.multi
def open_view(self): def open_view(self):
self.ensure_one() self.ensure_one()
return { [action] = self.action_id.read()
'name': _('BI View'), action['display_name'] = _('BI View')
'type': 'ir.actions.act_window', return action
'res_model': self.model_name,
'view_type': 'form',
'view_mode': 'tree,graph,pivot',
}
@api.multi @api.multi
def copy(self, default=None): def copy(self, default=None):
self.ensure_one() self.ensure_one()
default = dict(default or {}, name=_("%s (copy)") % self.name) default = dict(default or {}, name=_("%s (copy)") % self.name)
return super(BveView, self).copy(default=default) return super(BveView, self).copy(default=default)
@api.multi
def action_reset(self):
self.ensure_one()
if self.action_id:
if self.action_id.view_id:
self.action_id.view_id.sudo().unlink()
self.action_id.sudo().unlink()
models = self.env['ir.model'].sudo().search(
[('model', '=', self.model_name)])
for model in models:
model.sudo().unlink()
table_name = self.model_name.replace('.', '_')
tools.drop_view_if_exists(self.env.cr, table_name)
self.state = 'draft'
@api.multi
def unlink(self):
for view in self:
if view.state == 'created':
raise UserError(
_('You cannot delete a created view! '
'Reset the view to draft first.'))
return super(BveView, self).unlink()

View File

@ -2,8 +2,7 @@
# Copyright 2015-2017 Onestein (<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 api, models from odoo import api, models
from openerp.modules.registry import RegistryManager
NO_BI_MODELS = [ NO_BI_MODELS = [
'temp.range', 'temp.range',
@ -42,20 +41,17 @@ def dict_for_field(field):
} }
def dict_for_model(model):
return {
'id': model.id,
'name': model.name,
'model': model.model
}
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):
name = ir_model_field_obj.name
model = ir_model_field_obj.model_id
model_name = model.model
Model = self.env[model_name]
if name in Model._columns:
f = Model._columns[name]
return f._classic_write
return False
@api.model @api.model
def _filter_bi_models(self, model): def _filter_bi_models(self, model):
@ -96,92 +92,73 @@ class IrModel(models.Model):
model['model'], 'read', False) model['model'], 'read', False)
return False return False
@api.model
def sort_filter_models(self, models_list):
res = sorted(
filter(self._filter_bi_models, models_list),
key=lambda x: x['name'])
return res
@api.model
def _search_fields(self, domain):
Fields = self.env['ir.model.fields']
fields = Fields.sudo().search(domain)
return fields
@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 = 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) def get_model_list(model_ids):
return related_fields model_list = []
@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()), domain = [('model_id', 'in', model_ids.values()),
('store', '=', True),
('ttype', 'in', ['many2one'])] ('ttype', 'in', ['many2one'])]
for field in filter( filtered_fields = self._search_fields(domain)
self._filter_bi_fields,
Fields.sudo().search(domain)):
for model in model_ids.items(): for model in model_ids.items():
for field in filtered_fields:
if model[1] == field.model_id.id: if model[1] == field.model_id.id:
rfields.append( model_list.append(
dict(dict_for_field(field), dict(dict_for_field(field),
join_node=-1, join_node=-1,
table_alias=model[0]) table_alias=model[0])
) )
return rfields return model_list
def _get_left_fields(model_ids, model_names): def get_relation_list(model_ids, model_names):
Fields = self.env['ir.model.fields'] relation_list = []
lfields = []
domain = [('relation', 'in', model_names.values()), domain = [('relation', 'in', model_names.values()),
('store', '=', True),
('ttype', 'in', ['many2one'])] ('ttype', 'in', ['many2one'])]
for field in filter( filtered_fields = self._search_fields(domain)
self._filter_bi_fields,
Fields.sudo().search(domain)):
for model in model_ids.items(): for model in model_ids.items():
for field in filtered_fields:
if model_names[model[1]] == field['relation']: if model_names[model[1]] == field['relation']:
lfields.append( relation_list.append(
dict(dict_for_field(field), dict(dict_for_field(field),
join_node=model[0], join_node=model[0],
table_alias=-1) 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 return relation_list
def _get_model_list(model_ids, rfields): models = self.sudo().browse(model_ids.values())
model_list = [] model_names = {}
for model in model_ids.items(): for model in models:
for field in rfields: model_names.update({model.id: model.model})
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) model_list = get_model_list(model_ids)
rfields = _get_right_fields(model_ids, model_names) relation_list = get_relation_list(model_ids, model_names)
relation_list = _get_relation_list(model_ids, model_names, lfields) return relation_list + model_list
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 the already selected models.
""" """
def _get_field(fields, orig, target): def _get_field(fields, orig, target):
field_list = [] field_list = []
for f in fields: for f in fields:
@ -205,35 +182,19 @@ class IrModel(models.Model):
domain = ['|', domain = ['|',
('id', 'in', list_id), ('id', 'in', list_id),
('model', 'in', list_model)] ('model', 'in', list_model)]
models = self.env['ir.model'].sudo().search(domain) for model in self.sudo().search(domain):
for model in models: models_list.append(dict_for_model(model))
models_list.append({ return self.sort_filter_models(models_list)
'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_list = []
return sorted(filter( for model in self.search([('transient', '=', False)]):
self._filter_bi_models, models_list.append(dict_for_model(model))
[dict_for_model(model) return self.sort_filter_models(models_list)
for model in self.search(models_domain)]),
key=lambda x: x['name'])
@api.model @api.model
def get_join_nodes(self, field_data, new_field): def get_join_nodes(self, field_data, new_field):
@ -252,15 +213,12 @@ class IrModel(models.Model):
for alias, model_id in model_ids.items(): for alias, model_id in model_ids.items():
if model_id == new_field['model_id']: if model_id == new_field['model_id']:
join_nodes.append({'table_alias': alias}) join_nodes.append({'table_alias': alias})
for dict_field in self.get_related_fields(model_ids): for field in self.get_related_fields(model_ids):
condition = [ c = [field['join_node'] == -1, field['table_alias'] == -1]
dict_field['join_node'] == -1, a = (new_field['model'] == field['relation'])
dict_field['table_alias'] == -1 b = (new_field['model_id'] == field['model_id'])
] if (a and c[0]) or (b and c[1]):
relation = (new_field['model'] == dict_field['relation']) join_nodes.append(field)
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 return join_nodes
model_ids = _get_model_ids(field_data) model_ids = _get_model_ids(field_data)
@ -273,18 +231,15 @@ class IrModel(models.Model):
@api.model @api.model
def get_fields(self, model_id): def get_fields(self, model_id):
bi_field_domain = [ domain = [
('model_id', '=', model_id), ('model_id', '=', model_id),
('store', '=', True),
('name', 'not in', NO_BI_FIELDS), ('name', 'not in', NO_BI_FIELDS),
('ttype', 'not in', NO_BI_TTYPES) ('ttype', 'not in', NO_BI_TTYPES)
] ]
Fields = self.env['ir.model.fields']
fields = filter(
self._filter_bi_fields,
Fields.sudo().search(bi_field_domain)
)
fields_dict = [] fields_dict = []
for field in fields: filtered_fields = self._search_fields(domain)
for field in filtered_fields:
fields_dict.append( fields_dict.append(
{'id': field.id, {'id': field.id,
'model_id': model_id, 'model_id': model_id,
@ -302,26 +257,3 @@ class IrModel(models.Model):
reverse=True reverse=True
) )
return sorted_fields return sorted_fields
@api.model
def create(self, vals):
if self._context and self._context.get('bve'):
vals['state'] = 'base'
res = super(IrModel, self).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 = """ + 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))
# signal that registry has changed
RegistryManager.signal_registry_change(self.env.cr.dbname)
return res

View File

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Onestein (<http://www.onestein.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, models
from odoo.exceptions import Warning as UserError
from odoo.tools.translate import _
class Base(models.AbstractModel):
_inherit = 'base'
@api.model
def _bi_view(self):
return self._name[0:6] == 'x_bve.'
@api.model
def _auto_end(self):
if not self._bi_view():
super(Base, self)._auto_end()
@api.model
def _setup_complete(self):
if not self._bi_view():
super(Base, self)._setup_complete()
else:
self.pool.models[self._name]._log_access = False
@api.model
def _read_group_process_groupby(self, gb, query):
if not self._bi_view():
return super(Base, self)._read_group_process_groupby(gb, query)
split = gb.split(':')
if split[0] not in self._fields:
raise UserError(
_('No data to be displayed.'))
return super(Base, self)._read_group_process_groupby(gb, query)
@api.model
def _add_magic_fields(self):
if self._bi_view():
self._log_access = False
return super(Base, self)._add_magic_fields()
@api.model_cr
def _table_exist(self):
if not self._bi_view():
return super(Base, self)._table_exist()
return 1
# @api.model_cr
# def _create_table(self):
# if not self._bi_view():
# return super(Base, self)._create_table()
# return 1

View File

@ -1,3 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_bve_view_everyone,bve.view,bi_view_editor.model_bve_view,,1,1,1,1 access_bve_view_everyone,bve.view,bi_view_editor.model_bve_view,,1,1,1,1
access_bve_view_technical_settings,bve.view,bi_view_editor.model_bve_view,base.group_no_one,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_bve_view_everyone bve.view bi_view_editor.model_bve_view 1 1 1 1
access_bve_view_technical_settings bve.view bi_view_editor.model_bve_view base.group_no_one 1 1 1 1

View File

@ -1,15 +1,67 @@
openerp.bi_view_editor = function (instance, local) { odoo.define('bi_view_editor', function (require) {
"use strict";
instance.bi_view_editor.BVEEditor = instance.web.form.AbstractField.extend({ var Core = require("web.core");
var FormCommon = require('web.form_common');
var Model = require('web.Model');
var Data = require('web.data');
var Widget = require('web.Widget');
var Dialog = require("web.Dialog");
var _t = Core._t;
var JoinNodePopup = Widget.extend({
template: "JoinNodePopup",
start: function() {
var self = this;
},
display_popup: function(choices, model_data, callback, callback_data) {
var self = this;
this.renderElement();
var joinnodes = this.$el.find('#join-nodes');
joinnodes.empty();
for (var i=0; i<choices.length; i++) {
var description = "";
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;
} else {
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>")
.data('idx', i)
.wrap("<p></p>")
.parent());
}
var dialog = new Dialog(this, {
dialogClass: 'oe_act_window',
title: "Choose Join Node",
$content: this.$el
}).open();
joinnodes.find('a').click(function() {
callback(callback_data, choices[$(this).data('idx')]);
dialog.close();
});
this.start();
}
});
var BiViewEditor = FormCommon.AbstractField.extend({
template: "BVEEditor", template: "BVEEditor",
activeModelMenus: [], activeModelMenus: [],
currentFilter: "", currentFilter: "",
init: function(parent, action) { init: function() {
this._super.apply(this, arguments); this._super.apply(this, arguments);
}, },
start: function() { start: function() {
this._super(); this._super();
this.on("change:effective_readonly", this, function() { this.view.on("change:effective_readonly", this, function() {
this.display_field(); this.display_field();
this.render_value(); this.render_value();
}); });
@ -80,20 +132,20 @@ openerp.bi_view_editor = function (instance, local) {
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 Model("ir.model");
if (this.$el.find(".field-list tbody tr").length > 0) { if (this.$el.find(".field-list tbody tr").length > 0) {
model.call("get_related_models", [this.get_model_ids()], { context: new instance.web.CompoundContext() }).then(function(result) { model.call("get_related_models", [this.get_model_ids()], { context: new Data.CompoundContext() }).then(function(result) {
self.show_classes(result); self.show_classes(result);
}); });
} else { } else {
model.call("get_models", { context: new instance.web.CompoundContext() }).then(function(result) { model.call("get_models", { context: new Data.CompoundContext() }).then(function(result) {
self.show_classes(result); self.show_classes(result);
}); });
} }
}, },
show_classes: function (result) { show_classes: function (result) {
var self = this; var self = this;
var model = new instance.web.Model("ir.model"); var model = new Model("ir.model");
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';
@ -112,7 +164,7 @@ openerp.bi_view_editor = function (instance, local) {
if(index !== -1) self.activeModelMenus.splice(index, 1); if(index !== -1) self.activeModelMenus.splice(index, 1);
} else { } else {
self.activeModelMenus.push(classel.data('model-data').id); 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) { model.call("get_fields", [classel.data('model-data').id], { context: new Data.CompoundContext() }).then(function(result) {
for (var i = 0; i < result.length; i++) { for (var i = 0; i < result.length; i++) {
classel.find("#bve-field-" + result[i].name).remove(); classel.find("#bve-field-" + result[i].name).remove();
self._render_field(self, i, result, classel, addField) self._render_field(self, i, result, classel, addField)
@ -138,7 +190,7 @@ openerp.bi_view_editor = function (instance, local) {
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")) { if(index !== -1 && !self.get("effective_readonly")) {
model.call("get_fields", [self.activeModelMenus[index]], { context: new instance.web.CompoundContext() }).then(renderFields); model.call("get_fields", [self.activeModelMenus[index]], { context: new Data.CompoundContext() }).then(renderFields);
} }
self.filter(); self.filter();
} }
@ -321,18 +373,18 @@ openerp.bi_view_editor = function (instance, local) {
}, },
add_field: function(field) { add_field: function(field) {
var data = field.data('field-data'); var data = field.data('field-data');
var model = new instance.web.Model("ir.model"); var model = new Model("ir.model");
var model_ids = this.get_model_ids(); var model_ids = this.get_model_ids();
var field_data = this.get_fields(); var field_data = this.get_fields();
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 Data.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);
} else if (result.length > 1) { } else if (result.length > 1) {
var pop = new local.JoinNodePopup(self); var pop = new JoinNodePopup(self);
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.
@ -363,49 +415,6 @@ openerp.bi_view_editor = function (instance, local) {
this.load_classes(); this.load_classes();
} }
}); });
instance.web.form.widgets.add('BVEEditor', 'instance.bi_view_editor.BVEEditor'); Core.form_widget_registry.add('BVEEditor', BiViewEditor);
local.JoinNodePopup = instance.web.Widget.extend({ });
template: "JoinNodePopup",
start: function() {
var self = this;
},
display_popup: function(choices, model_data, callback, callback_data) {
var self = this;
this.renderElement();
var joinnodes = this.$el.find('#join-nodes');
joinnodes.empty();
for (var i=0; i<choices.length; i++) {
var description = "";
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;
} else {
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>")
.data('idx', i)
.wrap("<p></p>")
.parent());
}
var dialog = new instance.web.Dialog(this, {
dialogClass: 'oe_act_window',
title: "Choose Join Node",
$content: this.$el
}).open();
joinnodes.find('a').click(function() {
callback(callback_data, choices[$(this).data('idx')]);
dialog.close();
});
this.start();
}
});
};

View File

@ -2,8 +2,8 @@
# Copyright 2017 Onestein (<http://www.onestein.eu>) # Copyright 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.tests.common import TransactionCase, at_install, post_install from odoo.tests.common import TransactionCase, at_install, post_install
from openerp.exceptions import Warning as UserError from odoo.exceptions import Warning as UserError
class TestBiViewEditor(TransactionCase): class TestBiViewEditor(TransactionCase):