Add optional column on tree view
issue10989 review374321002
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }?
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
Reference in New Issue
Block a user