From c356777678ccdd59d4b250ddc2b9903727db59df Mon Sep 17 00:00:00 2001
From: thien
Date: Wed, 2 Oct 2024 15:05:05 +0700
Subject: [PATCH] [MIG] bi_sql_editor: Migration to 18.0
---
bi_sql_editor/README.rst | 28 ++++--
bi_sql_editor/__manifest__.py | 2 +-
bi_sql_editor/models/bi_sql_view.py | 104 ++++++++++----------
bi_sql_editor/models/bi_sql_view_field.py | 9 +-
bi_sql_editor/readme/CONTRIBUTORS.md | 2 +
bi_sql_editor/readme/CREDITS.md | 1 +
bi_sql_editor/static/description/index.html | 33 ++++---
bi_sql_editor/tests/test_bi_sql_view.py | 8 +-
bi_sql_editor/views/view_bi_sql_view.xml | 76 +++++++-------
9 files changed, 139 insertions(+), 124 deletions(-)
create mode 100644 bi_sql_editor/readme/CREDITS.md
diff --git a/bi_sql_editor/README.rst b/bi_sql_editor/README.rst
index aab31bac0a..c4e575dd0b 100644
--- a/bi_sql_editor/README.rst
+++ b/bi_sql_editor/README.rst
@@ -17,13 +17,13 @@ BI SQL Editor
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Freporting--engine-lightgray.png?logo=github
- :target: https://github.com/OCA/reporting-engine/tree/17.0/bi_sql_editor
+ :target: https://github.com/OCA/reporting-engine/tree/18.0/bi_sql_editor
:alt: OCA/reporting-engine
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
- :target: https://translation.odoo-community.org/projects/reporting-engine-17-0/reporting-engine-17-0-bi_sql_editor
+ :target: https://translation.odoo-community.org/projects/reporting-engine-18-0/reporting-engine-18-0-bi_sql_editor
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
- :target: https://runboat.odoo-community.org/builds?repo=OCA/reporting-engine&target_branch=17.0
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/reporting-engine&target_branch=18.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
@@ -126,10 +126,10 @@ to make reporting depending on the current companies of the user.
- Finally, click on 'Create UI', to create new menu, action, graph view
and search view.
-.. |image1| image:: https://raw.githubusercontent.com/OCA/reporting-engine/17.0/bi_sql_editor/static/description/01_sql_request.png
-.. |image2| image:: https://raw.githubusercontent.com/OCA/reporting-engine/17.0/bi_sql_editor/static/description/02_security_access.png
-.. |image3| image:: https://raw.githubusercontent.com/OCA/reporting-engine/17.0/bi_sql_editor/static/description/03_field_mapping.png
-.. |image4| image:: https://raw.githubusercontent.com/OCA/reporting-engine/17.0/bi_sql_editor/static/description/04_materialized_view_setting.png
+.. |image1| image:: https://raw.githubusercontent.com/OCA/reporting-engine/18.0/bi_sql_editor/static/description/01_sql_request.png
+.. |image2| image:: https://raw.githubusercontent.com/OCA/reporting-engine/18.0/bi_sql_editor/static/description/02_security_access.png
+.. |image3| image:: https://raw.githubusercontent.com/OCA/reporting-engine/18.0/bi_sql_editor/static/description/03_field_mapping.png
+.. |image4| image:: https://raw.githubusercontent.com/OCA/reporting-engine/18.0/bi_sql_editor/static/description/04_materialized_view_setting.png
Usage
=====
@@ -145,7 +145,7 @@ To use this module, you need to:
- You can switch to 'Graph' or 'tree' views as any report.
-.. |usage-image1| image:: https://raw.githubusercontent.com/OCA/reporting-engine/17.0/bi_sql_editor/static/description/05_reporting_pivot.png
+.. |usage-image1| image:: https://raw.githubusercontent.com/OCA/reporting-engine/18.0/bi_sql_editor/static/description/05_reporting_pivot.png
Bug Tracker
===========
@@ -153,7 +153,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues `_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
-`feedback `_.
+`feedback `_.
Do not contact contributors directly about support or help with technical issues.
@@ -176,6 +176,8 @@ Contributors
- Guillem Casassas guillem.casassas@forgeflow.com
+- Thien Vo thienvh@trobz.com
+
- This module is highly inspired by the work of
- Onestein: (http://www.onestein.nl/) Module:
@@ -189,6 +191,12 @@ Contributors
grap/odoo-addons-misc/pos_sale_reporting link:
https://github.com/grap/odoo-addons-misc/tree/7.0/pos_sale_reporting
+Other credits
+-------------
+
+The migration of this module from 17.0 to 18.0 was financially supported
+by Camptocamp.
+
Maintainers
-----------
@@ -210,6 +218,6 @@ Current `maintainer `__:
|maintainer-legalsylvain|
-This module is part of the `OCA/reporting-engine `_ project on GitHub.
+This module is part of the `OCA/reporting-engine `_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/bi_sql_editor/__manifest__.py b/bi_sql_editor/__manifest__.py
index 7158a635b6..96f342c8d9 100644
--- a/bi_sql_editor/__manifest__.py
+++ b/bi_sql_editor/__manifest__.py
@@ -5,7 +5,7 @@
{
"name": "BI SQL Editor",
"summary": "BI Views builder, based on Materialized or Normal SQL Views",
- "version": "17.0.1.1.0",
+ "version": "18.0.1.0.0",
"license": "AGPL-3",
"category": "Reporting",
"author": "GRAP,Odoo Community Association (OCA)",
diff --git a/bi_sql_editor/models/bi_sql_view.py b/bi_sql_editor/models/bi_sql_view.py
index b57f6597be..f608e6e783 100644
--- a/bi_sql_editor/models/bi_sql_view.py
+++ b/bi_sql_editor/models/bi_sql_view.py
@@ -6,10 +6,11 @@
from datetime import datetime, timedelta
from psycopg2 import ProgrammingError
+from psycopg2.sql import SQL, Identifier
from odoo import SUPERUSER_ID, _, api, fields, models
from odoo.exceptions import UserError, ValidationError
-from odoo.tools import sql, table_columns
+from odoo.tools import sql
from odoo.tools.safe_eval import safe_eval
_logger = logging.getLogger(__name__)
@@ -44,14 +45,12 @@ class BiSQLView(models.Model):
view_name = fields.Char(
compute="_compute_view_name",
- readonly=True,
store=True,
help="Full name of the SQL view",
)
model_name = fields.Char(
compute="_compute_model_name",
- readonly=True,
store=True,
help="Full Qualified Name of the transient model that will" " be created.",
)
@@ -65,7 +64,6 @@ class BiSQLView(models.Model):
size = fields.Char(
string="Database Size",
- readonly=True,
help="Size of the materialized view and its indexes",
)
@@ -73,8 +71,8 @@ class BiSQLView(models.Model):
view_order = fields.Char(
required=True,
- default="pivot,graph,tree",
- help="Comma-separated text. Possible values:" ' "graph", "pivot" or "tree"',
+ default="pivot,graph,list",
+ help="Comma-separated text. Possible values:" ' "graph", "pivot" or "list"',
)
query = fields.Text(
@@ -111,9 +109,7 @@ class BiSQLView(models.Model):
inverse_name="bi_sql_view_id",
)
- model_id = fields.Many2one(
- string="Odoo Model", comodel_name="ir.model", readonly=True
- )
+ model_id = fields.Many2one(string="Odoo Model", comodel_name="ir.model")
# UI related fields
# 1. Editable fields, which can be set by the user (optional) before
# creating the UI elements
@@ -133,39 +129,30 @@ def _default_parent_menu_id(self):
)
# 2. Readonly fields, non editable by the user
- tree_view_id = fields.Many2one(
- string="Odoo Tree View", comodel_name="ir.ui.view", readonly=True
- )
+ tree_view_id = fields.Many2one(string="Odoo List View", comodel_name="ir.ui.view")
- graph_view_id = fields.Many2one(
- string="Odoo Graph View", comodel_name="ir.ui.view", readonly=True
- )
+ graph_view_id = fields.Many2one(string="Odoo Graph View", comodel_name="ir.ui.view")
- pivot_view_id = fields.Many2one(
- string="Odoo Pivot View", comodel_name="ir.ui.view", readonly=True
- )
+ pivot_view_id = fields.Many2one(string="Odoo Pivot View", comodel_name="ir.ui.view")
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"
)
action_id = fields.Many2one(
- string="Odoo Action", comodel_name="ir.actions.act_window", readonly=True
+ string="Odoo Action", comodel_name="ir.actions.act_window"
)
- menu_id = fields.Many2one(
- string="Odoo Menu", comodel_name="ir.ui.menu", readonly=True
- )
+ menu_id = fields.Many2one(string="Odoo Menu", comodel_name="ir.ui.menu")
cron_id = fields.Many2one(
string="Odoo Cron",
comodel_name="ir.cron",
- readonly=True,
help="Cron Task that will refresh the materialized view",
ondelete="cascade",
)
- rule_id = fields.Many2one(string="Odoo Rule", comodel_name="ir.rule", readonly=True)
+ rule_id = fields.Many2one(string="Odoo Rule", comodel_name="ir.rule")
sequence = fields.Integer(string="sequence")
@@ -183,9 +170,9 @@ 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"):
+ if vtype not in ("graph", "pivot", "list"):
raise UserError(
- _("Only graph, pivot or tree views are supported")
+ _("Only graph, pivot or list views are supported")
)
# Compute Section
@@ -244,15 +231,15 @@ def write(self, vals):
rec.menu_id.sequence = rec.sequence
return res
- def unlink(self):
+ @api.ondelete(at_uninstall=False)
+ def _check_unlink_constraints(self):
if any(view.state not in ("draft", "sql_valid") for view in self):
raise UserError(
_(
- "You can only unlink draft views."
+ "You can only unlink draft views. "
"If you want to delete them, first set them to draft."
)
)
- return super().unlink()
def copy(self, default=None):
self.ensure_one()
@@ -395,8 +382,7 @@ def _prepare_cron(self):
.search([("model", "=", self._name)], limit=1)
.id,
"state": "code",
- "code": "model._refresh_materialized_view_cron(%s)" % self.ids,
- "numbercall": -1,
+ "code": f"model._refresh_materialized_view_cron({self.ids})",
"interval_number": 1,
"interval_type": "days",
"nextcall": now + timedelta(days=1),
@@ -416,11 +402,11 @@ def _prepare_tree_view(self):
self.ensure_one()
return {
"name": self.name,
- "type": "tree",
+ "type": "list",
"model": self.model_id.model,
"arch": """"""
- """{}"""
- """""".format(
+ """{}"""
+ """
""".format(
"".join([x._prepare_tree_field() for x in self.bi_sql_view_field_ids])
),
}
@@ -477,7 +463,7 @@ def _prepare_action(self):
self.ensure_one()
view_mode = self.view_order
first_view = view_mode.split(",")[0]
- if first_view == "tree":
+ if first_view == "list":
view_id = self.tree_view_id.id
elif first_view == "pivot":
view_id = self.pivot_view_id.id
@@ -510,19 +496,22 @@ def _prepare_menu(self):
return {
"name": self.name,
"parent_id": self.parent_menu_id.id,
- "action": "ir.actions.act_window,%s" % self.action_id.id,
+ "action": f"ir.actions.act_window,{self.action_id.id}",
"sequence": self.sequence,
}
# Custom Section
def _log_execute(self, req):
- _logger.info("Executing SQL Request %s ..." % req)
+ _logger.info(f"Executing SQL Request {req} ...")
self.env.cr.execute(req)
def _drop_view(self):
for sql_view in self:
self._log_execute(
- f"DROP {sql_view.materialized_text} VIEW IF EXISTS {sql_view.view_name}"
+ SQL("DROP {materialized_text} VIEW IF EXISTS {view_name}").format(
+ materialized_text=SQL(sql_view.materialized_text),
+ view_name=Identifier(sql_view.view_name),
+ )
)
sql_view.size = False
@@ -551,8 +540,11 @@ def _create_index(self):
lambda x: x.is_index is True
):
self._log_execute(
- f"CREATE INDEX {sql_field.index_name} ON {sql_view.view_name} "
- f"({sql_field.name});"
+ SQL("CREATE INDEX {index_name} ON {view_name} ({name});").format(
+ index_name=SQL(sql_field.index_name),
+ view_name=Identifier(sql_view.view_name),
+ name=Identifier(sql_field.name),
+ )
)
def _create_model_and_fields(self):
@@ -562,7 +554,7 @@ def _create_model_and_fields(self):
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
+ req = SQL("DROP TABLE {}").format(Identifier(sql_view.view_name))
self._log_execute(req)
def _create_model_access(self):
@@ -585,28 +577,29 @@ def _drop_model_and_fields(self):
def _hook_executed_request(self):
self.ensure_one()
- req = (
+ req = SQL(
"""
SELECT attnum,
attname AS column,
format_type(atttypid, atttypmod) AS type
FROM pg_attribute
- WHERE attrelid = '%s'::regclass
+ WHERE attrelid = '{view_name}'::regclass
AND NOT attisdropped
AND attnum > 0
ORDER BY attnum;"""
- % self.view_name
- )
+ ).format(view_name=Identifier(self.view_name))
self._log_execute(req)
return self.env.cr.fetchall()
def _prepare_request_check_execution(self):
self.ensure_one()
- return f"CREATE VIEW {self.view_name} AS ({self.query});"
+ return SQL("CREATE VIEW {view_name} AS ({query});").format(
+ view_name=Identifier(self.view_name), query=SQL(self.query)
+ )
def _prepare_request_for_execution(self):
self.ensure_one()
- query = (
+ query = SQL(
"""
SELECT
CAST(row_number() OVER () as integer) AS id,
@@ -616,11 +609,14 @@ def _prepare_request_for_execution(self):
CAST(Null as integer) as write_uid,
my_query.*
FROM
- (%s) as my_query
+ ({}) as my_query
"""
- % self.query
+ ).format(SQL(self.query))
+ return SQL("CREATE {materialized_text} VIEW {view_name} AS ({query});").format(
+ materialized_text=SQL(self.materialized_text),
+ view_name=Identifier(self.view_name),
+ query=query,
)
- return f"CREATE {self.materialized_text} VIEW {self.view_name} AS ({query});"
def _check_execution(self):
"""Ensure that the query is valid, trying to execute it.
@@ -689,8 +685,8 @@ def _refresh_materialized_view(self):
def _refresh_size(self):
for sql_view in self:
- req = "SELECT pg_size_pretty(pg_total_relation_size('%s'));" % (
- sql_view.view_name
+ req = SQL("SELECT pg_size_pretty(pg_total_relation_size('{}'));").format(
+ Identifier(sql_view.view_name)
)
self._log_execute(req)
sql_view.size = self.env.cr.fetchone()[0]
@@ -700,7 +696,7 @@ def check_manual_fields(self, model):
# early on install / startup - particularly problematic during upgrade
if model._name.startswith(
self._model_prefix
- ) and "group_operator" in table_columns(self.env.cr, "bi_sql_view_field"):
+ ) and "group_operator" in sql.table_columns(self.env.cr, "bi_sql_view_field"):
# Use SQL instead of ORM, as ORM might not be fully initialised -
# we have no control over the order that fields are defined!
# We are not concerned about user security rules.
diff --git a/bi_sql_editor/models/bi_sql_view_field.py b/bi_sql_editor/models/bi_sql_view_field.py
index f098f20c28..f8bfe2b92b 100644
--- a/bi_sql_editor/models/bi_sql_view_field.py
+++ b/bi_sql_editor/models/bi_sql_view_field.py
@@ -149,9 +149,8 @@ def _check_index_materialized(self):
# Compute Section
def _compute_index_name(self):
for sql_field in self:
- sql_field.index_name = "{}_{}".format(
- sql_field.bi_sql_view_id.view_name,
- sql_field.name,
+ sql_field.index_name = (
+ f"{sql_field.bi_sql_view_id.view_name}_{sql_field.name}"
)
# Overload Section
@@ -209,8 +208,8 @@ def _model_mapping(self):
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"].sudo().search([("ttype", "=", "many2one")])
)
res = {}
keys_to_pop = []
diff --git a/bi_sql_editor/readme/CONTRIBUTORS.md b/bi_sql_editor/readme/CONTRIBUTORS.md
index c787a3c70e..2f6bce1bbe 100644
--- a/bi_sql_editor/readme/CONTRIBUTORS.md
+++ b/bi_sql_editor/readme/CONTRIBUTORS.md
@@ -6,6 +6,8 @@
- Guillem Casassas
+- Thien Vo
+
- This module is highly inspired by the work of
- Onestein: () Module:
OCA/server-tools/bi_view_editor. Link:
diff --git a/bi_sql_editor/readme/CREDITS.md b/bi_sql_editor/readme/CREDITS.md
new file mode 100644
index 0000000000..83b3ec91f7
--- /dev/null
+++ b/bi_sql_editor/readme/CREDITS.md
@@ -0,0 +1 @@
+The migration of this module from 17.0 to 18.0 was financially supported by Camptocamp.
diff --git a/bi_sql_editor/static/description/index.html b/bi_sql_editor/static/description/index.html
index d4ee7a4f7d..f5b63b420d 100644
--- a/bi_sql_editor/static/description/index.html
+++ b/bi_sql_editor/static/description/index.html
@@ -8,10 +8,11 @@
/*
:Author: David Goodger (goodger@python.org)
-:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
+:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
+Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
@@ -274,7 +275,7 @@
margin-left: 2em ;
margin-right: 2em }
-pre.code .ln { color: grey; } /* line numbers */
+pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
@@ -300,7 +301,7 @@
span.pre {
white-space: pre }
-span.problematic {
+span.problematic, pre.problematic {
color: red }
span.section-subtitle {
@@ -368,7 +369,7 @@ BI SQL Editor
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:1acb226c184032febd7db6e48ee618c7e5a6899c4685aa78b5b2e7578ebd9ce9
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-
+
This module extends the functionality of reporting, to support creation
of extra custom reports. It allows user to write a custom SQL request.
(Generally, admin users)
@@ -421,10 +422,10 @@
Go to Dashboard / Configuration / SQL Views
tip your SQL request
-
+
Select the group(s) that could have access to the view
-
+
Optionnaly, you can add a domain.
@@ -438,7 +439,7 @@
view, and propose field mapping. For each field, you can decide to
create an index and set if it will be displayed on the pivot graph as
a column, a row or a measure.
-
+
Click on the button ‘Create SQL elements’. (this step could take a
while, if view is materialized)
@@ -451,7 +452,7 @@
the size of view (and the indexes is displayed)
-
+
Before applying the final step, you will need to add a specific
Parent Menu to use when creating the UI Menu for the report. By
@@ -473,7 +474,7 @@
-
+
- You can switch to ‘Graph’ or ‘tree’ views as any report.
@@ -483,7 +484,7 @@
Bugs are tracked on GitHub Issues.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
-feedback.
+feedback.
Do not contact contributors directly about support or help with technical issues.
+
+
Other credits
+
The migration of this module from 17.0 to 18.0 was financially supported
+by Camptocamp.
+
Maintainers
This module is maintained by the OCA.
-
+
+
+
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
Current maintainer:
-
This module is part of the OCA/reporting-engine project on GitHub.
+
This module is part of the OCA/reporting-engine project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/bi_sql_editor/tests/test_bi_sql_view.py b/bi_sql_editor/tests/test_bi_sql_view.py
index d7999ebfa0..125c8c930e 100644
--- a/bi_sql_editor/tests/test_bi_sql_view.py
+++ b/bi_sql_editor/tests/test_bi_sql_view.py
@@ -1,6 +1,8 @@
# Copyright 2017 Onestein ()
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+import logging
+
from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.tests import tagged
from odoo.tests.common import SingleTransactionCase
@@ -73,7 +75,7 @@ def test_security(self):
[("name", "=", self.view.name)]
)
self.assertEqual(
- len(bi), 1, "Bi Manager should have access to bi %s" % self.view.name
+ len(bi), 1, f"Bi Manager should have access to bi {self.view.name}"
)
def test_unlink(self):
@@ -90,7 +92,9 @@ def test_unlink(self):
self.assertEqual(copy_view.state, "ui_valid")
with self.assertRaises(UserError):
copy_view.unlink()
- copy_view.button_set_draft()
+ logger_name = "odoo.addons.base.models.ir_model"
+ with self.assertLogs(logger_name, level=logging.WARNING):
+ copy_view.button_set_draft()
self.assertNotEqual(
copy_view.cron_id,
False,
diff --git a/bi_sql_editor/views/view_bi_sql_view.xml b/bi_sql_editor/views/view_bi_sql_view.xml
index 4590e19a14..3c2fa79c2c 100644
--- a/bi_sql_editor/views/view_bi_sql_view.xml
+++ b/bi_sql_editor/views/view_bi_sql_view.xml
@@ -5,7 +5,6 @@ Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
-
bi.sql.view
-
+
@@ -90,7 +89,7 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-
+
-
+
@@ -160,7 +161,7 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
readonly="state == 'ui_valid'"
/>
-
+
@@ -185,16 +186,12 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
@@ -246,7 +243,7 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
SQL Views
ir.actions.act_window
bi.sql.view
- tree,form
+ list,form
-