mirror of
https://github.com/OCA/reporting-engine.git
synced 2025-02-16 16:30:38 +02:00
@@ -20,6 +20,7 @@ ORDER BY unexisting_field
|
||||
<record id="partner_sql_view" model="bi.sql.view">
|
||||
<field name="name">Partners View</field>
|
||||
<field name="technical_name">partners_view</field>
|
||||
<field name="is_materialized" eval="True" />
|
||||
<field
|
||||
name="query"
|
||||
><![CDATA[
|
||||
@@ -49,19 +50,4 @@ FROM ir_module_module
|
||||
]]>
|
||||
</field>
|
||||
</record>
|
||||
<function
|
||||
model="bi.sql.view"
|
||||
name="button_validate_sql_expression"
|
||||
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>
|
||||
|
||||
@@ -8,7 +8,7 @@ from datetime import datetime, timedelta
|
||||
from psycopg2 import ProgrammingError
|
||||
|
||||
from odoo import SUPERUSER_ID, _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tools import sql, table_columns
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
@@ -169,12 +169,6 @@ class BiSQLView(models.Model):
|
||||
|
||||
sequence = fields.Integer(string="sequence")
|
||||
|
||||
option_context_field = fields.Boolean(
|
||||
string="Use Context Field",
|
||||
help="Check this box if you want to add a context column in the field list view."
|
||||
" Custom Context will be inserted in the created views.",
|
||||
)
|
||||
|
||||
# Constrains Section
|
||||
@api.constrains("is_materialized")
|
||||
def _check_index_materialized(self):
|
||||
@@ -269,17 +263,24 @@ class BiSQLView(models.Model):
|
||||
def copy(self, default=None):
|
||||
self.ensure_one()
|
||||
default = dict(default or {})
|
||||
default.update(
|
||||
{
|
||||
"name": _("%s (Copy)") % self.name,
|
||||
"technical_name": "%s_copy" % self.technical_name,
|
||||
}
|
||||
)
|
||||
if "name" not in default:
|
||||
default["name"] = _("%s (Copy)") % self.name
|
||||
if "technical_name" not in default:
|
||||
default["technical_name"] = f"{self.technical_name}_copy"
|
||||
return super().copy(default=default)
|
||||
|
||||
# Action Section
|
||||
def button_create_sql_view_and_model(self):
|
||||
for sql_view in self.filtered(lambda x: x.state == "sql_valid"):
|
||||
# Check if many2one fields are correctly
|
||||
bad_fields = sql_view.bi_sql_view_field_ids.filtered(
|
||||
lambda x: x.ttype == "many2one" and not x.many2one_model_id.id
|
||||
)
|
||||
if bad_fields:
|
||||
raise ValidationError(
|
||||
_("Please set related models on the following fields %s")
|
||||
% ",".join(bad_fields.mapped("name"))
|
||||
)
|
||||
# Create ORM and access
|
||||
sql_view._create_model_and_fields()
|
||||
sql_view._create_model_access()
|
||||
@@ -297,28 +298,33 @@ class BiSQLView(models.Model):
|
||||
sql_view.cron_id.active = True
|
||||
sql_view.state = "model_valid"
|
||||
|
||||
def button_reset_to_model_valid(self):
|
||||
views = self.filtered(lambda x: x.state == "ui_valid")
|
||||
views.mapped("tree_view_id").unlink()
|
||||
views.mapped("graph_view_id").unlink()
|
||||
views.mapped("pivot_view_id").unlink()
|
||||
views.mapped("search_view_id").unlink()
|
||||
views.mapped("action_id").unlink()
|
||||
views.mapped("menu_id").unlink()
|
||||
return views.write({"state": "model_valid"})
|
||||
|
||||
def button_reset_to_sql_valid(self):
|
||||
self.button_reset_to_model_valid()
|
||||
views = self.filtered(lambda x: x.state == "model_valid")
|
||||
for sql_view in views:
|
||||
# Drop SQL View (and indexes by cascade)
|
||||
if sql_view.is_materialized:
|
||||
sql_view._drop_view()
|
||||
if sql_view.cron_id:
|
||||
sql_view.cron_id.active = False
|
||||
# Drop ORM
|
||||
sql_view._drop_model_and_fields()
|
||||
return views.write({"state": "sql_valid"})
|
||||
|
||||
def button_set_draft(self):
|
||||
for sql_view in self.filtered(lambda x: x.state != "draft"):
|
||||
sql_view.menu_id.unlink()
|
||||
sql_view.action_id.unlink()
|
||||
sql_view.tree_view_id.unlink()
|
||||
sql_view.graph_view_id.unlink()
|
||||
sql_view.pivot_view_id.unlink()
|
||||
sql_view.search_view_id.unlink()
|
||||
|
||||
if sql_view.state in ("model_valid", "ui_valid"):
|
||||
# Drop SQL View (and indexes by cascade)
|
||||
if sql_view.is_materialized:
|
||||
sql_view._drop_view()
|
||||
|
||||
if sql_view.cron_id:
|
||||
sql_view.cron_id.active = False
|
||||
|
||||
# Drop ORM
|
||||
sql_view._drop_model_and_fields()
|
||||
|
||||
super(BiSQLView, sql_view).button_set_draft()
|
||||
return True
|
||||
self.button_reset_to_model_valid()
|
||||
self.button_reset_to_sql_valid()
|
||||
return super().button_set_draft()
|
||||
|
||||
def button_create_ui(self):
|
||||
self.tree_view_id = self.env["ir.ui.view"].create(self._prepare_tree_view()).id
|
||||
|
||||
@@ -70,31 +70,40 @@ class BiSQLViewField(models.Model):
|
||||
string="SQL View", comodel_name="bi.sql.view", ondelete="cascade"
|
||||
)
|
||||
|
||||
state = fields.Selection(related="bi_sql_view_id.state", store=True)
|
||||
|
||||
is_index = fields.Boolean(
|
||||
help="Check this box if you want to create"
|
||||
" an index on that field. This is recommended for searchable and"
|
||||
" groupable fields, to reduce duration",
|
||||
states={"model_valid": [("readonly", True)], "ui_valid": [("readonly", True)]},
|
||||
)
|
||||
|
||||
is_group_by = fields.Boolean(
|
||||
string="Is Group by",
|
||||
help="Check this box if you want to create"
|
||||
" a 'group by' option in the search view",
|
||||
states={"ui_valid": [("readonly", True)]},
|
||||
)
|
||||
|
||||
index_name = fields.Char(compute="_compute_index_name")
|
||||
|
||||
graph_type = fields.Selection(selection=_GRAPH_TYPE_SELECTION)
|
||||
graph_type = fields.Selection(
|
||||
selection=_GRAPH_TYPE_SELECTION,
|
||||
states={"ui_valid": [("readonly", True)]},
|
||||
)
|
||||
|
||||
tree_visibility = fields.Selection(
|
||||
selection=_TREE_VISIBILITY_SELECTION,
|
||||
default="available",
|
||||
required=True,
|
||||
states={"ui_valid": [("readonly", True)]},
|
||||
)
|
||||
|
||||
field_description = fields.Char(
|
||||
help="This will be used as the name of the Odoo field, displayed for users",
|
||||
required=True,
|
||||
states={"model_valid": [("readonly", True)], "ui_valid": [("readonly", True)]},
|
||||
)
|
||||
|
||||
ttype = fields.Selection(
|
||||
@@ -104,6 +113,7 @@ class BiSQLViewField(models.Model):
|
||||
" Odoo field that will be created. Keep empty if you don't want to"
|
||||
" create a new field. If empty, this field will not be displayed"
|
||||
" neither available for search or group by function",
|
||||
states={"model_valid": [("readonly", True)], "ui_valid": [("readonly", True)]},
|
||||
)
|
||||
|
||||
selection = fields.Text(
|
||||
@@ -113,24 +123,28 @@ class BiSQLViewField(models.Model):
|
||||
" List of options, specified as a Python expression defining a list of"
|
||||
" (key, label) pairs. For example:"
|
||||
" [('blue','Blue'), ('yellow','Yellow')]",
|
||||
states={"model_valid": [("readonly", True)], "ui_valid": [("readonly", True)]},
|
||||
)
|
||||
|
||||
many2one_model_id = fields.Many2one(
|
||||
comodel_name="ir.model",
|
||||
string="Model",
|
||||
help="For 'Many2one' Odoo field.\n" " Comodel of the field.",
|
||||
states={"model_valid": [("readonly", True)], "ui_valid": [("readonly", True)]},
|
||||
)
|
||||
|
||||
group_operator = fields.Selection(
|
||||
selection=_GROUP_OPERATOR_SELECTION,
|
||||
help="By default, Odoo will sum the values when grouping. If you wish "
|
||||
"to alter the behaviour, choose an alternate Group Operator",
|
||||
states={"model_valid": [("readonly", True)], "ui_valid": [("readonly", True)]},
|
||||
)
|
||||
|
||||
field_context = fields.Char(
|
||||
default="{}",
|
||||
help="Context value that will be inserted for this field in all the views."
|
||||
" Important note : please write a context with single quote.",
|
||||
states={"ui_valid": [("readonly", True)]},
|
||||
)
|
||||
|
||||
# Constrains Section
|
||||
@@ -188,6 +202,16 @@ class BiSQLViewField(models.Model):
|
||||
)
|
||||
return super().create(vals_list)
|
||||
|
||||
def unlink(self):
|
||||
if self.filtered(lambda x: x.state in ("model_valid", "ui_valid")):
|
||||
raise UserError(
|
||||
_(
|
||||
"Impossible to delete fields if the view"
|
||||
" is in the state 'Model Valid' or 'UI Valid'."
|
||||
)
|
||||
)
|
||||
return super().unlink()
|
||||
|
||||
# Custom Section
|
||||
@api.model
|
||||
def _model_mapping(self):
|
||||
@@ -233,12 +257,12 @@ class BiSQLViewField(models.Model):
|
||||
if self.tree_visibility == "invisible":
|
||||
visibility_text = 'invisible="1"'
|
||||
elif self.tree_visibility == "optional_hide":
|
||||
visibility_text = 'option="hide"'
|
||||
visibility_text = 'optional="hide"'
|
||||
elif self.tree_visibility == "optional_show":
|
||||
visibility_text = 'option="show"'
|
||||
visibility_text = 'optional="show"'
|
||||
|
||||
return (
|
||||
f"""<field name="{self.name}" {visibility_text} """
|
||||
f"""<field name="{self.name}" {visibility_text}"""
|
||||
f""" context="{self.field_context}"/>\n"""
|
||||
)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Copyright 2017 Onestein (<http://www.onestein.eu>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
from odoo.exceptions import AccessError, UserError, ValidationError
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import SingleTransactionCase
|
||||
|
||||
@@ -10,85 +10,107 @@ from odoo.tests.common import SingleTransactionCase
|
||||
class TestBiSqlViewEditor(SingleTransactionCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestBiSqlViewEditor, cls).setUpClass()
|
||||
super().setUpClass()
|
||||
|
||||
cls.res_partner = cls.env["res.partner"]
|
||||
cls.res_users = cls.env["res.users"]
|
||||
cls.bi_sql_view = cls.env["bi.sql.view"]
|
||||
cls.group_bi_user = cls.env.ref(
|
||||
cls.group_bi_manager = cls.env.ref(
|
||||
"sql_request_abstract.group_sql_request_manager"
|
||||
)
|
||||
cls.group_user = cls.env.ref("base.group_user")
|
||||
cls.view = cls.bi_sql_view.create(
|
||||
{
|
||||
"name": "Partners View 2",
|
||||
"is_materialized": True,
|
||||
"technical_name": "partners_view_2",
|
||||
"query": "SELECT name as x_name, street as x_street,"
|
||||
"company_id as x_company_id FROM res_partner "
|
||||
"ORDER BY name",
|
||||
}
|
||||
)
|
||||
cls.company = cls.env.ref("base.main_company")
|
||||
# Create bi user
|
||||
cls.bi_user = cls._create_user("bi_user", cls.group_bi_user, cls.company)
|
||||
cls.no_bi_user = cls._create_user("no_bi_user", cls.group_user, cls.company)
|
||||
cls.group_bi_no_access = cls.env.ref("base.group_user")
|
||||
cls.demo_user = cls.env.ref("base.user_demo")
|
||||
cls.view = cls.env.ref("bi_sql_editor.partner_sql_view")
|
||||
|
||||
@classmethod
|
||||
def _create_user(cls, login, groups, company):
|
||||
"""Create a user."""
|
||||
user = cls.res_users.create(
|
||||
{
|
||||
"name": login,
|
||||
"login": login,
|
||||
"password": "demo",
|
||||
"email": "example@yourcompany.com",
|
||||
"company_id": company.id,
|
||||
"groups_id": [(6, 0, groups.ids)],
|
||||
}
|
||||
)
|
||||
return user
|
||||
def _get_user(cls, access_level=False):
|
||||
if access_level == "manager":
|
||||
cls.demo_user.write({"groups_id": [(6, 0, cls.group_bi_manager.ids)]})
|
||||
else:
|
||||
cls.demo_user.write({"groups_id": [(6, 0, cls.group_bi_no_access.ids)]})
|
||||
return cls.demo_user
|
||||
|
||||
def test_process_view(self):
|
||||
view = self.view
|
||||
self.assertEqual(view.state, "draft", "state not draft")
|
||||
view.button_validate_sql_expression()
|
||||
self.assertEqual(view.state, "sql_valid", "state not sql_valid")
|
||||
view.button_create_sql_view_and_model()
|
||||
self.assertEqual(view.state, "model_valid", "state not model_valid")
|
||||
view.button_create_ui()
|
||||
self.assertEqual(view.state, "ui_valid", "state not ui_valid")
|
||||
view.button_update_model_access()
|
||||
self.assertEqual(view.has_group_changed, False, "has_group_changed not False")
|
||||
cron_res = view.cron_id.method_direct_trigger()
|
||||
self.assertEqual(cron_res, True, "something went wrong with the cron")
|
||||
copy_view = self.view.copy(default={"technical_name": "test_process_view"})
|
||||
self.assertEqual(copy_view.state, "draft")
|
||||
copy_view.button_validate_sql_expression()
|
||||
self.assertEqual(copy_view.state, "sql_valid")
|
||||
|
||||
field_lines = copy_view.bi_sql_view_field_ids
|
||||
self.assertEqual(len(field_lines), 3)
|
||||
field_lines.filtered(lambda x: x.name == "x_company_id").is_index = True
|
||||
|
||||
copy_view.button_create_sql_view_and_model()
|
||||
self.assertEqual(copy_view.state, "model_valid")
|
||||
|
||||
field_lines.filtered(lambda x: x.name == "x_name").tree_visibility = "invisible"
|
||||
field_lines.filtered(
|
||||
lambda x: x.name == "x_street"
|
||||
).tree_visibility = "optional_hide"
|
||||
field_lines.filtered(
|
||||
lambda x: x.name == "x_company_id"
|
||||
).tree_visibility = "optional_show"
|
||||
|
||||
field_lines.filtered(lambda x: x.name == "x_company_id").is_group_by = True
|
||||
|
||||
field_lines.filtered(lambda x: x.name == "x_company_id").graph_type = "row"
|
||||
|
||||
copy_view.button_create_ui()
|
||||
self.assertEqual(copy_view.state, "ui_valid")
|
||||
copy_view.button_update_model_access()
|
||||
self.assertEqual(copy_view.has_group_changed, False)
|
||||
# Check that cron works correctly
|
||||
copy_view.cron_id.method_direct_trigger()
|
||||
|
||||
def test_copy(self):
|
||||
copy_view = self.view.copy()
|
||||
self.assertEqual(copy_view.name, "Partners View 2 (Copy)", "Wrong name")
|
||||
copy_view = self.view.copy(default={"technical_name": "test_copy"})
|
||||
self.assertEqual(copy_view.name, f"{self.view.name} (Copy)")
|
||||
|
||||
def test_security(self):
|
||||
with self.assertRaises(AccessError):
|
||||
self.bi_sql_view.with_user(self.no_bi_user.id).search(
|
||||
[("name", "=", "Partners View 2")]
|
||||
self.bi_sql_view.with_user(self._get_user()).search(
|
||||
[("name", "=", self.view.name)]
|
||||
)
|
||||
bi = self.bi_sql_view.with_user(self.bi_user.id).search(
|
||||
[("name", "=", "Partners View 2")]
|
||||
bi = self.bi_sql_view.with_user(self._get_user("manager")).search(
|
||||
[("name", "=", self.view.name)]
|
||||
)
|
||||
self.assertEqual(
|
||||
len(bi), 1, "Bi user should not have access to " "bi %s" % self.view.name
|
||||
len(bi), 1, "Bi Manager should have access to bi %s" % self.view.name
|
||||
)
|
||||
|
||||
def test_unlink(self):
|
||||
self.assertEqual(self.view.state, "ui_valid", "state not ui_valid")
|
||||
with self.assertRaises(UserError):
|
||||
self.view.unlink()
|
||||
self.view.button_set_draft()
|
||||
self.assertNotEqual(
|
||||
self.view.cron_id,
|
||||
False,
|
||||
"Set to draft materialized view should" " not unlink cron",
|
||||
copy_view = self.view.copy(
|
||||
default={
|
||||
"name": "Test Unlink",
|
||||
"technical_name": "test_unlink",
|
||||
}
|
||||
)
|
||||
self.view.unlink()
|
||||
res = self.bi_sql_view.search([("name", "=", "Partners View 2")])
|
||||
view_name = copy_view.name
|
||||
copy_view.button_validate_sql_expression()
|
||||
copy_view.button_create_sql_view_and_model()
|
||||
copy_view.button_create_ui()
|
||||
self.assertEqual(copy_view.state, "ui_valid")
|
||||
with self.assertRaises(UserError):
|
||||
copy_view.unlink()
|
||||
copy_view.button_set_draft()
|
||||
self.assertNotEqual(
|
||||
copy_view.cron_id,
|
||||
False,
|
||||
"Set to draft materialized view should not unlink cron",
|
||||
)
|
||||
copy_view.unlink()
|
||||
res = self.bi_sql_view.search([("name", "=", view_name)])
|
||||
self.assertEqual(len(res), 0, "View not deleted")
|
||||
|
||||
def test_many2one_not_found(self):
|
||||
copy_view = self.view.copy(
|
||||
default={"technical_name": "test_many2one_not_found"}
|
||||
)
|
||||
|
||||
copy_view.query = "SELECT parent_id as x_weird_name_id FROM res_partner;"
|
||||
copy_view.button_validate_sql_expression()
|
||||
field_lines = copy_view.bi_sql_view_field_ids
|
||||
self.assertEqual(len(field_lines), 1)
|
||||
self.assertEqual(field_lines[0].ttype, "many2one")
|
||||
self.assertEqual(field_lines[0].many2one_model_id.id, False)
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
copy_view.button_create_sql_view_and_model()
|
||||
|
||||
@@ -34,14 +34,20 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//header" position="inside">
|
||||
<button
|
||||
name="button_set_draft"
|
||||
name="button_reset_to_sql_valid"
|
||||
type="object"
|
||||
states="model_valid,ui_valid"
|
||||
string="Set to Draft"
|
||||
states="model_valid"
|
||||
string="Delete SQL Elements"
|
||||
groups="sql_request_abstract.group_sql_request_manager"
|
||||
confirm="It will delete the materialized view, and all the previous mapping realized with the columns"
|
||||
/>
|
||||
<button
|
||||
name="button_reset_to_model_valid"
|
||||
type="object"
|
||||
states="ui_valid"
|
||||
string="Delete UI"
|
||||
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_create_sql_view_and_model"
|
||||
type="object"
|
||||
@@ -66,13 +72,6 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
class="oe_highlight"
|
||||
help="This will create Odoo View, Action and Menu"
|
||||
/>
|
||||
<button
|
||||
name="button_refresh_materialized_view"
|
||||
type="object"
|
||||
string="Refresh"
|
||||
help="Refresh Materialized View"
|
||||
attrs="{'invisible': ['|', ('state', 'in', ('draft', 'sql_valid')), ('is_materialized', '=', False)]}"
|
||||
/>
|
||||
<button
|
||||
name="button_open_view"
|
||||
type="object"
|
||||
@@ -80,6 +79,13 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
states="ui_valid"
|
||||
class="oe_highlight"
|
||||
/>
|
||||
<button
|
||||
name="button_refresh_materialized_view"
|
||||
type="object"
|
||||
string="Refresh"
|
||||
help="Refresh Materialized View"
|
||||
attrs="{'invisible': ['|', ('state', 'in', ('draft', 'sql_valid')), ('is_materialized', '=', False)]}"
|
||||
/>
|
||||
</xpath>
|
||||
<group name="group_main_info" position="inside">
|
||||
<group>
|
||||
@@ -98,7 +104,6 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
name="cron_id"
|
||||
attrs="{'invisible': ['|', ('state', 'in', ('draft', 'sql_valid')), ('is_materialized', '=', False)]}"
|
||||
/>
|
||||
<field name="option_context_field" />
|
||||
</group>
|
||||
</group>
|
||||
<page name="page_sql" position="after">
|
||||
@@ -106,16 +111,8 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
string="SQL Fields"
|
||||
attrs="{'invisible': [('state', '=', 'draft')]}"
|
||||
>
|
||||
<field
|
||||
name="bi_sql_view_field_ids"
|
||||
nolabel="1"
|
||||
colspan="4"
|
||||
attrs="{'readonly': [('state', '!=', 'sql_valid')]}"
|
||||
>
|
||||
<tree
|
||||
editable="bottom"
|
||||
decoration-info="field_description==False"
|
||||
>
|
||||
<field name="bi_sql_view_field_ids" nolabel="1" colspan="4">
|
||||
<tree editable="bottom" create="false">
|
||||
<field name="sequence" widget="handle" />
|
||||
<field name="name" />
|
||||
<field name="sql_type" />
|
||||
@@ -132,9 +129,8 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
name="selection"
|
||||
attrs="{
|
||||
'invisible': [('ttype', '!=', 'selection')],
|
||||
'required': [
|
||||
('field_description', '!=', False),
|
||||
('ttype', '=', 'selection')]}"
|
||||
'required': [('ttype', '=', 'selection')],
|
||||
}"
|
||||
/>
|
||||
<field
|
||||
name='group_operator'
|
||||
@@ -142,32 +138,12 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
attrs="{
|
||||
'invisible': [('ttype', 'not in', ('float', 'integer'))]}"
|
||||
/>
|
||||
<field
|
||||
name="is_index"
|
||||
optional="hide"
|
||||
attrs="{'invisible': [('field_description', '=', False)]}"
|
||||
/>
|
||||
<field
|
||||
name="graph_type"
|
||||
attrs="{'invisible': [('field_description', '=', False)]}"
|
||||
/>
|
||||
<field
|
||||
name="is_group_by"
|
||||
optional="hide"
|
||||
attrs="{'invisible': [('field_description', '=', False)]}"
|
||||
/>
|
||||
<field
|
||||
name="tree_visibility"
|
||||
optional="hide"
|
||||
attrs="{'invisible': [('field_description', '=', False)]}"
|
||||
/>
|
||||
<field
|
||||
name="field_context"
|
||||
attrs="{
|
||||
'invisible': [('field_description', '=', False)],
|
||||
'column_invisible': [('parent.option_context_field', '=', False)],
|
||||
}"
|
||||
/>
|
||||
<field name="is_index" optional="hide" />
|
||||
<field name="graph_type" />
|
||||
<field name="is_group_by" optional="hide" />
|
||||
<field name="tree_visibility" optional="hide" />
|
||||
<field name="field_context" optional="hide" />
|
||||
<field name="state" invisible="1" />
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
Reference in New Issue
Block a user