[IMP] bi_sql_editor: black, isort, prettier
parent
8445d37305
commit
1fa643fa26
|
@ -3,27 +3,21 @@
|
||||||
# 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 SQL Editor',
|
"name": "BI SQL Editor",
|
||||||
'summary': 'BI Views builder, based on Materialized or Normal SQL Views',
|
"summary": "BI Views builder, based on Materialized or Normal SQL Views",
|
||||||
'version': '12.0.1.2.0',
|
"version": "12.0.1.2.0",
|
||||||
'license': 'AGPL-3',
|
"license": "AGPL-3",
|
||||||
'category': 'Reporting',
|
"category": "Reporting",
|
||||||
'author': 'GRAP,Odoo Community Association (OCA)',
|
"author": "GRAP,Odoo Community Association (OCA)",
|
||||||
'website': 'https://github.com/OCA/reporting-engine',
|
"website": "https://github.com/OCA/reporting-engine",
|
||||||
'depends': [
|
"depends": ["base", "sql_request_abstract"],
|
||||||
'base',
|
"data": [
|
||||||
'sql_request_abstract',
|
"security/ir.model.access.csv",
|
||||||
|
"views/view_bi_sql_view.xml",
|
||||||
|
"views/action.xml",
|
||||||
|
"views/menu.xml",
|
||||||
],
|
],
|
||||||
'data': [
|
"demo": ["demo/res_groups_demo.xml", "demo/bi_sql_view_demo.xml"],
|
||||||
'security/ir.model.access.csv',
|
"installable": True,
|
||||||
'views/view_bi_sql_view.xml',
|
"uninstall_hook": "uninstall_hook",
|
||||||
'views/action.xml',
|
|
||||||
'views/menu.xml',
|
|
||||||
],
|
|
||||||
'demo': [
|
|
||||||
'demo/res_groups_demo.xml',
|
|
||||||
'demo/bi_sql_view_demo.xml',
|
|
||||||
],
|
|
||||||
'installable': True,
|
|
||||||
'uninstall_hook': 'uninstall_hook'
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,28 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<!--
|
<!--
|
||||||
Copyright (C) 2014 - Today GRAP (http://www.grap.coop)
|
Copyright (C) 2014 - Today GRAP (http://www.grap.coop)
|
||||||
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
|
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
|
||||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<odoo noupdate="1">
|
<odoo noupdate="1">
|
||||||
|
<record id="incorrect_sql_view" model="bi.sql.view">
|
||||||
<record id="incorrect_sql_view" model="bi.sql.view">
|
<field name="name">Draft Incorrect SQL View</field>
|
||||||
<field name="name">Draft Incorrect SQL View</field>
|
<field name="technical_name">incorrect_view</field>
|
||||||
<field name="technical_name">incorrect_view</field>
|
<field
|
||||||
<field name="query"><![CDATA[
|
name="query"
|
||||||
|
><![CDATA[
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM unexisting_table
|
FROM unexisting_table
|
||||||
ORDER BY unexisting_field
|
ORDER BY unexisting_field
|
||||||
]]>
|
]]>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
<record id="partner_sql_view" model="bi.sql.view">
|
||||||
<record id="partner_sql_view" model="bi.sql.view">
|
<field name="name">Partners View</field>
|
||||||
<field name="name">Partners View</field>
|
<field name="technical_name">partners_view</field>
|
||||||
<field name="technical_name">partners_view</field>
|
<field
|
||||||
<field name="query"><![CDATA[
|
name="query"
|
||||||
|
><![CDATA[
|
||||||
SELECT
|
SELECT
|
||||||
name as x_name,
|
name as x_name,
|
||||||
street as x_street,
|
street as x_street,
|
||||||
|
@ -30,13 +31,14 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
ORDER BY name
|
ORDER BY name
|
||||||
]]>
|
]]>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
<record id="module_sql_view" model="bi.sql.view">
|
||||||
<record id="module_sql_view" model="bi.sql.view">
|
<field name="name">Modules by Authors</field>
|
||||||
<field name="name">Modules by Authors</field>
|
<field name="technical_name">modules_view</field>
|
||||||
<field name="technical_name">modules_view</field>
|
<field name="is_materialized" eval="0" />
|
||||||
<field name="is_materialized" eval="0" />
|
<field
|
||||||
<field name="query"><![CDATA[
|
name="query"
|
||||||
|
><![CDATA[
|
||||||
SELECT
|
SELECT
|
||||||
name as x_name,
|
name as x_name,
|
||||||
case
|
case
|
||||||
|
@ -46,12 +48,20 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
FROM ir_module_module
|
FROM ir_module_module
|
||||||
]]>
|
]]>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
<function
|
||||||
<function model="bi.sql.view" name="button_validate_sql_expression" eval="([ref('module_sql_view')])"/>
|
model="bi.sql.view"
|
||||||
|
name="button_validate_sql_expression"
|
||||||
<function model="bi.sql.view" name="button_create_sql_view_and_model" eval="([ref('module_sql_view')])"/>
|
eval="([ref('module_sql_view')])"
|
||||||
|
/>
|
||||||
<function model="bi.sql.view" name="button_create_ui" eval="([ref('module_sql_view')])"/>
|
<function
|
||||||
|
model="bi.sql.view"
|
||||||
|
name="button_create_sql_view_and_model"
|
||||||
|
eval="([ref('module_sql_view')])"
|
||||||
|
/>
|
||||||
|
<function
|
||||||
|
model="bi.sql.view"
|
||||||
|
name="button_create_ui"
|
||||||
|
eval="([ref('module_sql_view')])"
|
||||||
|
/>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<!--
|
<!--
|
||||||
Copyright (C) 2014 - Today GRAP (http://www.grap.coop)
|
Copyright (C) 2014 - Today GRAP (http://www.grap.coop)
|
||||||
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
|
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
|
||||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<odoo>
|
<odoo>
|
||||||
<record id="base.group_no_one" model="res.groups">
|
<record id="base.group_no_one" model="res.groups">
|
||||||
<field name="users" eval="[(4, ref('base.user_admin'))]" />
|
<field name="users" eval="[(4, ref('base.user_admin'))]" />
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="sql_request_abstract.group_sql_request_user" model="res.groups">
|
<record id="sql_request_abstract.group_sql_request_user" model="res.groups">
|
||||||
<field name="users" eval="[(4, ref('base.user_demo'))]" />
|
<field name="users" eval="[(4, ref('base.user_demo'))]" />
|
||||||
</record>
|
</record>
|
||||||
|
|
|
@ -6,6 +6,6 @@ from odoo.api import Environment
|
||||||
|
|
||||||
def uninstall_hook(cr, registry):
|
def uninstall_hook(cr, registry):
|
||||||
env = Environment(cr, SUPERUSER_ID, {})
|
env = Environment(cr, SUPERUSER_ID, {})
|
||||||
recs = env['bi.sql.view'].search([])
|
recs = env["bi.sql.view"].search([])
|
||||||
for rec in recs:
|
for rec in recs:
|
||||||
rec.button_set_draft()
|
rec.button_set_draft()
|
||||||
|
|
|
@ -4,11 +4,13 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from psycopg2 import ProgrammingError
|
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.exceptions import UserError
|
||||||
from odoo.tools import pycompat, safe_eval, sql
|
from odoo.tools import pycompat, safe_eval, sql
|
||||||
|
|
||||||
from odoo.addons.base.models.ir_model import IrModel
|
from odoo.addons.base.models.ir_model import IrModel
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
@ -16,20 +18,20 @@ _logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _instanciate(self, model_data):
|
def _instanciate(self, model_data):
|
||||||
""" Return a class for the custom model given by
|
"""Return a class for the custom model given by
|
||||||
parameters ``model_data``. """
|
parameters ``model_data``."""
|
||||||
# This monkey patch is meant to avoid create/search tables for those
|
# This monkey patch is meant to avoid create/search tables for those
|
||||||
# materialized views. Doing "super" doesn't work.
|
# materialized views. Doing "super" doesn't work.
|
||||||
class CustomModel(models.Model):
|
class CustomModel(models.Model):
|
||||||
_name = pycompat.to_native(model_data['model'])
|
_name = pycompat.to_native(model_data["model"])
|
||||||
_description = model_data['name']
|
_description = model_data["name"]
|
||||||
_module = False
|
_module = False
|
||||||
_custom = True
|
_custom = True
|
||||||
_transient = bool(model_data['transient'])
|
_transient = bool(model_data["transient"])
|
||||||
__doc__ = model_data['info']
|
__doc__ = model_data["info"]
|
||||||
|
|
||||||
# START OF patch
|
# START OF patch
|
||||||
if model_data['model'].startswith(BiSQLView._model_prefix):
|
if model_data["model"].startswith(BiSQLView._model_prefix):
|
||||||
CustomModel._auto = False
|
CustomModel._auto = False
|
||||||
CustomModel._abstract = True
|
CustomModel._abstract = True
|
||||||
# END of patch
|
# END of patch
|
||||||
|
@ -40,62 +42,73 @@ IrModel._instanciate = _instanciate
|
||||||
|
|
||||||
|
|
||||||
class BiSQLView(models.Model):
|
class BiSQLView(models.Model):
|
||||||
_name = 'bi.sql.view'
|
_name = "bi.sql.view"
|
||||||
_order = 'sequence'
|
_order = "sequence"
|
||||||
_inherit = ['sql.request.mixin']
|
_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 = [
|
_STATE_SQL_EDITOR = [
|
||||||
('model_valid', 'SQL View and Model Created'),
|
("model_valid", "SQL View and Model Created"),
|
||||||
('ui_valid', 'Views, Action and Menu Created'),
|
("ui_valid", "Views, Action and Menu Created"),
|
||||||
]
|
]
|
||||||
|
|
||||||
technical_name = fields.Char(
|
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"
|
help="Suffix of the SQL view. SQL full name will be computed and"
|
||||||
" prefixed by 'x_bi_sql_view_'. Syntax should follow: "
|
" prefixed by 'x_bi_sql_view_'. Syntax should follow: "
|
||||||
"https://www.postgresql.org/"
|
"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(
|
view_name = fields.Char(
|
||||||
string='View Name', compute='_compute_view_name', readonly=True,
|
string="View Name",
|
||||||
store=True, help="Full name of the SQL view")
|
compute="_compute_view_name",
|
||||||
|
readonly=True,
|
||||||
|
store=True,
|
||||||
|
help="Full name of the SQL view",
|
||||||
|
)
|
||||||
|
|
||||||
model_name = fields.Char(
|
model_name = fields.Char(
|
||||||
string='Model Name', compute='_compute_model_name', readonly=True,
|
string="Model Name",
|
||||||
store=True, help="Full Qualified Name of the transient model that will"
|
compute="_compute_model_name",
|
||||||
" be created.")
|
readonly=True,
|
||||||
|
store=True,
|
||||||
|
help="Full Qualified Name of the transient model that will" " be created.",
|
||||||
|
)
|
||||||
|
|
||||||
is_materialized = fields.Boolean(
|
is_materialized = fields.Boolean(
|
||||||
string='Is Materialized View', default=True, readonly=True,
|
string="Is Materialized View",
|
||||||
states={
|
default=True,
|
||||||
'draft': [('readonly', False)],
|
readonly=True,
|
||||||
'sql_valid': [('readonly', False)],
|
states={"draft": [("readonly", False)], "sql_valid": [("readonly", False)]},
|
||||||
})
|
)
|
||||||
|
|
||||||
materialized_text = fields.Char(
|
materialized_text = fields.Char(compute="_compute_materialized_text", store=True)
|
||||||
compute='_compute_materialized_text', store=True)
|
|
||||||
|
|
||||||
size = fields.Char(
|
size = fields.Char(
|
||||||
string='Database Size', readonly=True,
|
string="Database Size",
|
||||||
help="Size of the materialized view and its indexes")
|
readonly=True,
|
||||||
|
help="Size of the materialized view and its indexes",
|
||||||
|
)
|
||||||
|
|
||||||
state = fields.Selection(selection_add=_STATE_SQL_EDITOR)
|
state = fields.Selection(selection_add=_STATE_SQL_EDITOR)
|
||||||
|
|
||||||
view_order = fields.Char(string='View Order',
|
view_order = fields.Char(
|
||||||
required=True,
|
string="View Order",
|
||||||
readonly=False,
|
required=True,
|
||||||
states={'ui_valid': [('readonly', True)]},
|
readonly=False,
|
||||||
default="pivot,graph,tree",
|
states={"ui_valid": [("readonly", True)]},
|
||||||
help='Comma-separated text. Possible values:'
|
default="pivot,graph,tree",
|
||||||
' "graph", "pivot" or "tree"')
|
help="Comma-separated text. Possible values:" ' "graph", "pivot" or "tree"',
|
||||||
|
)
|
||||||
|
|
||||||
query = fields.Text(
|
query = fields.Text(
|
||||||
help="SQL Request that will be inserted as the view. Take care to :\n"
|
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"
|
" SQL function (like EXTRACT, ...);\n"
|
||||||
" * Do not use 'SELECT *' or 'SELECT table.*';\n"
|
" * Do not use 'SELECT *' or 'SELECT table.*';\n"
|
||||||
" * prefix the name of the selectable columns by 'x_';",
|
" * prefix the name of the selectable columns by 'x_';",
|
||||||
default="SELECT\n"
|
default="SELECT\n" " my_field as x_my_field\n" "FROM my_table",
|
||||||
" my_field as x_my_field\n"
|
)
|
||||||
"FROM my_table")
|
|
||||||
|
|
||||||
domain_force = fields.Text(
|
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"
|
help="Define here access restriction to data.\n"
|
||||||
" Take care to use field name prefixed by 'x_'."
|
" Take care to use field name prefixed by 'x_'."
|
||||||
" A global 'ir.rule' will be created."
|
" A global 'ir.rule' will be created."
|
||||||
" A typical Multi Company rule is for exemple \n"
|
" A typical Multi Company rule is for exemple \n"
|
||||||
" ['|', ('x_company_id','child_of', [user.company_id.id]),"
|
" ['|', ('x_company_id','child_of', [user.company_id.id]),"
|
||||||
"('x_company_id','=',False)].",
|
"('x_company_id','=',False)].",
|
||||||
states={
|
states={"draft": [("readonly", False)], "sql_valid": [("readonly", False)]},
|
||||||
'draft': [('readonly', False)],
|
)
|
||||||
'sql_valid': [('readonly', False)],
|
|
||||||
})
|
|
||||||
|
|
||||||
computed_action_context = fields.Text(
|
computed_action_context = fields.Text(
|
||||||
compute="_compute_computed_action_context",
|
compute="_compute_computed_action_context", string="Computed Action Context"
|
||||||
string="Computed Action Context")
|
)
|
||||||
|
|
||||||
action_context = fields.Text(
|
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"
|
help="Define here a context that will be used"
|
||||||
" by default, when creating the action.",
|
" by default, when creating the action.",
|
||||||
states={
|
states={
|
||||||
'draft': [('readonly', False)],
|
"draft": [("readonly", False)],
|
||||||
'sql_valid': [('readonly', False)],
|
"sql_valid": [("readonly", False)],
|
||||||
'model_valid': [('readonly', False)],
|
"model_valid": [("readonly", False)],
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
has_group_changed = fields.Boolean(copy=False)
|
has_group_changed = fields.Boolean(copy=False)
|
||||||
|
|
||||||
bi_sql_view_field_ids = fields.One2many(
|
bi_sql_view_field_ids = fields.One2many(
|
||||||
string='SQL Fields', comodel_name='bi.sql.view.field',
|
string="SQL Fields",
|
||||||
inverse_name='bi_sql_view_id')
|
comodel_name="bi.sql.view.field",
|
||||||
|
inverse_name="bi_sql_view_id",
|
||||||
|
)
|
||||||
|
|
||||||
model_id = fields.Many2one(
|
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(
|
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(
|
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(
|
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(
|
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(
|
action_id = fields.Many2one(
|
||||||
string='Odoo Action', comodel_name='ir.actions.act_window',
|
string="Odoo Action", comodel_name="ir.actions.act_window", readonly=True
|
||||||
readonly=True)
|
)
|
||||||
|
|
||||||
menu_id = fields.Many2one(
|
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(
|
cron_id = fields.Many2one(
|
||||||
string='Odoo Cron', comodel_name='ir.cron', readonly=True,
|
string="Odoo Cron",
|
||||||
help="Cron Task that will refresh the materialized view")
|
comodel_name="ir.cron",
|
||||||
|
readonly=True,
|
||||||
|
help="Cron Task that will refresh the materialized view",
|
||||||
|
)
|
||||||
|
|
||||||
rule_id = fields.Many2one(
|
rule_id = fields.Many2one(string="Odoo Rule", comodel_name="ir.rule", readonly=True)
|
||||||
string='Odoo Rule', comodel_name='ir.rule', readonly=True)
|
|
||||||
|
|
||||||
group_ids = fields.Many2many(
|
group_ids = fields.Many2many(
|
||||||
comodel_name='res.groups', readonly=True, states={
|
comodel_name="res.groups",
|
||||||
'draft': [('readonly', False)],
|
readonly=True,
|
||||||
'sql_valid': [('readonly', False)],
|
states={"draft": [("readonly", False)], "sql_valid": [("readonly", False)]},
|
||||||
})
|
)
|
||||||
|
|
||||||
sequence = fields.Integer(string='sequence')
|
sequence = fields.Integer(string="sequence")
|
||||||
|
|
||||||
# Constrains Section
|
# Constrains Section
|
||||||
@api.constrains('is_materialized')
|
@api.constrains("is_materialized")
|
||||||
@api.multi
|
@api.multi
|
||||||
def _check_index_materialized(self):
|
def _check_index_materialized(self):
|
||||||
for rec in self.filtered(lambda x: not x.is_materialized):
|
for rec in self.filtered(lambda x: not x.is_materialized):
|
||||||
if rec.bi_sql_view_field_ids.filtered(lambda x: x.is_index):
|
if rec.bi_sql_view_field_ids.filtered(lambda x: x.is_index):
|
||||||
raise UserError(_(
|
raise UserError(
|
||||||
'You can not create indexes on non materialized views'))
|
_("You can not create indexes on non materialized views")
|
||||||
|
)
|
||||||
|
|
||||||
@api.constrains('view_order')
|
@api.constrains("view_order")
|
||||||
@api.multi
|
@api.multi
|
||||||
def _check_view_order(self):
|
def _check_view_order(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if rec.view_order:
|
if rec.view_order:
|
||||||
for vtype in rec.view_order.split(','):
|
for vtype in rec.view_order.split(","):
|
||||||
if vtype not in ('graph', 'pivot', 'tree'):
|
if vtype not in ("graph", "pivot", "tree"):
|
||||||
raise UserError(_(
|
raise UserError(
|
||||||
'Only graph, pivot or tree views are supported'))
|
_("Only graph, pivot or tree views are supported")
|
||||||
|
)
|
||||||
|
|
||||||
# Compute Section
|
# Compute Section
|
||||||
@api.depends("bi_sql_view_field_ids.graph_type")
|
@api.depends("bi_sql_view_field_ids.graph_type")
|
||||||
|
@ -207,60 +234,71 @@ class BiSQLView(models.Model):
|
||||||
"pivot_column_groupby": [],
|
"pivot_column_groupby": [],
|
||||||
}
|
}
|
||||||
for field in rec.bi_sql_view_field_ids.filtered(
|
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)
|
action["pivot_measures"].append(field.name)
|
||||||
|
|
||||||
for field in rec.bi_sql_view_field_ids.filtered(
|
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)
|
action["pivot_row_groupby"].append(field.name)
|
||||||
|
|
||||||
for field in rec.bi_sql_view_field_ids.filtered(
|
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)
|
action["pivot_column_groupby"].append(field.name)
|
||||||
|
|
||||||
rec.computed_action_context = str(action)
|
rec.computed_action_context = str(action)
|
||||||
|
|
||||||
@api.depends('is_materialized')
|
@api.depends("is_materialized")
|
||||||
@api.multi
|
@api.multi
|
||||||
def _compute_materialized_text(self):
|
def _compute_materialized_text(self):
|
||||||
for sql_view in self:
|
for sql_view in self:
|
||||||
sql_view.materialized_text =\
|
sql_view.materialized_text = (
|
||||||
sql_view.is_materialized and 'MATERIALIZED' or ''
|
sql_view.is_materialized and "MATERIALIZED" or ""
|
||||||
|
)
|
||||||
|
|
||||||
@api.depends('technical_name')
|
@api.depends("technical_name")
|
||||||
@api.multi
|
@api.multi
|
||||||
def _compute_view_name(self):
|
def _compute_view_name(self):
|
||||||
for sql_view in self:
|
for sql_view in self:
|
||||||
sql_view.view_name = '%s%s' % (
|
sql_view.view_name = "{}{}".format(
|
||||||
sql_view._sql_prefix, sql_view.technical_name)
|
sql_view._sql_prefix,
|
||||||
|
sql_view.technical_name,
|
||||||
|
)
|
||||||
|
|
||||||
@api.depends('technical_name')
|
@api.depends("technical_name")
|
||||||
@api.multi
|
@api.multi
|
||||||
def _compute_model_name(self):
|
def _compute_model_name(self):
|
||||||
for sql_view in self:
|
for sql_view in self:
|
||||||
sql_view.model_name = '%s%s' % (
|
sql_view.model_name = "{}{}".format(
|
||||||
sql_view._model_prefix, sql_view.technical_name)
|
sql_view._model_prefix,
|
||||||
|
sql_view.technical_name,
|
||||||
|
)
|
||||||
|
|
||||||
@api.onchange('group_ids')
|
@api.onchange("group_ids")
|
||||||
def onchange_group_ids(self):
|
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
|
self.has_group_changed = True
|
||||||
|
|
||||||
# Overload Section
|
# Overload Section
|
||||||
@api.multi
|
@api.multi
|
||||||
def write(self, vals):
|
def write(self, vals):
|
||||||
res = super(BiSQLView, self).write(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):
|
for rec in self.filtered(lambda x: x.menu_id):
|
||||||
rec.menu_id.sequence = rec.sequence
|
rec.menu_id.sequence = rec.sequence
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def unlink(self):
|
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(
|
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()
|
self.cron_id.unlink()
|
||||||
return super(BiSQLView, self).unlink()
|
return super(BiSQLView, self).unlink()
|
||||||
|
|
||||||
|
@ -268,10 +306,12 @@ class BiSQLView(models.Model):
|
||||||
def copy(self, default=None):
|
def copy(self, default=None):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
default = dict(default or {})
|
default = dict(default or {})
|
||||||
default.update({
|
default.update(
|
||||||
'name': _('%s (Copy)') % self.name,
|
{
|
||||||
'technical_name': '%s_copy' % self.technical_name,
|
"name": _("%s (Copy)") % self.name,
|
||||||
})
|
"technical_name": "%s_copy" % self.technical_name,
|
||||||
|
}
|
||||||
|
)
|
||||||
return super(BiSQLView, self).copy(default=default)
|
return super(BiSQLView, self).copy(default=default)
|
||||||
|
|
||||||
# Action Section
|
# Action Section
|
||||||
|
@ -288,11 +328,12 @@ class BiSQLView(models.Model):
|
||||||
|
|
||||||
if sql_view.is_materialized:
|
if sql_view.is_materialized:
|
||||||
if not sql_view.cron_id:
|
if not sql_view.cron_id:
|
||||||
sql_view.cron_id = self.env['ir.cron'].create(
|
sql_view.cron_id = (
|
||||||
sql_view._prepare_cron()).id
|
self.env["ir.cron"].create(sql_view._prepare_cron()).id
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
sql_view.cron_id.active = True
|
sql_view.cron_id.active = True
|
||||||
sql_view.state = 'model_valid'
|
sql_view.state = "model_valid"
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def button_set_draft(self):
|
def button_set_draft(self):
|
||||||
|
@ -304,7 +345,7 @@ class BiSQLView(models.Model):
|
||||||
sql_view.pivot_view_id.unlink()
|
sql_view.pivot_view_id.unlink()
|
||||||
sql_view.search_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)
|
# Drop SQL View (and indexes by cascade)
|
||||||
if sql_view.is_materialized:
|
if sql_view.is_materialized:
|
||||||
sql_view._drop_view()
|
sql_view._drop_view()
|
||||||
|
@ -321,25 +362,27 @@ class BiSQLView(models.Model):
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def button_create_ui(self):
|
def button_create_ui(self):
|
||||||
self.tree_view_id = self.env['ir.ui.view'].create(
|
self.tree_view_id = self.env["ir.ui.view"].create(self._prepare_tree_view()).id
|
||||||
self._prepare_tree_view()).id
|
self.graph_view_id = (
|
||||||
self.graph_view_id = self.env['ir.ui.view'].create(
|
self.env["ir.ui.view"].create(self._prepare_graph_view()).id
|
||||||
self._prepare_graph_view()).id
|
)
|
||||||
self.pivot_view_id = self.env['ir.ui.view'].create(
|
self.pivot_view_id = (
|
||||||
self._prepare_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.search_view_id = (
|
||||||
self.action_id = self.env['ir.actions.act_window'].create(
|
self.env["ir.ui.view"].create(self._prepare_search_view()).id
|
||||||
self._prepare_action()).id
|
)
|
||||||
self.menu_id = self.env['ir.ui.menu'].create(
|
self.action_id = (
|
||||||
self._prepare_menu()).id
|
self.env["ir.actions.act_window"].create(self._prepare_action()).id
|
||||||
self.write({'state': 'ui_valid'})
|
)
|
||||||
|
self.menu_id = self.env["ir.ui.menu"].create(self._prepare_menu()).id
|
||||||
|
self.write({"state": "ui_valid"})
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def button_update_model_access(self):
|
def button_update_model_access(self):
|
||||||
self._drop_model_access()
|
self._drop_model_access()
|
||||||
self._create_model_access()
|
self._create_model_access()
|
||||||
self.write({'has_group_changed': False})
|
self.write({"has_group_changed": False})
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def button_refresh_materialized_view(self):
|
def button_refresh_materialized_view(self):
|
||||||
|
@ -348,10 +391,10 @@ class BiSQLView(models.Model):
|
||||||
@api.multi
|
@api.multi
|
||||||
def button_open_view(self):
|
def button_open_view(self):
|
||||||
return {
|
return {
|
||||||
'type': 'ir.actions.act_window',
|
"type": "ir.actions.act_window",
|
||||||
'res_model': self.model_id.model,
|
"res_model": self.model_id.model,
|
||||||
'search_view_id': self.search_view_id.id,
|
"search_view_id": self.search_view_id.id,
|
||||||
'view_mode': self.action_id.view_mode,
|
"view_mode": self.action_id.view_mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Prepare Function
|
# Prepare Function
|
||||||
|
@ -360,13 +403,14 @@ class BiSQLView(models.Model):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
field_id = []
|
field_id = []
|
||||||
for field in self.bi_sql_view_field_ids.filtered(
|
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()])
|
field_id.append([0, False, field._prepare_model_field()])
|
||||||
return {
|
return {
|
||||||
'name': self.name,
|
"name": self.name,
|
||||||
'model': self.model_name,
|
"model": self.model_name,
|
||||||
'access_ids': [],
|
"access_ids": [],
|
||||||
'field_id': field_id,
|
"field_id": field_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
@ -374,118 +418,120 @@ class BiSQLView(models.Model):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
res = []
|
res = []
|
||||||
for group in self.group_ids:
|
for group in self.group_ids:
|
||||||
res.append({
|
res.append(
|
||||||
'name': _('%s Access %s') % (
|
{
|
||||||
self.model_name, group.full_name),
|
"name": _("%s Access %s") % (self.model_name, group.full_name),
|
||||||
'model_id': self.model_id.id,
|
"model_id": self.model_id.id,
|
||||||
'group_id': group.id,
|
"group_id": group.id,
|
||||||
'perm_read': True,
|
"perm_read": True,
|
||||||
'perm_create': False,
|
"perm_create": False,
|
||||||
'perm_write': False,
|
"perm_write": False,
|
||||||
'perm_unlink': False,
|
"perm_unlink": False,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _prepare_cron(self):
|
def _prepare_cron(self):
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
return {
|
return {
|
||||||
'name': _('Refresh Materialized View %s') % self.view_name,
|
"name": _("Refresh Materialized View %s") % self.view_name,
|
||||||
'user_id': SUPERUSER_ID,
|
"user_id": SUPERUSER_ID,
|
||||||
'model_id': self.env['ir.model'].search([
|
"model_id": self.env["ir.model"]
|
||||||
('model', '=', self._name)], limit=1).id,
|
.search([("model", "=", self._name)], limit=1)
|
||||||
'state': 'code',
|
.id,
|
||||||
'code': 'model._refresh_materialized_view_cron(%s)' % self.ids,
|
"state": "code",
|
||||||
'numbercall': -1,
|
"code": "model._refresh_materialized_view_cron(%s)" % self.ids,
|
||||||
'interval_number': 1,
|
"numbercall": -1,
|
||||||
'interval_type': 'days',
|
"interval_number": 1,
|
||||||
'nextcall': datetime(now.year, now.month, now.day+1),
|
"interval_type": "days",
|
||||||
'active': True,
|
"nextcall": datetime(now.year, now.month, now.day + 1),
|
||||||
|
"active": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _prepare_rule(self):
|
def _prepare_rule(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
return {
|
return {
|
||||||
'name': _('Access %s') % self.name,
|
"name": _("Access %s") % self.name,
|
||||||
'model_id': self.model_id.id,
|
"model_id": self.model_id.id,
|
||||||
'domain_force': self.domain_force,
|
"domain_force": self.domain_force,
|
||||||
'global': True,
|
"global": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _prepare_tree_view(self):
|
def _prepare_tree_view(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
return {
|
return {
|
||||||
'name': self.name,
|
"name": self.name,
|
||||||
'type': 'tree',
|
"type": "tree",
|
||||||
'model': self.model_id.model,
|
"model": self.model_id.model,
|
||||||
'arch':
|
"arch": """<?xml version="1.0"?>"""
|
||||||
"""<?xml version="1.0"?>"""
|
"""<tree string="Analysis">{}"""
|
||||||
"""<tree string="Analysis">{}"""
|
"""</tree>""".format(
|
||||||
"""</tree>""".format("".join(
|
"".join([x._prepare_tree_field() for x in self.bi_sql_view_field_ids])
|
||||||
[x._prepare_tree_field()
|
),
|
||||||
for x in self.bi_sql_view_field_ids]))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _prepare_graph_view(self):
|
def _prepare_graph_view(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
return {
|
return {
|
||||||
'name': self.name,
|
"name": self.name,
|
||||||
'type': 'graph',
|
"type": "graph",
|
||||||
'model': self.model_id.model,
|
"model": self.model_id.model,
|
||||||
'arch':
|
"arch": """<?xml version="1.0"?>"""
|
||||||
"""<?xml version="1.0"?>"""
|
"""<graph string="Analysis" type="bar" stacked="True">{}"""
|
||||||
"""<graph string="Analysis" type="bar" stacked="True">{}"""
|
"""</graph>""".format(
|
||||||
"""</graph>""".format("".join(
|
"".join([x._prepare_graph_field() for x in self.bi_sql_view_field_ids])
|
||||||
[x._prepare_graph_field()
|
),
|
||||||
for x in self.bi_sql_view_field_ids]))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _prepare_pivot_view(self):
|
def _prepare_pivot_view(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
return {
|
return {
|
||||||
'name': self.name,
|
"name": self.name,
|
||||||
'type': 'pivot',
|
"type": "pivot",
|
||||||
'model': self.model_id.model,
|
"model": self.model_id.model,
|
||||||
'arch':
|
"arch": """<?xml version="1.0"?>"""
|
||||||
"""<?xml version="1.0"?>"""
|
"""<pivot string="Analysis" stacked="True">{}"""
|
||||||
"""<pivot string="Analysis" stacked="True">{}"""
|
"""</pivot>""".format(
|
||||||
"""</pivot>""".format("".join(
|
"".join([x._prepare_pivot_field() for x in self.bi_sql_view_field_ids])
|
||||||
[x._prepare_pivot_field()
|
),
|
||||||
for x in self.bi_sql_view_field_ids]))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _prepare_search_view(self):
|
def _prepare_search_view(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
return {
|
return {
|
||||||
'name': self.name,
|
"name": self.name,
|
||||||
'type': 'search',
|
"type": "search",
|
||||||
'model': self.model_id.model,
|
"model": self.model_id.model,
|
||||||
'arch':
|
"arch": """<?xml version="1.0"?>"""
|
||||||
"""<?xml version="1.0"?>"""
|
"""<search string="Analysis">{}"""
|
||||||
"""<search string="Analysis">{}"""
|
"""<group expand="1" string="Group By">{}</group>"""
|
||||||
"""<group expand="1" string="Group By">{}</group>"""
|
"""</search>""".format(
|
||||||
"""</search>""".format(
|
"".join(
|
||||||
"".join(
|
[x._prepare_search_field() for x in self.bi_sql_view_field_ids]
|
||||||
[x._prepare_search_field()
|
),
|
||||||
for x in self.bi_sql_view_field_ids]),
|
"".join(
|
||||||
"".join(
|
[
|
||||||
[x._prepare_search_filter_field()
|
x._prepare_search_filter_field()
|
||||||
for x in self.bi_sql_view_field_ids]))
|
for x in self.bi_sql_view_field_ids
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _prepare_action(self):
|
def _prepare_action(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
view_mode = self.view_order
|
view_mode = self.view_order
|
||||||
first_view = view_mode.split(',')[0]
|
first_view = view_mode.split(",")[0]
|
||||||
if first_view == 'tree':
|
if first_view == "tree":
|
||||||
view_id = self.tree_view_id.id
|
view_id = self.tree_view_id.id
|
||||||
elif first_view == 'pivot':
|
elif first_view == "pivot":
|
||||||
view_id = self.pivot_view_id.id
|
view_id = self.pivot_view_id.id
|
||||||
else:
|
else:
|
||||||
view_id = self.graph_view_id.id
|
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():
|
for k, v in safe_eval(self.action_context).items():
|
||||||
action[k] = v
|
action[k] = v
|
||||||
return {
|
return {
|
||||||
'name': self._prepare_action_name(),
|
"name": self._prepare_action_name(),
|
||||||
'res_model': self.model_id.model,
|
"res_model": self.model_id.model,
|
||||||
'type': 'ir.actions.act_window',
|
"type": "ir.actions.act_window",
|
||||||
'view_mode': view_mode,
|
"view_mode": view_mode,
|
||||||
'view_id': view_id,
|
"view_id": view_id,
|
||||||
'search_view_id': self.search_view_id.id,
|
"search_view_id": self.search_view_id.id,
|
||||||
'context': str(action),
|
"context": str(action),
|
||||||
}
|
}
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
@ -507,18 +553,19 @@ class BiSQLView(models.Model):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
if not self.is_materialized:
|
if not self.is_materialized:
|
||||||
return self.name
|
return self.name
|
||||||
return "%s (%s)" % (
|
return "{} ({})".format(
|
||||||
self.name,
|
self.name,
|
||||||
datetime.utcnow().strftime(_("%m/%d/%Y %H:%M:%S UTC")))
|
datetime.utcnow().strftime(_("%m/%d/%Y %H:%M:%S UTC")),
|
||||||
|
)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _prepare_menu(self):
|
def _prepare_menu(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
return {
|
return {
|
||||||
'name': self.name,
|
"name": self.name,
|
||||||
'parent_id': self.env.ref('bi_sql_editor.menu_bi_sql_editor').id,
|
"parent_id": self.env.ref("bi_sql_editor.menu_bi_sql_editor").id,
|
||||||
'action': 'ir.actions.act_window,%s' % self.action_id.id,
|
"action": "ir.actions.act_window,%s" % self.action_id.id,
|
||||||
'sequence': self.sequence,
|
"sequence": self.sequence,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Custom Section
|
# Custom Section
|
||||||
|
@ -530,8 +577,9 @@ class BiSQLView(models.Model):
|
||||||
def _drop_view(self):
|
def _drop_view(self):
|
||||||
for sql_view in self:
|
for sql_view in self:
|
||||||
self._log_execute(
|
self._log_execute(
|
||||||
"DROP %s VIEW IF EXISTS %s" % (
|
"DROP %s VIEW IF EXISTS %s"
|
||||||
sql_view.materialized_text, sql_view.view_name))
|
% (sql_view.materialized_text, sql_view.view_name)
|
||||||
|
)
|
||||||
sql_view.size = False
|
sql_view.size = False
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
@ -542,29 +590,28 @@ class BiSQLView(models.Model):
|
||||||
self._log_execute(sql_view._prepare_request_for_execution())
|
self._log_execute(sql_view._prepare_request_for_execution())
|
||||||
sql_view._refresh_size()
|
sql_view._refresh_size()
|
||||||
except ProgrammingError as e:
|
except ProgrammingError as e:
|
||||||
raise UserError(_(
|
raise UserError(
|
||||||
"SQL Error while creating %s VIEW %s :\n %s") % (
|
_("SQL Error while creating %s VIEW %s :\n %s")
|
||||||
sql_view.materialized_text, sql_view.view_name,
|
% (sql_view.materialized_text, sql_view.view_name, str(e))
|
||||||
e.message))
|
)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _create_index(self):
|
def _create_index(self):
|
||||||
for sql_view in self:
|
for sql_view in self:
|
||||||
for sql_field in sql_view.bi_sql_view_field_ids.filtered(
|
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(
|
self._log_execute(
|
||||||
"CREATE INDEX %s ON %s (%s);" % (
|
"CREATE INDEX %s ON %s (%s);"
|
||||||
sql_field.index_name, sql_view.view_name,
|
% (sql_field.index_name, sql_view.view_name, sql_field.name)
|
||||||
sql_field.name))
|
)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _create_model_and_fields(self):
|
def _create_model_and_fields(self):
|
||||||
for sql_view in self:
|
for sql_view in self:
|
||||||
# Create model
|
# Create model
|
||||||
sql_view.model_id = self.env['ir.model'].create(
|
sql_view.model_id = self.env["ir.model"].create(self._prepare_model()).id
|
||||||
self._prepare_model()).id
|
sql_view.rule_id = self.env["ir.rule"].create(self._prepare_rule()).id
|
||||||
sql_view.rule_id = self.env['ir.rule'].create(
|
|
||||||
self._prepare_rule()).id
|
|
||||||
# Drop table, created by the ORM
|
# Drop table, created by the ORM
|
||||||
if sql.table_exists(self._cr, sql_view.view_name):
|
if sql.table_exists(self._cr, sql_view.view_name):
|
||||||
req = "DROP TABLE %s" % 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):
|
def _create_model_access(self):
|
||||||
for sql_view in self:
|
for sql_view in self:
|
||||||
for item in sql_view._prepare_model_access():
|
for item in sql_view._prepare_model_access():
|
||||||
self.env['ir.model.access'].create(item)
|
self.env["ir.model.access"].create(item)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _drop_model_access(self):
|
def _drop_model_access(self):
|
||||||
for sql_view in self:
|
for sql_view in self:
|
||||||
self.env['ir.model.access'].search(
|
self.env["ir.model.access"].search(
|
||||||
[('model_id', '=', sql_view.model_name)]).unlink()
|
[("model_id", "=", sql_view.model_name)]
|
||||||
|
).unlink()
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _drop_model_and_fields(self):
|
def _drop_model_and_fields(self):
|
||||||
|
@ -593,7 +641,8 @@ class BiSQLView(models.Model):
|
||||||
@api.multi
|
@api.multi
|
||||||
def _hook_executed_request(self):
|
def _hook_executed_request(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
req = """
|
req = (
|
||||||
|
"""
|
||||||
SELECT attnum,
|
SELECT attnum,
|
||||||
attname AS column,
|
attname AS column,
|
||||||
format_type(atttypid, atttypmod) AS type
|
format_type(atttypid, atttypmod) AS type
|
||||||
|
@ -601,19 +650,22 @@ class BiSQLView(models.Model):
|
||||||
WHERE attrelid = '%s'::regclass
|
WHERE attrelid = '%s'::regclass
|
||||||
AND NOT attisdropped
|
AND NOT attisdropped
|
||||||
AND attnum > 0
|
AND attnum > 0
|
||||||
ORDER BY attnum;""" % self.view_name
|
ORDER BY attnum;"""
|
||||||
|
% self.view_name
|
||||||
|
)
|
||||||
self._log_execute(req)
|
self._log_execute(req)
|
||||||
return self.env.cr.fetchall()
|
return self.env.cr.fetchall()
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _prepare_request_check_execution(self):
|
def _prepare_request_check_execution(self):
|
||||||
self.ensure_one()
|
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
|
@api.multi
|
||||||
def _prepare_request_for_execution(self):
|
def _prepare_request_for_execution(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
query = """
|
query = (
|
||||||
|
"""
|
||||||
SELECT
|
SELECT
|
||||||
CAST(row_number() OVER () as integer) AS id,
|
CAST(row_number() OVER () as integer) AS id,
|
||||||
CAST(Null as timestamp without time zone) as create_date,
|
CAST(Null as timestamp without time zone) as create_date,
|
||||||
|
@ -623,9 +675,14 @@ class BiSQLView(models.Model):
|
||||||
my_query.*
|
my_query.*
|
||||||
FROM
|
FROM
|
||||||
(%s) as my_query
|
(%s) as my_query
|
||||||
""" % self.query
|
"""
|
||||||
return "CREATE %s VIEW %s AS (%s);" % (
|
% self.query
|
||||||
self.materialized_text, self.view_name, query)
|
)
|
||||||
|
return "CREATE {} VIEW {} AS ({});".format(
|
||||||
|
self.materialized_text,
|
||||||
|
self.view_name,
|
||||||
|
query,
|
||||||
|
)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _check_execution(self):
|
def _check_execution(self):
|
||||||
|
@ -635,54 +692,59 @@ class BiSQLView(models.Model):
|
||||||
After the execution, and before the rollback, an analysis of
|
After the execution, and before the rollback, an analysis of
|
||||||
the database structure is done, to know fields type."""
|
the database structure is done, to know fields type."""
|
||||||
self.ensure_one()
|
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()
|
columns = super(BiSQLView, self)._check_execution()
|
||||||
field_ids = []
|
field_ids = []
|
||||||
for column in columns:
|
for column in columns:
|
||||||
existing_field = self.bi_sql_view_field_ids.filtered(
|
existing_field = self.bi_sql_view_field_ids.filtered(
|
||||||
lambda x: x.name == column[1])
|
lambda x: x.name == column[1]
|
||||||
|
)
|
||||||
if existing_field:
|
if existing_field:
|
||||||
# Update existing field
|
# Update existing field
|
||||||
field_ids.append(existing_field.id)
|
field_ids.append(existing_field.id)
|
||||||
existing_field.write({
|
existing_field.write({"sequence": column[0], "sql_type": column[2]})
|
||||||
'sequence': column[0],
|
|
||||||
'sql_type': column[2],
|
|
||||||
})
|
|
||||||
else:
|
else:
|
||||||
# Create a new one if name is prefixed by x_
|
# Create a new one if name is prefixed by x_
|
||||||
if column[1][:2] == 'x_':
|
if column[1][:2] == "x_":
|
||||||
field_ids.append(sql_view_field_obj.create({
|
field_ids.append(
|
||||||
'sequence': column[0],
|
sql_view_field_obj.create(
|
||||||
'name': column[1],
|
{
|
||||||
'sql_type': column[2],
|
"sequence": column[0],
|
||||||
'bi_sql_view_id': self.id,
|
"name": column[1],
|
||||||
}).id)
|
"sql_type": column[2],
|
||||||
|
"bi_sql_view_id": self.id,
|
||||||
|
}
|
||||||
|
).id
|
||||||
|
)
|
||||||
|
|
||||||
# Drop obsolete view field
|
# Drop obsolete view field
|
||||||
self.bi_sql_view_field_ids.filtered(
|
self.bi_sql_view_field_ids.filtered(lambda x: x.id not in field_ids).unlink()
|
||||||
lambda x: x.id not in field_ids).unlink()
|
|
||||||
|
|
||||||
if not self.bi_sql_view_field_ids:
|
if not self.bi_sql_view_field_ids:
|
||||||
raise UserError(_(
|
raise UserError(
|
||||||
"No Column was found.\n"
|
_("No Column was found.\n" "Columns name should be prefixed by 'x_'.")
|
||||||
"Columns name should be prefixed by 'x_'."))
|
)
|
||||||
|
|
||||||
return columns
|
return columns
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _refresh_materialized_view_cron(self, view_ids):
|
def _refresh_materialized_view_cron(self, view_ids):
|
||||||
sql_views = self.search([
|
sql_views = self.search(
|
||||||
('is_materialized', '=', True),
|
[
|
||||||
('state', 'in', ['model_valid', 'ui_valid']),
|
("is_materialized", "=", True),
|
||||||
('id', 'in', view_ids),
|
("state", "in", ["model_valid", "ui_valid"]),
|
||||||
])
|
("id", "in", view_ids),
|
||||||
|
]
|
||||||
|
)
|
||||||
return sql_views._refresh_materialized_view()
|
return sql_views._refresh_materialized_view()
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _refresh_materialized_view(self):
|
def _refresh_materialized_view(self):
|
||||||
for sql_view in self.filtered(lambda x: x.is_materialized):
|
for sql_view in self.filtered(lambda x: x.is_materialized):
|
||||||
req = "REFRESH %s VIEW %s" % (
|
req = "REFRESH {} VIEW {}".format(
|
||||||
sql_view.materialized_text, sql_view.view_name)
|
sql_view.materialized_text,
|
||||||
|
sql_view.view_name,
|
||||||
|
)
|
||||||
self._log_execute(req)
|
self._log_execute(req)
|
||||||
sql_view._refresh_size()
|
sql_view._refresh_size()
|
||||||
if sql_view.action_id:
|
if sql_view.action_id:
|
||||||
|
@ -696,7 +758,8 @@ class BiSQLView(models.Model):
|
||||||
def _refresh_size(self):
|
def _refresh_size(self):
|
||||||
for sql_view in self:
|
for sql_view in self:
|
||||||
req = "SELECT pg_size_pretty(pg_total_relation_size('%s'));" % (
|
req = "SELECT pg_size_pretty(pg_total_relation_size('%s'));" % (
|
||||||
sql_view.view_name)
|
sql_view.view_name
|
||||||
|
)
|
||||||
self._log_execute(req)
|
self._log_execute(req)
|
||||||
sql_view.size = self.env.cr.fetchone()[0]
|
sql_view.size = self.env.cr.fetchone()[0]
|
||||||
|
|
||||||
|
@ -704,4 +767,4 @@ class BiSQLView(models.Model):
|
||||||
def button_preview_sql_expression(self):
|
def button_preview_sql_expression(self):
|
||||||
self.button_validate_sql_expression()
|
self.button_validate_sql_expression()
|
||||||
res = self._execute_sql_request()
|
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])))
|
||||||
|
|
|
@ -9,145 +9,165 @@ from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
|
||||||
class BiSQLViewField(models.Model):
|
class BiSQLViewField(models.Model):
|
||||||
_name = 'bi.sql.view.field'
|
_name = "bi.sql.view.field"
|
||||||
_description = 'Bi SQL View Field'
|
_description = "Bi SQL View Field"
|
||||||
_order = 'sequence'
|
_order = "sequence"
|
||||||
|
|
||||||
_TTYPE_SELECTION = [
|
_TTYPE_SELECTION = [
|
||||||
('boolean', 'boolean'),
|
("boolean", "boolean"),
|
||||||
('char', 'char'),
|
("char", "char"),
|
||||||
('date', 'date'),
|
("date", "date"),
|
||||||
('datetime', 'datetime'),
|
("datetime", "datetime"),
|
||||||
('float', 'float'),
|
("float", "float"),
|
||||||
('integer', 'integer'),
|
("integer", "integer"),
|
||||||
('many2one', 'many2one'),
|
("many2one", "many2one"),
|
||||||
('selection', 'selection'),
|
("selection", "selection"),
|
||||||
]
|
]
|
||||||
|
|
||||||
_GRAPH_TYPE_SELECTION = [
|
_GRAPH_TYPE_SELECTION = [
|
||||||
('col', 'Column'),
|
("col", "Column"),
|
||||||
('row', 'Row'),
|
("row", "Row"),
|
||||||
('measure', 'Measure'),
|
("measure", "Measure"),
|
||||||
]
|
]
|
||||||
|
|
||||||
_TREE_VISIBILITY_SELECTION = [
|
_TREE_VISIBILITY_SELECTION = [
|
||||||
('unavailable', 'Unavailable'),
|
("unavailable", "Unavailable"),
|
||||||
('hidden', 'Hidden'),
|
("hidden", "Hidden"),
|
||||||
('available', 'Available'),
|
("available", "Available"),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Mapping to guess Odoo field type, from SQL column type
|
# Mapping to guess Odoo field type, from SQL column type
|
||||||
_SQL_MAPPING = {
|
_SQL_MAPPING = {
|
||||||
'boolean': 'boolean',
|
"boolean": "boolean",
|
||||||
'bigint': 'integer',
|
"bigint": "integer",
|
||||||
'integer': 'integer',
|
"integer": "integer",
|
||||||
'double precision': 'float',
|
"double precision": "float",
|
||||||
'numeric': 'float',
|
"numeric": "float",
|
||||||
'text': 'char',
|
"text": "char",
|
||||||
'character varying': 'char',
|
"character varying": "char",
|
||||||
'date': 'date',
|
"date": "date",
|
||||||
'timestamp without time zone': 'datetime',
|
"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(
|
sql_type = fields.Char(
|
||||||
string='SQL Type', required=True, readonly=True,
|
string="SQL Type", required=True, readonly=True, help="SQL Type in the database"
|
||||||
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(
|
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(
|
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"
|
" 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(
|
is_group_by = fields.Boolean(
|
||||||
string='Is Group by', help="Check this box if you want to create"
|
string="Is Group by",
|
||||||
" a 'group by' option in the search view")
|
help="Check this box if you want to create"
|
||||||
|
" a 'group by' option in the search view",
|
||||||
|
)
|
||||||
|
|
||||||
index_name = fields.Char(
|
index_name = fields.Char(string="Index Name", compute="_compute_index_name")
|
||||||
string='Index Name', compute='_compute_index_name')
|
|
||||||
|
|
||||||
graph_type = fields.Selection(
|
graph_type = fields.Selection(string="Graph Type", selection=_GRAPH_TYPE_SELECTION)
|
||||||
string='Graph Type', selection=_GRAPH_TYPE_SELECTION)
|
|
||||||
|
|
||||||
tree_visibility = fields.Selection(
|
tree_visibility = fields.Selection(
|
||||||
string='Tree Visibility', selection=_TREE_VISIBILITY_SELECTION,
|
string="Tree Visibility",
|
||||||
default='available', required=True)
|
selection=_TREE_VISIBILITY_SELECTION,
|
||||||
|
default="available",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
field_description = fields.Char(
|
field_description = fields.Char(
|
||||||
string='Field Description', help="This will be used as the name"
|
string="Field Description",
|
||||||
" of the Odoo field, displayed for users")
|
help="This will be used as the name" " of the Odoo field, displayed for users",
|
||||||
|
)
|
||||||
|
|
||||||
ttype = fields.Selection(
|
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"
|
" 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"
|
" 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(
|
selection = fields.Text(
|
||||||
string='Selection Options', default='[]',
|
string="Selection Options",
|
||||||
|
default="[]",
|
||||||
help="For 'Selection' Odoo field.\n"
|
help="For 'Selection' Odoo field.\n"
|
||||||
" List of options, specified as a Python expression defining a list of"
|
" List of options, specified as a Python expression defining a list of"
|
||||||
" (key, label) pairs. For example:"
|
" (key, label) pairs. For example:"
|
||||||
" [('blue','Blue'), ('yellow','Yellow')]")
|
" [('blue','Blue'), ('yellow','Yellow')]",
|
||||||
|
)
|
||||||
|
|
||||||
many2one_model_id = fields.Many2one(
|
many2one_model_id = fields.Many2one(
|
||||||
comodel_name='ir.model', string='Model',
|
comodel_name="ir.model",
|
||||||
help="For 'Many2one' Odoo field.\n"
|
string="Model",
|
||||||
" Comodel of the field.")
|
help="For 'Many2one' Odoo field.\n" " Comodel of the field.",
|
||||||
|
)
|
||||||
|
|
||||||
# Constrains Section
|
# Constrains Section
|
||||||
@api.constrains('is_index')
|
@api.constrains("is_index")
|
||||||
@api.multi
|
@api.multi
|
||||||
def _check_index_materialized(self):
|
def _check_index_materialized(self):
|
||||||
for rec in self.filtered(lambda x: x.is_index):
|
for rec in self.filtered(lambda x: x.is_index):
|
||||||
if not rec.bi_sql_view_id.is_materialized:
|
if not rec.bi_sql_view_id.is_materialized:
|
||||||
raise UserError(_(
|
raise UserError(
|
||||||
'You can not create indexes on non materialized views'))
|
_("You can not create indexes on non materialized views")
|
||||||
|
)
|
||||||
|
|
||||||
# Compute Section
|
# Compute Section
|
||||||
@api.multi
|
@api.multi
|
||||||
def _compute_index_name(self):
|
def _compute_index_name(self):
|
||||||
for sql_field in self:
|
for sql_field in self:
|
||||||
sql_field.index_name = '%s_%s' % (
|
sql_field.index_name = "{}_{}".format(
|
||||||
sql_field.bi_sql_view_id.view_name, sql_field.name)
|
sql_field.bi_sql_view_id.view_name,
|
||||||
|
sql_field.name,
|
||||||
|
)
|
||||||
|
|
||||||
# Overload Section
|
# Overload Section
|
||||||
@api.model
|
@api.model
|
||||||
def create(self, vals):
|
def create(self, vals):
|
||||||
field_without_prefix = vals['name'][2:]
|
field_without_prefix = vals["name"][2:]
|
||||||
# guess field description
|
# guess field description
|
||||||
field_description = re.sub(
|
field_description = re.sub(
|
||||||
r'\w+', lambda m: m.group(0).capitalize(),
|
r"\w+",
|
||||||
field_without_prefix.replace('_id', '').replace('_', ' '))
|
lambda m: m.group(0).capitalize(),
|
||||||
|
field_without_prefix.replace("_id", "").replace("_", " "),
|
||||||
|
)
|
||||||
|
|
||||||
# Guess ttype
|
# Guess ttype
|
||||||
# Don't execute as simple .get() in the dict to manage
|
# Don't execute as simple .get() in the dict to manage
|
||||||
# correctly the type 'character varying(x)'
|
# correctly the type 'character varying(x)'
|
||||||
ttype = False
|
ttype = False
|
||||||
for k, v in self._SQL_MAPPING.items():
|
for k, v in self._SQL_MAPPING.items():
|
||||||
if k in vals['sql_type']:
|
if k in vals["sql_type"]:
|
||||||
ttype = v
|
ttype = v
|
||||||
|
|
||||||
# Guess many2one_model_id
|
# Guess many2one_model_id
|
||||||
many2one_model_id = False
|
many2one_model_id = False
|
||||||
if vals['sql_type'] == 'integer' and(
|
if vals["sql_type"] == "integer" and (vals["name"][-3:] == "_id"):
|
||||||
vals['name'][-3:] == '_id'):
|
ttype = "many2one"
|
||||||
ttype = 'many2one'
|
model_name = self._model_mapping().get(field_without_prefix, "")
|
||||||
model_name = self._model_mapping().get(field_without_prefix, '')
|
many2one_model_id = (
|
||||||
many2one_model_id = self.env['ir.model'].search(
|
self.env["ir.model"].search([("model", "=", model_name)]).id
|
||||||
[('model', '=', model_name)]).id
|
)
|
||||||
|
|
||||||
vals.update({
|
vals.update(
|
||||||
'ttype': ttype,
|
{
|
||||||
'field_description': field_description,
|
"ttype": ttype,
|
||||||
'many2one_model_id': many2one_model_id,
|
"field_description": field_description,
|
||||||
})
|
"many2one_model_id": many2one_model_id,
|
||||||
|
}
|
||||||
|
)
|
||||||
return super(BiSQLViewField, self).create(vals)
|
return super(BiSQLViewField, self).create(vals)
|
||||||
|
|
||||||
# Custom Section
|
# Custom Section
|
||||||
|
@ -157,8 +177,9 @@ class BiSQLViewField(models.Model):
|
||||||
field name. Sample :
|
field name. Sample :
|
||||||
{'account_id': 'account.account'; 'product_id': 'product.product'}
|
{'account_id': 'account.account'; 'product_id': 'product.product'}
|
||||||
"""
|
"""
|
||||||
relation_fields = self.env['ir.model.fields'].search([
|
relation_fields = self.env["ir.model.fields"].search(
|
||||||
('ttype', '=', 'many2one')])
|
[("ttype", "=", "many2one")]
|
||||||
|
)
|
||||||
res = {}
|
res = {}
|
||||||
keys_to_pop = []
|
keys_to_pop = []
|
||||||
for field in relation_fields:
|
for field in relation_fields:
|
||||||
|
@ -177,49 +198,49 @@ class BiSQLViewField(models.Model):
|
||||||
def _prepare_model_field(self):
|
def _prepare_model_field(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
return {
|
return {
|
||||||
'name': self.name,
|
"name": self.name,
|
||||||
'field_description': self.field_description,
|
"field_description": self.field_description,
|
||||||
'model_id': self.bi_sql_view_id.model_id.id,
|
"model_id": self.bi_sql_view_id.model_id.id,
|
||||||
'ttype': self.ttype,
|
"ttype": self.ttype,
|
||||||
'selection': self.ttype == 'selection' and self.selection or False,
|
"selection": self.ttype == "selection" and self.selection or False,
|
||||||
'relation': self.ttype == 'many2one' and
|
"relation": self.ttype == "many2one"
|
||||||
self.many2one_model_id.model or False,
|
and self.many2one_model_id.model
|
||||||
|
or False,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _prepare_tree_field(self):
|
def _prepare_tree_field(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
res = ''
|
res = ""
|
||||||
if self.field_description and self.tree_visibility != 'unavailable':
|
if self.field_description and self.tree_visibility != "unavailable":
|
||||||
res = """<field name="{}" {}/>""".format(
|
res = """<field name="{}" {}/>""".format(
|
||||||
self.name,
|
self.name, self.tree_visibility == "hidden" and 'invisible="1"' or ""
|
||||||
self.tree_visibility == 'hidden' and 'invisible="1"' or '')
|
)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _prepare_graph_field(self):
|
def _prepare_graph_field(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
res = ''
|
res = ""
|
||||||
if self.graph_type and self.field_description:
|
if self.graph_type and self.field_description:
|
||||||
res = """<field name="{}" type="{}" />\n""".format(
|
res = """<field name="{}" type="{}" />\n""".format(
|
||||||
self.name, self.graph_type)
|
self.name, self.graph_type
|
||||||
|
)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _prepare_pivot_field(self):
|
def _prepare_pivot_field(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
res = ''
|
res = ""
|
||||||
if self.field_description:
|
if self.field_description:
|
||||||
graph_type_text =\
|
graph_type_text = self.graph_type and 'type="%s"' % (self.graph_type) or ""
|
||||||
self.graph_type and "type=\"%s\"" % (self.graph_type) or ""
|
res = """<field name="{}" {} />\n""".format(self.name, graph_type_text)
|
||||||
res = """<field name="{}" {} />\n""".format(
|
|
||||||
self.name, graph_type_text)
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _prepare_search_field(self):
|
def _prepare_search_field(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
res = ''
|
res = ""
|
||||||
if self.field_description:
|
if self.field_description:
|
||||||
res = """<field name="{}"/>\n""".format(self.name)
|
res = """<field name="{}"/>\n""".format(self.name)
|
||||||
return res
|
return res
|
||||||
|
@ -227,10 +248,12 @@ class BiSQLViewField(models.Model):
|
||||||
@api.multi
|
@api.multi
|
||||||
def _prepare_search_filter_field(self):
|
def _prepare_search_filter_field(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
res = ''
|
res = ""
|
||||||
if self.field_description and self.is_group_by:
|
if self.field_description and self.is_group_by:
|
||||||
res = """<filter name="group_by_%s" string="%s"
|
res = """<filter name="group_by_%s" string="%s"
|
||||||
context="{'group_by':'%s'}"/>\n""" % (
|
context="{'group_by':'%s'}"/>\n""" % (
|
||||||
self.name, self.field_description, self.name
|
self.name,
|
||||||
)
|
self.field_description,
|
||||||
|
self.name,
|
||||||
|
)
|
||||||
return res
|
return res
|
||||||
|
|
|
@ -1,91 +1,94 @@
|
||||||
# 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 odoo.tests.common import SingleTransactionCase, at_install, post_install
|
|
||||||
from odoo.exceptions import AccessError, UserError
|
from odoo.exceptions import AccessError, UserError
|
||||||
|
from odoo.tests.common import SingleTransactionCase, at_install, post_install
|
||||||
|
|
||||||
|
|
||||||
@at_install(False)
|
@at_install(False)
|
||||||
@post_install(True)
|
@post_install(True)
|
||||||
class TestBiSqlViewEditor(SingleTransactionCase):
|
class TestBiSqlViewEditor(SingleTransactionCase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(TestBiSqlViewEditor, cls).setUpClass()
|
super(TestBiSqlViewEditor, cls).setUpClass()
|
||||||
|
|
||||||
cls.res_partner = cls.env['res.partner']
|
cls.res_partner = cls.env["res.partner"]
|
||||||
cls.res_users = cls.env['res.users']
|
cls.res_users = cls.env["res.users"]
|
||||||
cls.bi_sql_view = cls.env['bi.sql.view']
|
cls.bi_sql_view = cls.env["bi.sql.view"]
|
||||||
cls.group_bi_user = cls.env.ref(
|
cls.group_bi_user = cls.env.ref(
|
||||||
'sql_request_abstract.group_sql_request_manager')
|
"sql_request_abstract.group_sql_request_manager"
|
||||||
cls.group_user = cls.env.ref(
|
)
|
||||||
'base.group_user')
|
cls.group_user = cls.env.ref("base.group_user")
|
||||||
cls.view = cls.bi_sql_view.create({
|
cls.view = cls.bi_sql_view.create(
|
||||||
'name': 'Partners View 2',
|
{
|
||||||
'is_materialized': True,
|
"name": "Partners View 2",
|
||||||
'technical_name': 'partners_view_2',
|
"is_materialized": True,
|
||||||
'query': "SELECT name as x_name, street as x_street,"
|
"technical_name": "partners_view_2",
|
||||||
"company_id as x_company_id FROM res_partner "
|
"query": "SELECT name as x_name, street as x_street,"
|
||||||
"ORDER BY name"
|
"company_id as x_company_id FROM res_partner "
|
||||||
})
|
"ORDER BY name",
|
||||||
cls.company = cls.env.ref('base.main_company')
|
}
|
||||||
|
)
|
||||||
|
cls.company = cls.env.ref("base.main_company")
|
||||||
# Create bi user
|
# Create bi user
|
||||||
cls.bi_user = cls._create_user('bi_user', cls.group_bi_user,
|
cls.bi_user = cls._create_user("bi_user", cls.group_bi_user, cls.company)
|
||||||
cls.company)
|
cls.no_bi_user = cls._create_user("no_bi_user", cls.group_user, cls.company)
|
||||||
cls.no_bi_user = cls._create_user('no_bi_user', cls.group_user,
|
|
||||||
cls.company)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _create_user(cls, login, groups, company):
|
def _create_user(cls, login, groups, company):
|
||||||
"""Create a user."""
|
"""Create a user."""
|
||||||
user = cls.res_users.create({
|
user = cls.res_users.create(
|
||||||
'name': login,
|
{
|
||||||
'login': login,
|
"name": login,
|
||||||
'password': 'demo',
|
"login": login,
|
||||||
'email': 'example@yourcompany.com',
|
"password": "demo",
|
||||||
'company_id': company.id,
|
"email": "example@yourcompany.com",
|
||||||
'groups_id': [(6, 0, groups.ids)]
|
"company_id": company.id,
|
||||||
})
|
"groups_id": [(6, 0, groups.ids)],
|
||||||
|
}
|
||||||
|
)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def test_process_view(self):
|
def test_process_view(self):
|
||||||
view = self.view
|
view = self.view
|
||||||
self.assertEqual(view.state, 'draft', 'state not draft')
|
self.assertEqual(view.state, "draft", "state not draft")
|
||||||
view.button_validate_sql_expression()
|
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()
|
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()
|
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()
|
view.button_update_model_access()
|
||||||
self.assertEqual(view.has_group_changed, False,
|
self.assertEqual(view.has_group_changed, False, "has_group_changed not False")
|
||||||
'has_group_changed not False')
|
|
||||||
cron_res = view.cron_id.method_direct_trigger()
|
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):
|
def test_copy(self):
|
||||||
copy_view = self.view.copy()
|
copy_view = self.view.copy()
|
||||||
self.assertEqual(
|
self.assertEqual(copy_view.name, "Partners View 2 (Copy)", "Wrong name")
|
||||||
copy_view.name, 'Partners View 2 (Copy)', 'Wrong name')
|
|
||||||
|
|
||||||
def test_security(self):
|
def test_security(self):
|
||||||
with self.assertRaises(AccessError):
|
with self.assertRaises(AccessError):
|
||||||
self.bi_sql_view.sudo(self.no_bi_user.id).search(
|
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(
|
bi = self.bi_sql_view.sudo(self.bi_user.id).search(
|
||||||
[('name', '=', 'Partners View 2')])
|
[("name", "=", "Partners View 2")]
|
||||||
self.assertEqual(len(bi), 1, 'Bi user should not have access to '
|
)
|
||||||
'bi %s' % self.view.name)
|
self.assertEqual(
|
||||||
|
len(bi), 1, "Bi user should not have access to " "bi %s" % self.view.name
|
||||||
|
)
|
||||||
|
|
||||||
def test_unlink(self):
|
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):
|
with self.assertRaises(UserError):
|
||||||
self.view.unlink()
|
self.view.unlink()
|
||||||
self.view.button_set_draft()
|
self.view.button_set_draft()
|
||||||
self.assertNotEqual(
|
self.assertNotEqual(
|
||||||
self.view.cron_id, False, 'Set to draft materialized view should'
|
self.view.cron_id,
|
||||||
' not unlink cron'
|
False,
|
||||||
|
"Set to draft materialized view should" " not unlink cron",
|
||||||
)
|
)
|
||||||
self.view.unlink()
|
self.view.unlink()
|
||||||
res = self.bi_sql_view.search([('name', '=', 'Partners View 2')])
|
res = self.bi_sql_view.search([("name", "=", "Partners View 2")])
|
||||||
self.assertEqual(len(res), 0, 'View not deleted')
|
self.assertEqual(len(res), 0, "View not deleted")
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<!--
|
<!--
|
||||||
Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
|
Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
|
||||||
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
|
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
|
||||||
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).
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<record id="action_bi_sql_view" model="ir.actions.act_window">
|
<record id="action_bi_sql_view" model="ir.actions.act_window">
|
||||||
<field name="name">SQL Views</field>
|
<field name="name">SQL Views</field>
|
||||||
<field name="type">ir.actions.act_window</field>
|
<field name="type">ir.actions.act_window</field>
|
||||||
|
@ -14,5 +12,4 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
<field name="view_type">form</field>
|
<field name="view_type">form</field>
|
||||||
<field name="view_mode">tree,form</field>
|
<field name="view_mode">tree,form</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<!--
|
<!--
|
||||||
Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
|
Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
|
||||||
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
|
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
|
||||||
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).
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<!-- Menu that will contain all the SQL report generated by this module -->
|
<!-- Menu that will contain all the SQL report generated by this module -->
|
||||||
<menuitem id="menu_bi_sql_editor"
|
<menuitem
|
||||||
name="SQL Reports"
|
id="menu_bi_sql_editor"
|
||||||
parent="base.menu_board_root"
|
name="SQL Reports"
|
||||||
groups="sql_request_abstract.group_sql_request_user"
|
parent="base.menu_board_root"
|
||||||
sequence="0"/>
|
groups="sql_request_abstract.group_sql_request_user"
|
||||||
|
sequence="0"
|
||||||
<menuitem id="menu_bi_sql_view"
|
/>
|
||||||
parent="base.next_id_9"
|
<menuitem
|
||||||
groups="sql_request_abstract.group_sql_request_manager"
|
id="menu_bi_sql_view"
|
||||||
action="action_bi_sql_view"/>
|
parent="base.next_id_9"
|
||||||
|
groups="sql_request_abstract.group_sql_request_manager"
|
||||||
|
action="action_bi_sql_view"
|
||||||
|
/>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
@ -1,132 +1,231 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<!--
|
<!--
|
||||||
Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
|
Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
|
||||||
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
|
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
|
||||||
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).
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<record id="view_bi_sql_view_tree" model="ir.ui.view">
|
<record id="view_bi_sql_view_tree" model="ir.ui.view">
|
||||||
<field name="model">bi.sql.view</field>
|
<field name="model">bi.sql.view</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree decoration-info="state=='draft'" decoration-warning="state in ('sql_valid', 'model_valid')">
|
<tree
|
||||||
<field name="sequence" widget="handle"/>
|
decoration-info="state=='draft'"
|
||||||
<field name="name"/>
|
decoration-warning="state in ('sql_valid', 'model_valid')"
|
||||||
<field name="technical_name"/>
|
>
|
||||||
<field name="size"/>
|
<field name="sequence" widget="handle" />
|
||||||
<field name="state"/>
|
<field name="name" />
|
||||||
|
<field name="technical_name" />
|
||||||
|
<field name="size" />
|
||||||
|
<field name="state" />
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="view_bi_sql_view_form" model="ir.ui.view">
|
<record id="view_bi_sql_view_form" model="ir.ui.view">
|
||||||
<field name="model">bi.sql.view</field>
|
<field name="model">bi.sql.view</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form>
|
<form>
|
||||||
<header>
|
<header>
|
||||||
<button name="button_validate_sql_expression" type="object" states="draft"
|
<button
|
||||||
string="Validate SQL Expression" class="oe_highlight"/>
|
name="button_validate_sql_expression"
|
||||||
<button name="button_set_draft" type="object" states="sql_valid"
|
type="object"
|
||||||
string="Set to Draft" groups="sql_request_abstract.group_sql_request_manager"/>
|
states="draft"
|
||||||
<button name="button_set_draft" type="object" states="model_valid,ui_valid"
|
string="Validate SQL Expression"
|
||||||
string="Set to Draft" groups="sql_request_abstract.group_sql_request_manager"
|
class="oe_highlight"
|
||||||
confirm="Are you sure you want to set to draft this SQL View. It will delete the materialized view, and all the previous mapping realized with the columns"/>
|
/>
|
||||||
<button name="button_preview_sql_expression" type="object" states="draft" string="Preview SQL Expression" />
|
<button
|
||||||
<button name="button_create_sql_view_and_model" type="object" states="sql_valid"
|
name="button_set_draft"
|
||||||
string="Create SQL View, Indexes and Models" class="oe_highlight"
|
type="object"
|
||||||
help="This will try to create an SQL View, based on the SQL request and the according Transient Model and fields, based on settings"/>
|
states="sql_valid"
|
||||||
<button name="button_update_model_access" type="object"
|
string="Set to Draft"
|
||||||
|
groups="sql_request_abstract.group_sql_request_manager"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
name="button_set_draft"
|
||||||
|
type="object"
|
||||||
|
states="model_valid,ui_valid"
|
||||||
|
string="Set to Draft"
|
||||||
|
groups="sql_request_abstract.group_sql_request_manager"
|
||||||
|
confirm="Are you sure you want to set to draft this SQL View. It will delete the materialized view, and all the previous mapping realized with the columns"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
name="button_preview_sql_expression"
|
||||||
|
type="object"
|
||||||
|
states="draft"
|
||||||
|
string="Preview SQL Expression"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
name="button_create_sql_view_and_model"
|
||||||
|
type="object"
|
||||||
|
states="sql_valid"
|
||||||
|
string="Create SQL View, Indexes and Models"
|
||||||
|
class="oe_highlight"
|
||||||
|
help="This will try to create an SQL View, based on the SQL request and the according Transient Model and fields, based on settings"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
name="button_update_model_access"
|
||||||
|
type="object"
|
||||||
attrs="{'invisible': ['|', ('state', 'in', ('draft', 'sql_valid')), ('has_group_changed', '=', False)]}"
|
attrs="{'invisible': ['|', ('state', 'in', ('draft', 'sql_valid')), ('has_group_changed', '=', False)]}"
|
||||||
string="Update Model Access" class="oe_highlight"
|
string="Update Model Access"
|
||||||
help="Update Model Access. Required if you changed groups list after having created the model"/>
|
class="oe_highlight"
|
||||||
<button name="button_create_ui" type="object" states="model_valid" string="Create UI"
|
help="Update Model Access. Required if you changed groups list after having created the model"
|
||||||
class="oe_highlight" help="This will create Odoo View, Action and Menu"/>
|
/>
|
||||||
<button name="button_refresh_materialized_view" type="object" string="Refresh Materialized View"
|
<button
|
||||||
|
name="button_create_ui"
|
||||||
|
type="object"
|
||||||
|
states="model_valid"
|
||||||
|
string="Create UI"
|
||||||
|
class="oe_highlight"
|
||||||
|
help="This will create Odoo View, Action and Menu"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
name="button_refresh_materialized_view"
|
||||||
|
type="object"
|
||||||
|
string="Refresh Materialized View"
|
||||||
attrs="{'invisible': ['|', ('state', 'in', ('draft', 'sql_valid')), ('is_materialized', '=', False)]}"
|
attrs="{'invisible': ['|', ('state', 'in', ('draft', 'sql_valid')), ('is_materialized', '=', False)]}"
|
||||||
help="this will refresh the materialized view"/>
|
help="this will refresh the materialized view"
|
||||||
<button name="button_open_view" type="object" string="Open View" states="ui_valid" class="oe_highlight" />
|
/>
|
||||||
|
<button
|
||||||
|
name="button_open_view"
|
||||||
|
type="object"
|
||||||
|
string="Open View"
|
||||||
|
states="ui_valid"
|
||||||
|
class="oe_highlight"
|
||||||
|
/>
|
||||||
<field name="state" widget="statusbar" />
|
<field name="state" widget="statusbar" />
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<h1>
|
<h1>
|
||||||
<field name="name" attrs="{'readonly': [('state','!=','draft')]}" colspan="4"/>
|
<field
|
||||||
|
name="name"
|
||||||
|
attrs="{'readonly': [('state','!=','draft')]}"
|
||||||
|
colspan="4"
|
||||||
|
/>
|
||||||
</h1>
|
</h1>
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field name="technical_name" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
<field
|
||||||
<field name="view_name"/>
|
name="technical_name"
|
||||||
<field name="view_order"/>
|
attrs="{'readonly': [('state', '!=', 'draft')]}"
|
||||||
<field name="is_materialized"/>
|
/>
|
||||||
<field name="size"
|
<field name="view_name" />
|
||||||
attrs="{'invisible': ['|', ('state', '=', 'draft'), ('is_materialized', '=', False)]}"/>
|
<field name="view_order" />
|
||||||
<field name="cron_id"
|
<field name="is_materialized" />
|
||||||
attrs="{'invisible': ['|', ('state', 'in', ('draft', 'sql_valid')), ('is_materialized', '=', False)]}"/>
|
<field
|
||||||
|
name="size"
|
||||||
|
attrs="{'invisible': ['|', ('state', '=', 'draft'), ('is_materialized', '=', False)]}"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="cron_id"
|
||||||
|
attrs="{'invisible': ['|', ('state', 'in', ('draft', 'sql_valid')), ('is_materialized', '=', False)]}"
|
||||||
|
/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
<notebook>
|
<notebook>
|
||||||
<page string="SQL Query">
|
<page string="SQL Query">
|
||||||
<field name="query" nolabel="1" colspan="4" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
<field
|
||||||
|
name="query"
|
||||||
|
nolabel="1"
|
||||||
|
colspan="4"
|
||||||
|
attrs="{'readonly': [('state', '!=', 'draft')]}"
|
||||||
|
/>
|
||||||
</page>
|
</page>
|
||||||
<page string="SQL Fields" attrs="{'invisible': [('state', '=', 'draft')]}">
|
<page
|
||||||
<field name="bi_sql_view_field_ids" nolabel="1" colspan="4" attrs="{'readonly': [('state', '!=', 'sql_valid')]}">
|
string="SQL Fields"
|
||||||
<tree editable="bottom" decoration-info="field_description==False">
|
attrs="{'invisible': [('state', '=', 'draft')]}"
|
||||||
<field name="sequence"/>
|
>
|
||||||
<field name="name"/>
|
<field
|
||||||
<field name="sql_type"/>
|
name="bi_sql_view_field_ids"
|
||||||
<field name="field_description"/>
|
nolabel="1"
|
||||||
<field name="ttype" attrs="{
|
colspan="4"
|
||||||
'required': [('field_description', '!=', False)]}"/>
|
attrs="{'readonly': [('state', '!=', 'sql_valid')]}"
|
||||||
<field name="many2one_model_id" attrs="{
|
>
|
||||||
|
<tree
|
||||||
|
editable="bottom"
|
||||||
|
decoration-info="field_description==False"
|
||||||
|
>
|
||||||
|
<field name="sequence" />
|
||||||
|
<field name="name" />
|
||||||
|
<field name="sql_type" />
|
||||||
|
<field name="field_description" />
|
||||||
|
<field
|
||||||
|
name="ttype"
|
||||||
|
attrs="{
|
||||||
|
'required': [('field_description', '!=', False)]}"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="many2one_model_id"
|
||||||
|
attrs="{
|
||||||
'invisible': [('ttype', '!=', 'many2one')],
|
'invisible': [('ttype', '!=', 'many2one')],
|
||||||
'required': [
|
'required': [
|
||||||
('field_description', '!=', False),
|
('field_description', '!=', False),
|
||||||
('ttype', '=', 'many2one')]}"/>
|
('ttype', '=', 'many2one')]}"
|
||||||
<field name="selection" attrs="{
|
/>
|
||||||
|
<field
|
||||||
|
name="selection"
|
||||||
|
attrs="{
|
||||||
'invisible': [('ttype', '!=', 'selection')],
|
'invisible': [('ttype', '!=', 'selection')],
|
||||||
'required': [
|
'required': [
|
||||||
('field_description', '!=', False),
|
('field_description', '!=', False),
|
||||||
('ttype', '=', 'selection')]}"/>
|
('ttype', '=', 'selection')]}"
|
||||||
<field name="is_index" attrs="{'invisible': [('field_description', '=', False)]}"/>
|
/>
|
||||||
<field name="is_group_by" attrs="{'invisible': [('field_description', '=', False)]}"/>
|
<field
|
||||||
<field name="graph_type" attrs="{'invisible': [('field_description', '=', False)]}"/>
|
name="is_index"
|
||||||
<field name="tree_visibility" attrs="{'invisible': [('field_description', '=', False)]}"/>
|
attrs="{'invisible': [('field_description', '=', False)]}"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="is_group_by"
|
||||||
|
attrs="{'invisible': [('field_description', '=', False)]}"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="graph_type"
|
||||||
|
attrs="{'invisible': [('field_description', '=', False)]}"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="tree_visibility"
|
||||||
|
attrs="{'invisible': [('field_description', '=', False)]}"
|
||||||
|
/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</page>
|
</page>
|
||||||
<page string="Security">
|
<page string="Security">
|
||||||
<group string="Rule Definition">
|
<group string="Rule Definition">
|
||||||
<field name="domain_force" nolabel="1" colspan="4"/>
|
<field name="domain_force" nolabel="1" colspan="4" />
|
||||||
</group>
|
</group>
|
||||||
<group string="Allowed Groups">
|
<group string="Allowed Groups">
|
||||||
<field name="group_ids" nolabel="1" colspan="4"/>
|
<field name="group_ids" nolabel="1" colspan="4" />
|
||||||
<field name="has_group_changed" invisible="1"/>
|
<field name="has_group_changed" invisible="1" />
|
||||||
</group>
|
</group>
|
||||||
</page>
|
</page>
|
||||||
<page string="Action Settings">
|
<page string="Action Settings">
|
||||||
<group string="Computed Context">
|
<group string="Computed Context">
|
||||||
<field name="computed_action_context" nolabel="1" colspan="4"/>
|
<field
|
||||||
|
name="computed_action_context"
|
||||||
|
nolabel="1"
|
||||||
|
colspan="4"
|
||||||
|
/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Custom Context">
|
<group string="Custom Context">
|
||||||
<field name="action_context" nolabel="1" colspan="4"/>
|
<field name="action_context" nolabel="1" colspan="4" />
|
||||||
</group>
|
</group>
|
||||||
</page>
|
</page>
|
||||||
<page string="Extras Information">
|
<page string="Extras Information">
|
||||||
<group>
|
<group>
|
||||||
<group string="Model">
|
<group string="Model">
|
||||||
<field name="model_name" />
|
<field name="model_name" />
|
||||||
<field name="model_id" attrs="{'invisible': [('state', '=', 'draft')]}"/>
|
<field
|
||||||
|
name="model_id"
|
||||||
|
attrs="{'invisible': [('state', '=', 'draft')]}"
|
||||||
|
/>
|
||||||
</group>
|
</group>
|
||||||
<group string="User Interface">
|
<group string="User Interface">
|
||||||
<field name="tree_view_id"/>
|
<field name="tree_view_id" />
|
||||||
<field name="graph_view_id"/>
|
<field name="graph_view_id" />
|
||||||
<field name="pivot_view_id"/>
|
<field name="pivot_view_id" />
|
||||||
<field name="search_view_id"/>
|
<field name="search_view_id" />
|
||||||
<field name="action_id"/>
|
<field name="action_id" />
|
||||||
<field name="menu_id"/>
|
<field name="menu_id" />
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</page>
|
</page>
|
||||||
|
@ -135,5 +234,4 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
Loading…
Reference in New Issue