Add optional column on tree view

issue10989
review374321002
This commit is contained in:
Cédric Krier
2022-02-03 23:13:28 +01:00
parent 632deb43e5
commit 10d277f001
24 changed files with 230 additions and 61 deletions
+1
View File
@@ -1,3 +1,4 @@
* Add optional column on tree view
* Use dictionary as domain on Reference field
* Include model, field and column in import data error message
* Support limit and offset to ModelSQL count search and search_count
+1 -1
View File
@@ -188,7 +188,7 @@ Class methods:
:attr:`~trytond.transaction.Transaction.context` when calling this method.
.. classmethod:: ModelView.parse_view(tree, type[, field_children[, level[, view_depends]]])
.. classmethod:: ModelView.parse_view(tree, type[, view_id[, field_children[, level[, view_depends]]]])
Return the sanitized XML and the corresponding fields definition.
+5
View File
@@ -619,6 +619,11 @@ field
that will be evaluated as boolean with the context of the view to display
or not the column.
* ``optional``: A boolean to define if the column is hidden or not.
Defining the optional attribute allows each user to show/hide the column.
The attribute value is used as default when the user has no custom
setting for it.
* ``visual``: A :ref:`PYSON statement <topics-pyson>` that will be
evaluated as string ``muted``, ``success``, ``warning`` or ``danger``
with the context of the record to provide a visual context to the field.
+1
View File
@@ -29,6 +29,7 @@ def register():
ui.view.View,
ui.view.ShowViewStart,
ui.view.ViewTreeWidth,
ui.view.ViewTreeOptional,
ui.view.ViewTreeState,
ui.view.ViewSearch,
ui.icon.Icon,
+1
View File
@@ -46,6 +46,7 @@ attlist.field &=
}?
attlist.field &=
[ a:defaultValue = "0" ] attribute tree_invisible { text }?
attlist.field &= attribute optional { "0" | "1" }?
attlist.field &=
[ a:defaultValue = "0" ] attribute expand { xsd:integer }?
attlist.field &= attribute visual { text }?
+11
View File
@@ -152,6 +152,17 @@
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">optional</name>
<choice>
<value>0</value>
<value>1</value>
</choice>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute a:defaultValue="0">
+63
View File
@@ -405,6 +405,69 @@ class ViewTreeWidth(ModelSQL, ModelView):
cls.create(to_create)
class ViewTreeOptional(ModelSQL, ModelView):
"View Tree Optional"
__name__ = 'ir.ui.view_tree_optional'
view_id = fields.Many2One(
'ir.ui.view', "View ID", required=True, ondelete='CASCADE',
domain=[
('type', '=', 'tree'),
])
user = fields.Many2One(
'res.user', "User", required=True, ondelete='CASCADE')
field = fields.Char("Field", required=True)
value = fields.Boolean("Value")
@classmethod
def __setup__(cls):
super().__setup__()
cls.__rpc__.update({
'set_optional': RPC(readonly=False),
})
@classmethod
def __register__(cls, module_name):
super().__register__(module_name)
table_h = cls.__table_handler__(module_name)
table_h.index_action(['view_id', 'user'], 'add')
@classmethod
def create(cls, vlist):
records = super().create(vlist)
ModelView._fields_view_get_cache.clear()
return records
@classmethod
def write(cls, *args):
super().write(*args)
ModelView._fields_view_get_cache.clear()
@classmethod
def delete(cls, records):
ModelView._fields_view_get_cache.clear()
super().delete(records)
@classmethod
def set_optional(cls, view_id, fields):
"Store optional field that must be displayed"
user = Transaction().user
records = cls.search([
('view_id', '=', view_id),
('user', '=', user),
])
cls.delete(records)
to_create = []
for field, value in fields.items():
to_create.append({
'view_id': view_id,
'user': user,
'field': field,
'value': bool(value),
})
if to_create:
cls.create(to_create)
class ViewTreeState(ModelSQL, ModelView):
'View Tree State'
__name__ = 'ir.ui.view_tree_state'
+33
View File
@@ -80,6 +80,39 @@ this repository contains the full copyright notices and license terms. -->
sequence="50"
id="menu_view_tree_width"/>
<record model="ir.ui.view" id="view_tree_optional_view_form">
<field name="model">ir.ui.view_tree_optional</field>
<field name="type">form</field>
<field name="name">ui_view_tree_optional_form</field>
</record>
<record model="ir.ui.view" id="view_tree_optional_view_tree">
<field name="model">ir.ui.view_tree_optional</field>
<field name="type">tree</field>
<field name="name">ui_view_tree_optional_list</field>
</record>
<record model="ir.action.act_window" id="act_view_tree_optional_form">
<field name="name">View Tree Optional</field>
<field name="res_model">ir.ui.view_tree_optional</field>
</record>
<record model="ir.action.act_window.view" id="act_view_tree_optional_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="view_tree_optional_view_tree"/>
<field name="act_window" ref="act_view_tree_optional_form"/>
</record>
<record model="ir.action.act_window.view" id="act_view_tree_optional_form_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="view_tree_optional_view_form"/>
<field name="act_window" ref="act_view_tree_optional_form"/>
</record>
<menuitem
parent="menu_ui"
action="act_view_tree_optional_form"
sequence="50"
id="menu_view_tree_optional"/>
<record model="ir.ui.view" id="view_tree_state_form">
<field name="model">ir.ui.view_tree_state</field>
<field name="type">form</field>
+5 -5
View File
@@ -4,12 +4,12 @@ this repository contains the full copyright notices and license terms. -->
<tree editable="1">
<field name="resource" expand="2"/>
<field name="name" expand="2"/>
<field name="summary" expand="1"/>
<field name="last_user" expand="1"/>
<field name="last_modification" widget="date"/>
<field name="last_modification" widget="time"
<field name="summary" expand="1" optional="1"/>
<field name="last_user" expand="1" optional="1"/>
<field name="last_modification" widget="date" optional="1"/>
<field name="last_modification" widget="time" optional="1"
string="Last Modification Time"/>
<field name="type"/>
<field name="type" optional="1"/>
<field name="data"/>
<field name="link" widget="url" expand="1"/>
<!-- To allow client to set description field -->
+2 -2
View File
@@ -3,8 +3,8 @@
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="method" expand="1"/>
<field name="next_call" widget="date"/>
<field name="next_call" widget="time"/>
<field name="next_call" widget="date" optional="1"/>
<field name="next_call" widget="time" optional="1"/>
<field name="interval_number"/>
<field name="interval_type"/>
<button name="run_once" string="Run Once" tree_invisible="1"/>
+2 -2
View File
@@ -3,6 +3,6 @@
this repository contains the full copyright notices and license terms. -->
<tree sequence="sequence">
<field name="name" icon="name" expand="2"/>
<field name="module"/>
<field name="path" expand="1"/>
<field name="module" optional="0"/>
<field name="path" expand="1" optional="1"/>
</tree>
+2 -2
View File
@@ -3,8 +3,8 @@
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="name" expand="1"/>
<field name="code"/>
<field name="direction"/>
<field name="code" optional="1"/>
<field name="direction" optional="1"/>
<field name="translatable"/>
<button name="load_translations"/>
</tree>
+2 -2
View File
@@ -2,10 +2,10 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="module"/>
<field name="module" optional="0"/>
<field name="model" expand="1"/>
<field name="fs_id" expand="1"/>
<field name="db_id"/>
<field name="db_id" optional="1"/>
<field name="noupdate"/>
<field name="out_of_sync"/>
<button name="sync"/>
+5 -5
View File
@@ -4,9 +4,9 @@ this repository contains the full copyright notices and license terms. -->
<tree>
<field name="name" expand="2"/>
<field name="model" expand="1"/>
<field name="ttype"/>
<field name="relation" expand="1"/>
<field name="field_description" expand="1"/>
<field name="access"/>
<field name="module"/>
<field name="ttype" optional="0"/>
<field name="relation" expand="1" optional="1"/>
<field name="field_description" expand="1" optional="0"/>
<field name="access" optional="1"/>
<field name="module" optional="0"/>
</tree>
+2 -2
View File
@@ -4,7 +4,7 @@ this repository contains the full copyright notices and license terms. -->
<tree>
<field name="name" expand="2"/>
<field name="model" expand="1"/>
<field name="info" expand="1"/>
<field name="info" expand="1" optional="1"/>
<field name="global_search_p"/>
<field name="module"/>
<field name="module" optional="0"/>
</tree>
@@ -3,6 +3,6 @@
this repository contains the full copyright notices and license terms. -->
<tree editable="1">
<field name="action" expand="1"/>
<field name="sequence"/>
<field name="sequence" optional="1"/>
<field name="state"/>
</tree>
+1 -1
View File
@@ -3,7 +3,7 @@
this repository contains the full copyright notices and license terms. -->
<tree on_write="on_write">
<field name="name" expand="1"/>
<field name="version"/>
<field name="version" optional="1"/>
<field name="state"/>
<button string="Mark for Upgrade" name="upgrade"/>
<button string="Mark for Activation" name="activate"/>
+3 -3
View File
@@ -4,8 +4,8 @@ this repository contains the full copyright notices and license terms. -->
<tree>
<field name="unread"/>
<field name="resource" expand="1"/>
<field name="last_user" string="User" expand="1"/>
<field name="last_modification" widget="date" string="Date"/>
<field name="last_modification" widget="time" string="Time"/>
<field name="last_user" string="User" expand="1" optional="1"/>
<field name="last_modification" widget="date" string="Date" optional="1"/>
<field name="last_modification" widget="time" string="Time" optional="1"/>
<field name="message_wrapped" expand="2"/>
</tree>
+4 -4
View File
@@ -5,9 +5,9 @@ this repository contains the full copyright notices and license terms. -->
<field name="src" widget="char" expand="2"/>
<field name="value" widget="char" expand="2"/>
<field name="fuzzy"/>
<field name="name" expand="1"/>
<field name="name" expand="1" optional="1"/>
<field name="lang"/>
<field name="type"/>
<field name="res_id"/>
<field name="module"/>
<field name="type" optional="1"/>
<field name="res_id" optional="1"/>
<field name="module" optional="0"/>
</tree>
+4 -4
View File
@@ -2,10 +2,10 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="priority"/>
<field name="priority" optional="1"/>
<field name="model" expand="2"/>
<field name="type"/>
<field name="inherit" expand="1"/>
<field name="domain" expand="1"/>
<field name="field_childs"/>
<field name="inherit" expand="1" optional="0"/>
<field name="domain" expand="1" optional="1"/>
<field name="field_childs" optional="1"/>
</tree>
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<label name="view_id"/>
<field name="view_id"/>
<label name="user"/>
<field name="user"/>
<label name="field"/>
<field name="field"/>
<label name="value"/>
<field name="value"/>
</form>
@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="view_id" expand="1"/>
<field name="user" expand="1"/>
<field name="field" expand="1"/>
<field name="value"/>
</tree>
+54 -27
View File
@@ -252,9 +252,9 @@ class ModelView(Model):
# Update arch and compute fields from arch
parser = etree.XMLParser(remove_blank_text=True)
tree = etree.fromstring(result['arch'], parser)
with Transaction().set_context(view_id=view_id):
result['arch'], result['fields'] = cls.parse_view(
tree, result['type'], result['field_childs'], level=level)
result['arch'], result['fields'] = cls.parse_view(
tree, result['type'], view_id=view_id,
field_children=result['field_childs'], level=level)
cls._fields_view_get_cache.set(key, result)
return result
@@ -303,7 +303,8 @@ class ModelView(Model):
@classmethod
def parse_view(
cls, tree, type, field_children=None, level=0, view_depends=None):
cls, tree, type, view_id=None, field_children=None, level=0,
view_depends=None):
"""
Return sanitized XML and the corresponding fields definition
"""
@@ -316,17 +317,19 @@ class ModelView(Model):
view_depends = []
else:
view_depends = view_depends.copy()
for xpath, attribute, value, *extra in cls.view_attributes():
depends = []
if extra:
depends, = extra
nodes = tree.xpath(xpath)
for element in nodes:
element.set(attribute, encoder.encode(value))
if nodes and depends:
view_depends.extend(depends)
with Transaction().set_context(view_id=view_id):
for xpath, attribute, value, *extra in cls.view_attributes():
depends = []
if extra:
depends, = extra
nodes = tree.xpath(xpath)
for element in nodes:
element.set(attribute, encoder.encode(value))
if nodes and depends:
view_depends.extend(depends)
fields_width = {}
fields_optional = {}
tree_root = tree.getroottree().getroot()
# Find field without read access
@@ -382,18 +385,30 @@ class ModelView(Model):
for page in tree.xpath('//page[not(descendant::*)]'):
page.getparent().remove(page)
if type == 'tree' and Transaction().context.get('view_tree_width'):
ViewTreeWidth = pool.get('ir.ui.view_tree_width')
viewtreewidth_ids = ViewTreeWidth.search([
('model', '=', cls.__name__),
('user', '=', Transaction().user),
])
for viewtreewidth in ViewTreeWidth.browse(viewtreewidth_ids):
if viewtreewidth.width > 0:
fields_width[viewtreewidth.field] = viewtreewidth.width
if type == 'tree':
user = Transaction().user
if Transaction().context.get('view_tree_width'):
ViewTreeWidth = pool.get('ir.ui.view_tree_width')
viewtreewidths = ViewTreeWidth.search([
('model', '=', cls.__name__),
('user', '=', user),
])
for viewtreewidth in viewtreewidths:
if viewtreewidth.width > 0:
fields_width[viewtreewidth.field] = viewtreewidth.width
if view_id:
ViewTreeOptional = pool.get('ir.ui.view_tree_optional')
viewtreeoptionals = ViewTreeOptional.search([
('view_id', '=', view_id),
('user', '=', user),
])
fields_optional = {o.field: o.value for o in viewtreeoptionals}
fields_def = cls.__parse_fields(
tree_root, type, fields_width=fields_width)
tree_root, type,
fields_width=fields_width,
fields_optional=fields_optional)
if hasattr(cls, 'active'):
fields_def.setdefault('active', {'name': 'active'})
@@ -435,7 +450,8 @@ class ModelView(Model):
@classmethod
def __parse_fields(
cls, element, type, fields_width=None, _fields_attrs=None):
cls, element, type, fields_width=None, fields_optional=None,
_fields_attrs=None):
pool = Pool()
Translation = pool.get('ir.translation')
ModelData = pool.get('ir.model.data')
@@ -446,6 +462,8 @@ class ModelView(Model):
if fields_width is None:
fields_width = {}
if fields_optional is None:
fields_optional = {}
if _fields_attrs is None:
fields_attrs = {}
else:
@@ -509,8 +527,15 @@ class ModelView(Model):
element.attrib['mode'] = ','.join(mode)
fields_attrs[fname].setdefault('views', {}).update(views)
if type == 'tree' and element.get('name') in fields_width:
element.set('width', str(fields_width[element.get('name')]))
if type == 'tree':
if element.get('name') in fields_width:
element.set(
'width', str(fields_width[element.get('name')]))
if element.get('optional'):
if element.get('name') in fields_optional:
optional = str(int(
fields_optional[element.get('name')]))
element.set('optional', optional)
encoder = PYSONEncoder()
if element.tag == 'button':
@@ -595,7 +620,9 @@ class ModelView(Model):
for field in element:
fields_attrs = cls.__parse_fields(
field, type, fields_width=fields_width,
field, type,
fields_width=fields_width,
fields_optional=fields_optional,
_fields_attrs=fields_attrs)
return fields_attrs
+4
View File
@@ -589,6 +589,10 @@ this repository contains the full copyright notices and license terms. -->
<field name="menu" ref="ir.menu_view_tree_width"/>
<field name="group" ref="group_admin"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_view_tree_optional_group_admin">
<field name="menu" ref="ir.menu_view_tree_optional"/>
<field name="group" ref="group_admin"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_action_group_admin">
<field name="menu" ref="ir.menu_action"/>
<field name="group" ref="group_admin"/>