diff --git a/bi_sql_editor/__manifest__.py b/bi_sql_editor/__manifest__.py
index 1ce605594..16b9dfa3e 100644
--- a/bi_sql_editor/__manifest__.py
+++ b/bi_sql_editor/__manifest__.py
@@ -3,27 +3,21 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
- 'name': 'BI SQL Editor',
- 'summary': 'BI Views builder, based on Materialized or Normal SQL Views',
- 'version': '12.0.1.2.0',
- 'license': 'AGPL-3',
- 'category': 'Reporting',
- 'author': 'GRAP,Odoo Community Association (OCA)',
- 'website': 'https://github.com/OCA/reporting-engine',
- 'depends': [
- 'base',
- 'sql_request_abstract',
+ "name": "BI SQL Editor",
+ "summary": "BI Views builder, based on Materialized or Normal SQL Views",
+ "version": "12.0.1.2.0",
+ "license": "AGPL-3",
+ "category": "Reporting",
+ "author": "GRAP,Odoo Community Association (OCA)",
+ "website": "https://github.com/OCA/reporting-engine",
+ "depends": ["base", "sql_request_abstract"],
+ "data": [
+ "security/ir.model.access.csv",
+ "views/view_bi_sql_view.xml",
+ "views/action.xml",
+ "views/menu.xml",
],
- 'data': [
- 'security/ir.model.access.csv',
- 'views/view_bi_sql_view.xml',
- 'views/action.xml',
- 'views/menu.xml',
- ],
- 'demo': [
- 'demo/res_groups_demo.xml',
- 'demo/bi_sql_view_demo.xml',
- ],
- 'installable': True,
- 'uninstall_hook': 'uninstall_hook'
+ "demo": ["demo/res_groups_demo.xml", "demo/bi_sql_view_demo.xml"],
+ "installable": True,
+ "uninstall_hook": "uninstall_hook",
}
diff --git a/bi_sql_editor/demo/bi_sql_view_demo.xml b/bi_sql_editor/demo/bi_sql_view_demo.xml
index 13c87d02c..6e7e97116 100644
--- a/bi_sql_editor/demo/bi_sql_view_demo.xml
+++ b/bi_sql_editor/demo/bi_sql_view_demo.xml
@@ -1,27 +1,28 @@
-
+
-
-
-
- Draft Incorrect SQL View
- incorrect_view
-
+ Draft Incorrect SQL View
+ incorrect_view
+
-
-
-
- Partners View
- partners_view
-
+
+ Partners View
+ partners_view
+
-
-
-
- Modules by Authors
- modules_view
-
-
+
+ Modules by Authors
+ modules_view
+
+
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/bi_sql_editor/demo/res_groups_demo.xml b/bi_sql_editor/demo/res_groups_demo.xml
index f9377b73b..454e815dc 100644
--- a/bi_sql_editor/demo/res_groups_demo.xml
+++ b/bi_sql_editor/demo/res_groups_demo.xml
@@ -1,15 +1,13 @@
-
+
-
-
diff --git a/bi_sql_editor/hooks.py b/bi_sql_editor/hooks.py
index ec2ecb6a8..25fe633ed 100644
--- a/bi_sql_editor/hooks.py
+++ b/bi_sql_editor/hooks.py
@@ -6,6 +6,6 @@ from odoo.api import Environment
def uninstall_hook(cr, registry):
env = Environment(cr, SUPERUSER_ID, {})
- recs = env['bi.sql.view'].search([])
+ recs = env["bi.sql.view"].search([])
for rec in recs:
rec.button_set_draft()
diff --git a/bi_sql_editor/models/bi_sql_view.py b/bi_sql_editor/models/bi_sql_view.py
index e7b6f9631..0e7f77d6d 100644
--- a/bi_sql_editor/models/bi_sql_view.py
+++ b/bi_sql_editor/models/bi_sql_view.py
@@ -4,11 +4,13 @@
import logging
from datetime import datetime
+
from psycopg2 import ProgrammingError
-from odoo import _, api, fields, models, SUPERUSER_ID
+from odoo import SUPERUSER_ID, _, api, fields, models
from odoo.exceptions import UserError
from odoo.tools import pycompat, safe_eval, sql
+
from odoo.addons.base.models.ir_model import IrModel
_logger = logging.getLogger(__name__)
@@ -16,20 +18,20 @@ _logger = logging.getLogger(__name__)
@api.model
def _instanciate(self, model_data):
- """ Return a class for the custom model given by
- parameters ``model_data``. """
+ """Return a class for the custom model given by
+ parameters ``model_data``."""
# This monkey patch is meant to avoid create/search tables for those
# materialized views. Doing "super" doesn't work.
class CustomModel(models.Model):
- _name = pycompat.to_native(model_data['model'])
- _description = model_data['name']
+ _name = pycompat.to_native(model_data["model"])
+ _description = model_data["name"]
_module = False
_custom = True
- _transient = bool(model_data['transient'])
- __doc__ = model_data['info']
+ _transient = bool(model_data["transient"])
+ __doc__ = model_data["info"]
# START OF patch
- if model_data['model'].startswith(BiSQLView._model_prefix):
+ if model_data["model"].startswith(BiSQLView._model_prefix):
CustomModel._auto = False
CustomModel._abstract = True
# END of patch
@@ -40,62 +42,73 @@ IrModel._instanciate = _instanciate
class BiSQLView(models.Model):
- _name = 'bi.sql.view'
- _order = 'sequence'
- _inherit = ['sql.request.mixin']
+ _name = "bi.sql.view"
+ _order = "sequence"
+ _inherit = ["sql.request.mixin"]
- _sql_prefix = 'x_bi_sql_view_'
+ _sql_prefix = "x_bi_sql_view_"
- _model_prefix = 'x_bi_sql_view.'
+ _model_prefix = "x_bi_sql_view."
- _sql_request_groups_relation = 'bi_sql_view_groups_rel'
+ _sql_request_groups_relation = "bi_sql_view_groups_rel"
- _sql_request_users_relation = 'bi_sql_view_users_rel'
+ _sql_request_users_relation = "bi_sql_view_users_rel"
_STATE_SQL_EDITOR = [
- ('model_valid', 'SQL View and Model Created'),
- ('ui_valid', 'Views, Action and Menu Created'),
+ ("model_valid", "SQL View and Model Created"),
+ ("ui_valid", "Views, Action and Menu Created"),
]
technical_name = fields.Char(
- string='Technical Name', required=True,
+ string="Technical Name",
+ required=True,
help="Suffix of the SQL view. SQL full name will be computed and"
" prefixed by 'x_bi_sql_view_'. Syntax should follow: "
"https://www.postgresql.org/"
- "docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS")
+ "docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS",
+ )
view_name = fields.Char(
- string='View Name', compute='_compute_view_name', readonly=True,
- store=True, help="Full name of the SQL view")
+ string="View Name",
+ compute="_compute_view_name",
+ readonly=True,
+ store=True,
+ help="Full name of the SQL view",
+ )
model_name = fields.Char(
- string='Model Name', compute='_compute_model_name', readonly=True,
- store=True, help="Full Qualified Name of the transient model that will"
- " be created.")
+ string="Model Name",
+ compute="_compute_model_name",
+ readonly=True,
+ store=True,
+ help="Full Qualified Name of the transient model that will" " be created.",
+ )
is_materialized = fields.Boolean(
- string='Is Materialized View', default=True, readonly=True,
- states={
- 'draft': [('readonly', False)],
- 'sql_valid': [('readonly', False)],
- })
+ string="Is Materialized View",
+ default=True,
+ readonly=True,
+ states={"draft": [("readonly", False)], "sql_valid": [("readonly", False)]},
+ )
- materialized_text = fields.Char(
- compute='_compute_materialized_text', store=True)
+ materialized_text = fields.Char(compute="_compute_materialized_text", store=True)
size = fields.Char(
- string='Database Size', readonly=True,
- help="Size of the materialized view and its indexes")
+ string="Database Size",
+ readonly=True,
+ help="Size of the materialized view and its indexes",
+ )
state = fields.Selection(selection_add=_STATE_SQL_EDITOR)
- view_order = fields.Char(string='View Order',
- required=True,
- readonly=False,
- states={'ui_valid': [('readonly', True)]},
- default="pivot,graph,tree",
- help='Comma-separated text. Possible values:'
- ' "graph", "pivot" or "tree"')
+ view_order = fields.Char(
+ string="View Order",
+ required=True,
+ readonly=False,
+ states={"ui_valid": [("readonly", True)]},
+ default="pivot,graph,tree",
+ help="Comma-separated text. Possible values:" ' "graph", "pivot" or "tree"',
+ )
query = fields.Text(
help="SQL Request that will be inserted as the view. Take care to :\n"
@@ -103,98 +116,112 @@ class BiSQLView(models.Model):
" SQL function (like EXTRACT, ...);\n"
" * Do not use 'SELECT *' or 'SELECT table.*';\n"
" * prefix the name of the selectable columns by 'x_';",
- default="SELECT\n"
- " my_field as x_my_field\n"
- "FROM my_table")
+ default="SELECT\n" " my_field as x_my_field\n" "FROM my_table",
+ )
domain_force = fields.Text(
- string='Extra Rule Definition', default="[]", readonly=True,
+ string="Extra Rule Definition",
+ default="[]",
+ readonly=True,
help="Define here access restriction to data.\n"
" Take care to use field name prefixed by 'x_'."
" A global 'ir.rule' will be created."
" A typical Multi Company rule is for exemple \n"
" ['|', ('x_company_id','child_of', [user.company_id.id]),"
"('x_company_id','=',False)].",
- states={
- 'draft': [('readonly', False)],
- 'sql_valid': [('readonly', False)],
- })
+ states={"draft": [("readonly", False)], "sql_valid": [("readonly", False)]},
+ )
computed_action_context = fields.Text(
- compute="_compute_computed_action_context",
- string="Computed Action Context")
+ compute="_compute_computed_action_context", string="Computed Action Context"
+ )
action_context = fields.Text(
- string="Action Context", default="{}", readonly=True,
+ string="Action Context",
+ default="{}",
+ readonly=True,
help="Define here a context that will be used"
" by default, when creating the action.",
states={
- 'draft': [('readonly', False)],
- 'sql_valid': [('readonly', False)],
- 'model_valid': [('readonly', False)],
- })
+ "draft": [("readonly", False)],
+ "sql_valid": [("readonly", False)],
+ "model_valid": [("readonly", False)],
+ },
+ )
has_group_changed = fields.Boolean(copy=False)
bi_sql_view_field_ids = fields.One2many(
- string='SQL Fields', comodel_name='bi.sql.view.field',
- inverse_name='bi_sql_view_id')
+ string="SQL Fields",
+ comodel_name="bi.sql.view.field",
+ inverse_name="bi_sql_view_id",
+ )
model_id = fields.Many2one(
- string='Odoo Model', comodel_name='ir.model', readonly=True)
+ string="Odoo Model", comodel_name="ir.model", readonly=True
+ )
tree_view_id = fields.Many2one(
- string='Odoo Tree View', comodel_name='ir.ui.view', readonly=True)
+ string="Odoo Tree View", comodel_name="ir.ui.view", readonly=True
+ )
graph_view_id = fields.Many2one(
- string='Odoo Graph View', comodel_name='ir.ui.view', readonly=True)
+ string="Odoo Graph View", comodel_name="ir.ui.view", readonly=True
+ )
pivot_view_id = fields.Many2one(
- string='Odoo Pivot View', comodel_name='ir.ui.view', readonly=True)
+ string="Odoo Pivot View", comodel_name="ir.ui.view", readonly=True
+ )
search_view_id = fields.Many2one(
- string='Odoo Search View', comodel_name='ir.ui.view', readonly=True)
+ string="Odoo Search View", comodel_name="ir.ui.view", readonly=True
+ )
action_id = fields.Many2one(
- string='Odoo Action', comodel_name='ir.actions.act_window',
- readonly=True)
+ string="Odoo Action", comodel_name="ir.actions.act_window", readonly=True
+ )
menu_id = fields.Many2one(
- string='Odoo Menu', comodel_name='ir.ui.menu', readonly=True)
+ string="Odoo Menu", comodel_name="ir.ui.menu", readonly=True
+ )
cron_id = fields.Many2one(
- string='Odoo Cron', comodel_name='ir.cron', readonly=True,
- help="Cron Task that will refresh the materialized view")
+ string="Odoo Cron",
+ comodel_name="ir.cron",
+ readonly=True,
+ help="Cron Task that will refresh the materialized view",
+ )
- rule_id = fields.Many2one(
- string='Odoo Rule', comodel_name='ir.rule', readonly=True)
+ rule_id = fields.Many2one(string="Odoo Rule", comodel_name="ir.rule", readonly=True)
group_ids = fields.Many2many(
- comodel_name='res.groups', readonly=True, states={
- 'draft': [('readonly', False)],
- 'sql_valid': [('readonly', False)],
- })
+ comodel_name="res.groups",
+ readonly=True,
+ states={"draft": [("readonly", False)], "sql_valid": [("readonly", False)]},
+ )
- sequence = fields.Integer(string='sequence')
+ sequence = fields.Integer(string="sequence")
# Constrains Section
- @api.constrains('is_materialized')
+ @api.constrains("is_materialized")
@api.multi
def _check_index_materialized(self):
for rec in self.filtered(lambda x: not x.is_materialized):
if rec.bi_sql_view_field_ids.filtered(lambda x: x.is_index):
- raise UserError(_(
- 'You can not create indexes on non materialized views'))
+ raise UserError(
+ _("You can not create indexes on non materialized views")
+ )
- @api.constrains('view_order')
+ @api.constrains("view_order")
@api.multi
def _check_view_order(self):
for rec in self:
if rec.view_order:
- for vtype in rec.view_order.split(','):
- if vtype not in ('graph', 'pivot', 'tree'):
- raise UserError(_(
- 'Only graph, pivot or tree views are supported'))
+ for vtype in rec.view_order.split(","):
+ if vtype not in ("graph", "pivot", "tree"):
+ raise UserError(
+ _("Only graph, pivot or tree views are supported")
+ )
# Compute Section
@api.depends("bi_sql_view_field_ids.graph_type")
@@ -207,60 +234,71 @@ class BiSQLView(models.Model):
"pivot_column_groupby": [],
}
for field in rec.bi_sql_view_field_ids.filtered(
- lambda x: x.graph_type == "measure"):
+ lambda x: x.graph_type == "measure"
+ ):
action["pivot_measures"].append(field.name)
for field in rec.bi_sql_view_field_ids.filtered(
- lambda x: x.graph_type == "row"):
+ lambda x: x.graph_type == "row"
+ ):
action["pivot_row_groupby"].append(field.name)
for field in rec.bi_sql_view_field_ids.filtered(
- lambda x: x.graph_type == "col"):
+ lambda x: x.graph_type == "col"
+ ):
action["pivot_column_groupby"].append(field.name)
rec.computed_action_context = str(action)
- @api.depends('is_materialized')
+ @api.depends("is_materialized")
@api.multi
def _compute_materialized_text(self):
for sql_view in self:
- sql_view.materialized_text =\
- sql_view.is_materialized and 'MATERIALIZED' or ''
+ sql_view.materialized_text = (
+ sql_view.is_materialized and "MATERIALIZED" or ""
+ )
- @api.depends('technical_name')
+ @api.depends("technical_name")
@api.multi
def _compute_view_name(self):
for sql_view in self:
- sql_view.view_name = '%s%s' % (
- sql_view._sql_prefix, sql_view.technical_name)
+ sql_view.view_name = "{}{}".format(
+ sql_view._sql_prefix,
+ sql_view.technical_name,
+ )
- @api.depends('technical_name')
+ @api.depends("technical_name")
@api.multi
def _compute_model_name(self):
for sql_view in self:
- sql_view.model_name = '%s%s' % (
- sql_view._model_prefix, sql_view.technical_name)
+ sql_view.model_name = "{}{}".format(
+ sql_view._model_prefix,
+ sql_view.technical_name,
+ )
- @api.onchange('group_ids')
+ @api.onchange("group_ids")
def onchange_group_ids(self):
- if self.state not in ('draft', 'sql_valid'):
+ if self.state not in ("draft", "sql_valid"):
self.has_group_changed = True
# Overload Section
@api.multi
def write(self, vals):
res = super(BiSQLView, self).write(vals)
- if vals.get('sequence', False):
+ if vals.get("sequence", False):
for rec in self.filtered(lambda x: x.menu_id):
rec.menu_id.sequence = rec.sequence
return res
@api.multi
def unlink(self):
- if any(view.state not in ('draft', 'sql_valid') for view in self):
+ if any(view.state not in ("draft", "sql_valid") for view in self):
raise UserError(
- _("You can only unlink draft views."
- "If you want to delete them, first set them to draft."))
+ _(
+ "You can only unlink draft views."
+ "If you want to delete them, first set them to draft."
+ )
+ )
self.cron_id.unlink()
return super(BiSQLView, self).unlink()
@@ -268,10 +306,12 @@ class BiSQLView(models.Model):
def copy(self, default=None):
self.ensure_one()
default = dict(default or {})
- default.update({
- 'name': _('%s (Copy)') % self.name,
- 'technical_name': '%s_copy' % self.technical_name,
- })
+ default.update(
+ {
+ "name": _("%s (Copy)") % self.name,
+ "technical_name": "%s_copy" % self.technical_name,
+ }
+ )
return super(BiSQLView, self).copy(default=default)
# Action Section
@@ -288,11 +328,12 @@ class BiSQLView(models.Model):
if sql_view.is_materialized:
if not sql_view.cron_id:
- sql_view.cron_id = self.env['ir.cron'].create(
- sql_view._prepare_cron()).id
+ sql_view.cron_id = (
+ self.env["ir.cron"].create(sql_view._prepare_cron()).id
+ )
else:
sql_view.cron_id.active = True
- sql_view.state = 'model_valid'
+ sql_view.state = "model_valid"
@api.multi
def button_set_draft(self):
@@ -304,7 +345,7 @@ class BiSQLView(models.Model):
sql_view.pivot_view_id.unlink()
sql_view.search_view_id.unlink()
- if sql_view.state in ('model_valid', 'ui_valid'):
+ if sql_view.state in ("model_valid", "ui_valid"):
# Drop SQL View (and indexes by cascade)
if sql_view.is_materialized:
sql_view._drop_view()
@@ -321,25 +362,27 @@ class BiSQLView(models.Model):
@api.multi
def button_create_ui(self):
- self.tree_view_id = self.env['ir.ui.view'].create(
- self._prepare_tree_view()).id
- self.graph_view_id = self.env['ir.ui.view'].create(
- self._prepare_graph_view()).id
- self.pivot_view_id = self.env['ir.ui.view'].create(
- self._prepare_pivot_view()).id
- self.search_view_id = self.env['ir.ui.view'].create(
- self._prepare_search_view()).id
- self.action_id = self.env['ir.actions.act_window'].create(
- self._prepare_action()).id
- self.menu_id = self.env['ir.ui.menu'].create(
- self._prepare_menu()).id
- self.write({'state': 'ui_valid'})
+ self.tree_view_id = self.env["ir.ui.view"].create(self._prepare_tree_view()).id
+ self.graph_view_id = (
+ self.env["ir.ui.view"].create(self._prepare_graph_view()).id
+ )
+ self.pivot_view_id = (
+ self.env["ir.ui.view"].create(self._prepare_pivot_view()).id
+ )
+ self.search_view_id = (
+ self.env["ir.ui.view"].create(self._prepare_search_view()).id
+ )
+ self.action_id = (
+ self.env["ir.actions.act_window"].create(self._prepare_action()).id
+ )
+ self.menu_id = self.env["ir.ui.menu"].create(self._prepare_menu()).id
+ self.write({"state": "ui_valid"})
@api.multi
def button_update_model_access(self):
self._drop_model_access()
self._create_model_access()
- self.write({'has_group_changed': False})
+ self.write({"has_group_changed": False})
@api.multi
def button_refresh_materialized_view(self):
@@ -348,10 +391,10 @@ class BiSQLView(models.Model):
@api.multi
def button_open_view(self):
return {
- 'type': 'ir.actions.act_window',
- 'res_model': self.model_id.model,
- 'search_view_id': self.search_view_id.id,
- 'view_mode': self.action_id.view_mode,
+ "type": "ir.actions.act_window",
+ "res_model": self.model_id.model,
+ "search_view_id": self.search_view_id.id,
+ "view_mode": self.action_id.view_mode,
}
# Prepare Function
@@ -360,13 +403,14 @@ class BiSQLView(models.Model):
self.ensure_one()
field_id = []
for field in self.bi_sql_view_field_ids.filtered(
- lambda x: x.field_description is not False):
+ lambda x: x.field_description is not False
+ ):
field_id.append([0, False, field._prepare_model_field()])
return {
- 'name': self.name,
- 'model': self.model_name,
- 'access_ids': [],
- 'field_id': field_id,
+ "name": self.name,
+ "model": self.model_name,
+ "access_ids": [],
+ "field_id": field_id,
}
@api.multi
@@ -374,118 +418,120 @@ class BiSQLView(models.Model):
self.ensure_one()
res = []
for group in self.group_ids:
- res.append({
- 'name': _('%s Access %s') % (
- self.model_name, group.full_name),
- 'model_id': self.model_id.id,
- 'group_id': group.id,
- 'perm_read': True,
- 'perm_create': False,
- 'perm_write': False,
- 'perm_unlink': False,
- })
+ res.append(
+ {
+ "name": _("%s Access %s") % (self.model_name, group.full_name),
+ "model_id": self.model_id.id,
+ "group_id": group.id,
+ "perm_read": True,
+ "perm_create": False,
+ "perm_write": False,
+ "perm_unlink": False,
+ }
+ )
return res
@api.multi
def _prepare_cron(self):
now = datetime.now()
return {
- 'name': _('Refresh Materialized View %s') % self.view_name,
- 'user_id': SUPERUSER_ID,
- 'model_id': self.env['ir.model'].search([
- ('model', '=', self._name)], limit=1).id,
- 'state': 'code',
- 'code': 'model._refresh_materialized_view_cron(%s)' % self.ids,
- 'numbercall': -1,
- 'interval_number': 1,
- 'interval_type': 'days',
- 'nextcall': datetime(now.year, now.month, now.day+1),
- 'active': True,
+ "name": _("Refresh Materialized View %s") % self.view_name,
+ "user_id": SUPERUSER_ID,
+ "model_id": self.env["ir.model"]
+ .search([("model", "=", self._name)], limit=1)
+ .id,
+ "state": "code",
+ "code": "model._refresh_materialized_view_cron(%s)" % self.ids,
+ "numbercall": -1,
+ "interval_number": 1,
+ "interval_type": "days",
+ "nextcall": datetime(now.year, now.month, now.day + 1),
+ "active": True,
}
@api.multi
def _prepare_rule(self):
self.ensure_one()
return {
- 'name': _('Access %s') % self.name,
- 'model_id': self.model_id.id,
- 'domain_force': self.domain_force,
- 'global': True,
+ "name": _("Access %s") % self.name,
+ "model_id": self.model_id.id,
+ "domain_force": self.domain_force,
+ "global": True,
}
@api.multi
def _prepare_tree_view(self):
self.ensure_one()
return {
- 'name': self.name,
- 'type': 'tree',
- 'model': self.model_id.model,
- 'arch':
- """"""
- """{}"""
- """""".format("".join(
- [x._prepare_tree_field()
- for x in self.bi_sql_view_field_ids]))
+ "name": self.name,
+ "type": "tree",
+ "model": self.model_id.model,
+ "arch": """"""
+ """{}"""
+ """""".format(
+ "".join([x._prepare_tree_field() for x in self.bi_sql_view_field_ids])
+ ),
}
@api.multi
def _prepare_graph_view(self):
self.ensure_one()
return {
- 'name': self.name,
- 'type': 'graph',
- 'model': self.model_id.model,
- 'arch':
- """"""
- """{}"""
- """""".format("".join(
- [x._prepare_graph_field()
- for x in self.bi_sql_view_field_ids]))
+ "name": self.name,
+ "type": "graph",
+ "model": self.model_id.model,
+ "arch": """"""
+ """{}"""
+ """""".format(
+ "".join([x._prepare_graph_field() for x in self.bi_sql_view_field_ids])
+ ),
}
@api.multi
def _prepare_pivot_view(self):
self.ensure_one()
return {
- 'name': self.name,
- 'type': 'pivot',
- 'model': self.model_id.model,
- 'arch':
- """"""
- """{}"""
- """""".format("".join(
- [x._prepare_pivot_field()
- for x in self.bi_sql_view_field_ids]))
+ "name": self.name,
+ "type": "pivot",
+ "model": self.model_id.model,
+ "arch": """"""
+ """{}"""
+ """""".format(
+ "".join([x._prepare_pivot_field() for x in self.bi_sql_view_field_ids])
+ ),
}
@api.multi
def _prepare_search_view(self):
self.ensure_one()
return {
- 'name': self.name,
- 'type': 'search',
- 'model': self.model_id.model,
- 'arch':
- """"""
- """{}"""
- """{}"""
- """""".format(
- "".join(
- [x._prepare_search_field()
- for x in self.bi_sql_view_field_ids]),
- "".join(
- [x._prepare_search_filter_field()
- for x in self.bi_sql_view_field_ids]))
+ "name": self.name,
+ "type": "search",
+ "model": self.model_id.model,
+ "arch": """"""
+ """{}"""
+ """{}"""
+ """""".format(
+ "".join(
+ [x._prepare_search_field() for x in self.bi_sql_view_field_ids]
+ ),
+ "".join(
+ [
+ x._prepare_search_filter_field()
+ for x in self.bi_sql_view_field_ids
+ ]
+ ),
+ ),
}
@api.multi
def _prepare_action(self):
self.ensure_one()
view_mode = self.view_order
- first_view = view_mode.split(',')[0]
- if first_view == 'tree':
+ first_view = view_mode.split(",")[0]
+ if first_view == "tree":
view_id = self.tree_view_id.id
- elif first_view == 'pivot':
+ elif first_view == "pivot":
view_id = self.pivot_view_id.id
else:
view_id = self.graph_view_id.id
@@ -493,13 +539,13 @@ class BiSQLView(models.Model):
for k, v in safe_eval(self.action_context).items():
action[k] = v
return {
- 'name': self._prepare_action_name(),
- 'res_model': self.model_id.model,
- 'type': 'ir.actions.act_window',
- 'view_mode': view_mode,
- 'view_id': view_id,
- 'search_view_id': self.search_view_id.id,
- 'context': str(action),
+ "name": self._prepare_action_name(),
+ "res_model": self.model_id.model,
+ "type": "ir.actions.act_window",
+ "view_mode": view_mode,
+ "view_id": view_id,
+ "search_view_id": self.search_view_id.id,
+ "context": str(action),
}
@api.multi
@@ -507,18 +553,19 @@ class BiSQLView(models.Model):
self.ensure_one()
if not self.is_materialized:
return self.name
- return "%s (%s)" % (
+ return "{} ({})".format(
self.name,
- datetime.utcnow().strftime(_("%m/%d/%Y %H:%M:%S UTC")))
+ datetime.utcnow().strftime(_("%m/%d/%Y %H:%M:%S UTC")),
+ )
@api.multi
def _prepare_menu(self):
self.ensure_one()
return {
- 'name': self.name,
- 'parent_id': self.env.ref('bi_sql_editor.menu_bi_sql_editor').id,
- 'action': 'ir.actions.act_window,%s' % self.action_id.id,
- 'sequence': self.sequence,
+ "name": self.name,
+ "parent_id": self.env.ref("bi_sql_editor.menu_bi_sql_editor").id,
+ "action": "ir.actions.act_window,%s" % self.action_id.id,
+ "sequence": self.sequence,
}
# Custom Section
@@ -530,8 +577,9 @@ class BiSQLView(models.Model):
def _drop_view(self):
for sql_view in self:
self._log_execute(
- "DROP %s VIEW IF EXISTS %s" % (
- sql_view.materialized_text, sql_view.view_name))
+ "DROP %s VIEW IF EXISTS %s"
+ % (sql_view.materialized_text, sql_view.view_name)
+ )
sql_view.size = False
@api.multi
@@ -542,29 +590,28 @@ class BiSQLView(models.Model):
self._log_execute(sql_view._prepare_request_for_execution())
sql_view._refresh_size()
except ProgrammingError as e:
- raise UserError(_(
- "SQL Error while creating %s VIEW %s :\n %s") % (
- sql_view.materialized_text, sql_view.view_name,
- e.message))
+ raise UserError(
+ _("SQL Error while creating %s VIEW %s :\n %s")
+ % (sql_view.materialized_text, sql_view.view_name, str(e))
+ )
@api.multi
def _create_index(self):
for sql_view in self:
for sql_field in sql_view.bi_sql_view_field_ids.filtered(
- lambda x: x.is_index is True):
+ lambda x: x.is_index is True
+ ):
self._log_execute(
- "CREATE INDEX %s ON %s (%s);" % (
- sql_field.index_name, sql_view.view_name,
- sql_field.name))
+ "CREATE INDEX %s ON %s (%s);"
+ % (sql_field.index_name, sql_view.view_name, sql_field.name)
+ )
@api.multi
def _create_model_and_fields(self):
for sql_view in self:
# Create model
- sql_view.model_id = self.env['ir.model'].create(
- self._prepare_model()).id
- sql_view.rule_id = self.env['ir.rule'].create(
- self._prepare_rule()).id
+ sql_view.model_id = self.env["ir.model"].create(self._prepare_model()).id
+ sql_view.rule_id = self.env["ir.rule"].create(self._prepare_rule()).id
# Drop table, created by the ORM
if sql.table_exists(self._cr, sql_view.view_name):
req = "DROP TABLE %s" % sql_view.view_name
@@ -574,13 +621,14 @@ class BiSQLView(models.Model):
def _create_model_access(self):
for sql_view in self:
for item in sql_view._prepare_model_access():
- self.env['ir.model.access'].create(item)
+ self.env["ir.model.access"].create(item)
@api.multi
def _drop_model_access(self):
for sql_view in self:
- self.env['ir.model.access'].search(
- [('model_id', '=', sql_view.model_name)]).unlink()
+ self.env["ir.model.access"].search(
+ [("model_id", "=", sql_view.model_name)]
+ ).unlink()
@api.multi
def _drop_model_and_fields(self):
@@ -593,7 +641,8 @@ class BiSQLView(models.Model):
@api.multi
def _hook_executed_request(self):
self.ensure_one()
- req = """
+ req = (
+ """
SELECT attnum,
attname AS column,
format_type(atttypid, atttypmod) AS type
@@ -601,19 +650,22 @@ class BiSQLView(models.Model):
WHERE attrelid = '%s'::regclass
AND NOT attisdropped
AND attnum > 0
- ORDER BY attnum;""" % self.view_name
+ ORDER BY attnum;"""
+ % self.view_name
+ )
self._log_execute(req)
return self.env.cr.fetchall()
@api.multi
def _prepare_request_check_execution(self):
self.ensure_one()
- return "CREATE VIEW %s AS (%s);" % (self.view_name, self.query)
+ return "CREATE VIEW {} AS ({});".format(self.view_name, self.query)
@api.multi
def _prepare_request_for_execution(self):
self.ensure_one()
- query = """
+ query = (
+ """
SELECT
CAST(row_number() OVER () as integer) AS id,
CAST(Null as timestamp without time zone) as create_date,
@@ -623,9 +675,14 @@ class BiSQLView(models.Model):
my_query.*
FROM
(%s) as my_query
- """ % self.query
- return "CREATE %s VIEW %s AS (%s);" % (
- self.materialized_text, self.view_name, query)
+ """
+ % self.query
+ )
+ return "CREATE {} VIEW {} AS ({});".format(
+ self.materialized_text,
+ self.view_name,
+ query,
+ )
@api.multi
def _check_execution(self):
@@ -635,54 +692,59 @@ class BiSQLView(models.Model):
After the execution, and before the rollback, an analysis of
the database structure is done, to know fields type."""
self.ensure_one()
- sql_view_field_obj = self.env['bi.sql.view.field']
+ sql_view_field_obj = self.env["bi.sql.view.field"]
columns = super(BiSQLView, self)._check_execution()
field_ids = []
for column in columns:
existing_field = self.bi_sql_view_field_ids.filtered(
- lambda x: x.name == column[1])
+ lambda x: x.name == column[1]
+ )
if existing_field:
# Update existing field
field_ids.append(existing_field.id)
- existing_field.write({
- 'sequence': column[0],
- 'sql_type': column[2],
- })
+ existing_field.write({"sequence": column[0], "sql_type": column[2]})
else:
# Create a new one if name is prefixed by x_
- if column[1][:2] == 'x_':
- field_ids.append(sql_view_field_obj.create({
- 'sequence': column[0],
- 'name': column[1],
- 'sql_type': column[2],
- 'bi_sql_view_id': self.id,
- }).id)
+ if column[1][:2] == "x_":
+ field_ids.append(
+ sql_view_field_obj.create(
+ {
+ "sequence": column[0],
+ "name": column[1],
+ "sql_type": column[2],
+ "bi_sql_view_id": self.id,
+ }
+ ).id
+ )
# Drop obsolete view field
- self.bi_sql_view_field_ids.filtered(
- lambda x: x.id not in field_ids).unlink()
+ self.bi_sql_view_field_ids.filtered(lambda x: x.id not in field_ids).unlink()
if not self.bi_sql_view_field_ids:
- raise UserError(_(
- "No Column was found.\n"
- "Columns name should be prefixed by 'x_'."))
+ raise UserError(
+ _("No Column was found.\n" "Columns name should be prefixed by 'x_'.")
+ )
return columns
@api.model
def _refresh_materialized_view_cron(self, view_ids):
- sql_views = self.search([
- ('is_materialized', '=', True),
- ('state', 'in', ['model_valid', 'ui_valid']),
- ('id', 'in', view_ids),
- ])
+ sql_views = self.search(
+ [
+ ("is_materialized", "=", True),
+ ("state", "in", ["model_valid", "ui_valid"]),
+ ("id", "in", view_ids),
+ ]
+ )
return sql_views._refresh_materialized_view()
@api.multi
def _refresh_materialized_view(self):
for sql_view in self.filtered(lambda x: x.is_materialized):
- req = "REFRESH %s VIEW %s" % (
- sql_view.materialized_text, sql_view.view_name)
+ req = "REFRESH {} VIEW {}".format(
+ sql_view.materialized_text,
+ sql_view.view_name,
+ )
self._log_execute(req)
sql_view._refresh_size()
if sql_view.action_id:
@@ -696,7 +758,8 @@ class BiSQLView(models.Model):
def _refresh_size(self):
for sql_view in self:
req = "SELECT pg_size_pretty(pg_total_relation_size('%s'));" % (
- sql_view.view_name)
+ sql_view.view_name
+ )
self._log_execute(req)
sql_view.size = self.env.cr.fetchone()[0]
@@ -704,4 +767,4 @@ class BiSQLView(models.Model):
def button_preview_sql_expression(self):
self.button_validate_sql_expression()
res = self._execute_sql_request()
- raise UserError('\n'.join(map(lambda x: str(x), res[:100])))
+ raise UserError("\n".join(map(lambda x: str(x), res[:100])))
diff --git a/bi_sql_editor/models/bi_sql_view_field.py b/bi_sql_editor/models/bi_sql_view_field.py
index c8c943008..be44ffd47 100644
--- a/bi_sql_editor/models/bi_sql_view_field.py
+++ b/bi_sql_editor/models/bi_sql_view_field.py
@@ -9,145 +9,165 @@ from odoo.exceptions import UserError
class BiSQLViewField(models.Model):
- _name = 'bi.sql.view.field'
- _description = 'Bi SQL View Field'
- _order = 'sequence'
+ _name = "bi.sql.view.field"
+ _description = "Bi SQL View Field"
+ _order = "sequence"
_TTYPE_SELECTION = [
- ('boolean', 'boolean'),
- ('char', 'char'),
- ('date', 'date'),
- ('datetime', 'datetime'),
- ('float', 'float'),
- ('integer', 'integer'),
- ('many2one', 'many2one'),
- ('selection', 'selection'),
+ ("boolean", "boolean"),
+ ("char", "char"),
+ ("date", "date"),
+ ("datetime", "datetime"),
+ ("float", "float"),
+ ("integer", "integer"),
+ ("many2one", "many2one"),
+ ("selection", "selection"),
]
_GRAPH_TYPE_SELECTION = [
- ('col', 'Column'),
- ('row', 'Row'),
- ('measure', 'Measure'),
+ ("col", "Column"),
+ ("row", "Row"),
+ ("measure", "Measure"),
]
_TREE_VISIBILITY_SELECTION = [
- ('unavailable', 'Unavailable'),
- ('hidden', 'Hidden'),
- ('available', 'Available'),
+ ("unavailable", "Unavailable"),
+ ("hidden", "Hidden"),
+ ("available", "Available"),
]
# Mapping to guess Odoo field type, from SQL column type
_SQL_MAPPING = {
- 'boolean': 'boolean',
- 'bigint': 'integer',
- 'integer': 'integer',
- 'double precision': 'float',
- 'numeric': 'float',
- 'text': 'char',
- 'character varying': 'char',
- 'date': 'date',
- 'timestamp without time zone': 'datetime',
+ "boolean": "boolean",
+ "bigint": "integer",
+ "integer": "integer",
+ "double precision": "float",
+ "numeric": "float",
+ "text": "char",
+ "character varying": "char",
+ "date": "date",
+ "timestamp without time zone": "datetime",
}
- name = fields.Char(string='Name', required=True, readonly=True)
+ name = fields.Char(string="Name", required=True, readonly=True)
sql_type = fields.Char(
- string='SQL Type', required=True, readonly=True,
- help="SQL Type in the database")
+ string="SQL Type", required=True, readonly=True, help="SQL Type in the database"
+ )
- sequence = fields.Integer(string='sequence', required=True, readonly=True)
+ sequence = fields.Integer(string="sequence", required=True, readonly=True)
bi_sql_view_id = fields.Many2one(
- string='SQL View', comodel_name='bi.sql.view', ondelete='cascade')
+ string="SQL View", comodel_name="bi.sql.view", ondelete="cascade"
+ )
is_index = fields.Boolean(
- string='Is Index', help="Check this box if you want to create"
+ string="Is Index",
+ help="Check this box if you want to create"
" an index on that field. This is recommended for searchable and"
- " groupable fields, to reduce duration")
+ " groupable fields, to reduce duration",
+ )
is_group_by = fields.Boolean(
- string='Is Group by', help="Check this box if you want to create"
- " a 'group by' option in the search view")
+ string="Is Group by",
+ help="Check this box if you want to create"
+ " a 'group by' option in the search view",
+ )
- index_name = fields.Char(
- string='Index Name', compute='_compute_index_name')
+ index_name = fields.Char(string="Index Name", compute="_compute_index_name")
- graph_type = fields.Selection(
- string='Graph Type', selection=_GRAPH_TYPE_SELECTION)
+ graph_type = fields.Selection(string="Graph Type", selection=_GRAPH_TYPE_SELECTION)
tree_visibility = fields.Selection(
- string='Tree Visibility', selection=_TREE_VISIBILITY_SELECTION,
- default='available', required=True)
+ string="Tree Visibility",
+ selection=_TREE_VISIBILITY_SELECTION,
+ default="available",
+ required=True,
+ )
field_description = fields.Char(
- string='Field Description', help="This will be used as the name"
- " of the Odoo field, displayed for users")
+ string="Field Description",
+ help="This will be used as the name" " of the Odoo field, displayed for users",
+ )
ttype = fields.Selection(
- string='Field Type', selection=_TTYPE_SELECTION, help="Type of the"
+ string="Field Type",
+ selection=_TTYPE_SELECTION,
+ help="Type of the"
" Odoo field that will be created. Keep empty if you don't want to"
" create a new field. If empty, this field will not be displayed"
- " neither available for search or group by function")
+ " neither available for search or group by function",
+ )
selection = fields.Text(
- string='Selection Options', default='[]',
+ string="Selection Options",
+ default="[]",
help="For 'Selection' Odoo field.\n"
" List of options, specified as a Python expression defining a list of"
" (key, label) pairs. For example:"
- " [('blue','Blue'), ('yellow','Yellow')]")
+ " [('blue','Blue'), ('yellow','Yellow')]",
+ )
many2one_model_id = fields.Many2one(
- comodel_name='ir.model', string='Model',
- help="For 'Many2one' Odoo field.\n"
- " Comodel of the field.")
+ comodel_name="ir.model",
+ string="Model",
+ help="For 'Many2one' Odoo field.\n" " Comodel of the field.",
+ )
# Constrains Section
- @api.constrains('is_index')
+ @api.constrains("is_index")
@api.multi
def _check_index_materialized(self):
for rec in self.filtered(lambda x: x.is_index):
if not rec.bi_sql_view_id.is_materialized:
- raise UserError(_(
- 'You can not create indexes on non materialized views'))
+ raise UserError(
+ _("You can not create indexes on non materialized views")
+ )
# Compute Section
@api.multi
def _compute_index_name(self):
for sql_field in self:
- sql_field.index_name = '%s_%s' % (
- sql_field.bi_sql_view_id.view_name, sql_field.name)
+ sql_field.index_name = "{}_{}".format(
+ sql_field.bi_sql_view_id.view_name,
+ sql_field.name,
+ )
# Overload Section
@api.model
def create(self, vals):
- field_without_prefix = vals['name'][2:]
+ field_without_prefix = vals["name"][2:]
# guess field description
field_description = re.sub(
- r'\w+', lambda m: m.group(0).capitalize(),
- field_without_prefix.replace('_id', '').replace('_', ' '))
+ r"\w+",
+ lambda m: m.group(0).capitalize(),
+ field_without_prefix.replace("_id", "").replace("_", " "),
+ )
# Guess ttype
# Don't execute as simple .get() in the dict to manage
# correctly the type 'character varying(x)'
ttype = False
for k, v in self._SQL_MAPPING.items():
- if k in vals['sql_type']:
+ if k in vals["sql_type"]:
ttype = v
# Guess many2one_model_id
many2one_model_id = False
- if vals['sql_type'] == 'integer' and(
- vals['name'][-3:] == '_id'):
- ttype = 'many2one'
- model_name = self._model_mapping().get(field_without_prefix, '')
- many2one_model_id = self.env['ir.model'].search(
- [('model', '=', model_name)]).id
+ if vals["sql_type"] == "integer" and (vals["name"][-3:] == "_id"):
+ ttype = "many2one"
+ model_name = self._model_mapping().get(field_without_prefix, "")
+ many2one_model_id = (
+ self.env["ir.model"].search([("model", "=", model_name)]).id
+ )
- vals.update({
- 'ttype': ttype,
- 'field_description': field_description,
- 'many2one_model_id': many2one_model_id,
- })
+ vals.update(
+ {
+ "ttype": ttype,
+ "field_description": field_description,
+ "many2one_model_id": many2one_model_id,
+ }
+ )
return super(BiSQLViewField, self).create(vals)
# Custom Section
@@ -157,8 +177,9 @@ class BiSQLViewField(models.Model):
field name. Sample :
{'account_id': 'account.account'; 'product_id': 'product.product'}
"""
- relation_fields = self.env['ir.model.fields'].search([
- ('ttype', '=', 'many2one')])
+ relation_fields = self.env["ir.model.fields"].search(
+ [("ttype", "=", "many2one")]
+ )
res = {}
keys_to_pop = []
for field in relation_fields:
@@ -177,49 +198,49 @@ class BiSQLViewField(models.Model):
def _prepare_model_field(self):
self.ensure_one()
return {
- 'name': self.name,
- 'field_description': self.field_description,
- 'model_id': self.bi_sql_view_id.model_id.id,
- 'ttype': self.ttype,
- 'selection': self.ttype == 'selection' and self.selection or False,
- 'relation': self.ttype == 'many2one' and
- self.many2one_model_id.model or False,
+ "name": self.name,
+ "field_description": self.field_description,
+ "model_id": self.bi_sql_view_id.model_id.id,
+ "ttype": self.ttype,
+ "selection": self.ttype == "selection" and self.selection or False,
+ "relation": self.ttype == "many2one"
+ and self.many2one_model_id.model
+ or False,
}
@api.multi
def _prepare_tree_field(self):
self.ensure_one()
- res = ''
- if self.field_description and self.tree_visibility != 'unavailable':
+ res = ""
+ if self.field_description and self.tree_visibility != "unavailable":
res = """""".format(
- self.name,
- self.tree_visibility == 'hidden' and 'invisible="1"' or '')
+ self.name, self.tree_visibility == "hidden" and 'invisible="1"' or ""
+ )
return res
@api.multi
def _prepare_graph_field(self):
self.ensure_one()
- res = ''
+ res = ""
if self.graph_type and self.field_description:
res = """\n""".format(
- self.name, self.graph_type)
+ self.name, self.graph_type
+ )
return res
@api.multi
def _prepare_pivot_field(self):
self.ensure_one()
- res = ''
+ res = ""
if self.field_description:
- graph_type_text =\
- self.graph_type and "type=\"%s\"" % (self.graph_type) or ""
- res = """\n""".format(
- self.name, graph_type_text)
+ graph_type_text = self.graph_type and 'type="%s"' % (self.graph_type) or ""
+ res = """\n""".format(self.name, graph_type_text)
return res
@api.multi
def _prepare_search_field(self):
self.ensure_one()
- res = ''
+ res = ""
if self.field_description:
res = """\n""".format(self.name)
return res
@@ -227,10 +248,12 @@ class BiSQLViewField(models.Model):
@api.multi
def _prepare_search_filter_field(self):
self.ensure_one()
- res = ''
+ res = ""
if self.field_description and self.is_group_by:
res = """\n""" % (
- self.name, self.field_description, self.name
- )
+ self.name,
+ self.field_description,
+ self.name,
+ )
return res
diff --git a/bi_sql_editor/tests/test_bi_sql_view.py b/bi_sql_editor/tests/test_bi_sql_view.py
index bfc4c2b7a..806b090e6 100644
--- a/bi_sql_editor/tests/test_bi_sql_view.py
+++ b/bi_sql_editor/tests/test_bi_sql_view.py
@@ -1,91 +1,94 @@
# Copyright 2017 Onestein ()
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-from odoo.tests.common import SingleTransactionCase, at_install, post_install
from odoo.exceptions import AccessError, UserError
+from odoo.tests.common import SingleTransactionCase, at_install, post_install
@at_install(False)
@post_install(True)
class TestBiSqlViewEditor(SingleTransactionCase):
-
@classmethod
def setUpClass(cls):
super(TestBiSqlViewEditor, cls).setUpClass()
- cls.res_partner = cls.env['res.partner']
- cls.res_users = cls.env['res.users']
- cls.bi_sql_view = cls.env['bi.sql.view']
+ cls.res_partner = cls.env["res.partner"]
+ cls.res_users = cls.env["res.users"]
+ cls.bi_sql_view = cls.env["bi.sql.view"]
cls.group_bi_user = cls.env.ref(
- 'sql_request_abstract.group_sql_request_manager')
- cls.group_user = cls.env.ref(
- 'base.group_user')
- cls.view = cls.bi_sql_view.create({
- 'name': 'Partners View 2',
- 'is_materialized': True,
- 'technical_name': 'partners_view_2',
- 'query': "SELECT name as x_name, street as x_street,"
- "company_id as x_company_id FROM res_partner "
- "ORDER BY name"
- })
- cls.company = cls.env.ref('base.main_company')
+ "sql_request_abstract.group_sql_request_manager"
+ )
+ cls.group_user = cls.env.ref("base.group_user")
+ cls.view = cls.bi_sql_view.create(
+ {
+ "name": "Partners View 2",
+ "is_materialized": True,
+ "technical_name": "partners_view_2",
+ "query": "SELECT name as x_name, street as x_street,"
+ "company_id as x_company_id FROM res_partner "
+ "ORDER BY name",
+ }
+ )
+ cls.company = cls.env.ref("base.main_company")
# Create bi user
- cls.bi_user = cls._create_user('bi_user', cls.group_bi_user,
- cls.company)
- cls.no_bi_user = cls._create_user('no_bi_user', cls.group_user,
- cls.company)
+ cls.bi_user = cls._create_user("bi_user", cls.group_bi_user, cls.company)
+ cls.no_bi_user = cls._create_user("no_bi_user", cls.group_user, cls.company)
@classmethod
def _create_user(cls, login, groups, company):
"""Create a user."""
- user = cls.res_users.create({
- 'name': login,
- 'login': login,
- 'password': 'demo',
- 'email': 'example@yourcompany.com',
- 'company_id': company.id,
- 'groups_id': [(6, 0, groups.ids)]
- })
+ user = cls.res_users.create(
+ {
+ "name": login,
+ "login": login,
+ "password": "demo",
+ "email": "example@yourcompany.com",
+ "company_id": company.id,
+ "groups_id": [(6, 0, groups.ids)],
+ }
+ )
return user
def test_process_view(self):
view = self.view
- self.assertEqual(view.state, 'draft', 'state not draft')
+ self.assertEqual(view.state, "draft", "state not draft")
view.button_validate_sql_expression()
- self.assertEqual(view.state, 'sql_valid', 'state not sql_valid')
+ self.assertEqual(view.state, "sql_valid", "state not sql_valid")
view.button_create_sql_view_and_model()
- self.assertEqual(view.state, 'model_valid', 'state not model_valid')
+ self.assertEqual(view.state, "model_valid", "state not model_valid")
view.button_create_ui()
- self.assertEqual(view.state, 'ui_valid', 'state not ui_valid')
+ self.assertEqual(view.state, "ui_valid", "state not ui_valid")
view.button_update_model_access()
- self.assertEqual(view.has_group_changed, False,
- 'has_group_changed not False')
+ self.assertEqual(view.has_group_changed, False, "has_group_changed not False")
cron_res = view.cron_id.method_direct_trigger()
- self.assertEqual(cron_res, True, 'something went wrong with the cron')
+ self.assertEqual(cron_res, True, "something went wrong with the cron")
def test_copy(self):
copy_view = self.view.copy()
- self.assertEqual(
- copy_view.name, 'Partners View 2 (Copy)', 'Wrong name')
+ self.assertEqual(copy_view.name, "Partners View 2 (Copy)", "Wrong name")
def test_security(self):
with self.assertRaises(AccessError):
self.bi_sql_view.sudo(self.no_bi_user.id).search(
- [('name', '=', 'Partners View 2')])
+ [("name", "=", "Partners View 2")]
+ )
bi = self.bi_sql_view.sudo(self.bi_user.id).search(
- [('name', '=', 'Partners View 2')])
- self.assertEqual(len(bi), 1, 'Bi user should not have access to '
- 'bi %s' % self.view.name)
+ [("name", "=", "Partners View 2")]
+ )
+ self.assertEqual(
+ len(bi), 1, "Bi user should not have access to " "bi %s" % self.view.name
+ )
def test_unlink(self):
- self.assertEqual(self.view.state, 'ui_valid', 'state not ui_valid')
+ self.assertEqual(self.view.state, "ui_valid", "state not ui_valid")
with self.assertRaises(UserError):
self.view.unlink()
self.view.button_set_draft()
self.assertNotEqual(
- self.view.cron_id, False, 'Set to draft materialized view should'
- ' not unlink cron'
+ self.view.cron_id,
+ False,
+ "Set to draft materialized view should" " not unlink cron",
)
self.view.unlink()
- res = self.bi_sql_view.search([('name', '=', 'Partners View 2')])
- self.assertEqual(len(res), 0, 'View not deleted')
+ res = self.bi_sql_view.search([("name", "=", "Partners View 2")])
+ self.assertEqual(len(res), 0, "View not deleted")
diff --git a/bi_sql_editor/views/action.xml b/bi_sql_editor/views/action.xml
index b984cbff2..2e7e9ce2e 100644
--- a/bi_sql_editor/views/action.xml
+++ b/bi_sql_editor/views/action.xml
@@ -1,12 +1,10 @@
-
+
-
-
SQL Views
ir.actions.act_window
@@ -14,5 +12,4 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
form
tree,form
-
diff --git a/bi_sql_editor/views/menu.xml b/bi_sql_editor/views/menu.xml
index 46087d5c5..00c540f0e 100644
--- a/bi_sql_editor/views/menu.xml
+++ b/bi_sql_editor/views/menu.xml
@@ -1,22 +1,22 @@
-
+
-
-
-
-
-
-
+
+
diff --git a/bi_sql_editor/views/view_bi_sql_view.xml b/bi_sql_editor/views/view_bi_sql_view.xml
index 91013803d..bbbd1a0bc 100644
--- a/bi_sql_editor/views/view_bi_sql_view.xml
+++ b/bi_sql_editor/views/view_bi_sql_view.xml
@@ -1,132 +1,231 @@
-
+
-
-
bi.sql.view
-
-
-
-
-
-
+
+
+
+
+
+
-
bi.sql.view
-