+15
-19
@@ -72,8 +72,7 @@
|
||||
this.view.columns.push(column);
|
||||
|
||||
if (attributes.optional && (name !== this.exclude_field)) {
|
||||
Sao.setdefault(
|
||||
this.view.optionals, column.attributes.name, []).push(column);
|
||||
this.view.optionals.push(column);
|
||||
}
|
||||
|
||||
if (parseInt(attributes.sum || '0', 10)) {
|
||||
@@ -112,7 +111,7 @@
|
||||
display_size: null,
|
||||
init: function(view_id, screen, xml, children_field) {
|
||||
this.children_field = children_field;
|
||||
this.optionals = {};
|
||||
this.optionals = [];
|
||||
this.sum_widgets = new Map();
|
||||
this.columns = [];
|
||||
this.selection_mode = (screen.attributes.selection_mode ||
|
||||
@@ -365,19 +364,18 @@
|
||||
},
|
||||
tree_menu: function(evt) {
|
||||
const toggle = evt => {
|
||||
let columns = evt.data;
|
||||
let column = evt.data;
|
||||
let visible = jQuery(evt.delegateTarget).prop('checked');
|
||||
columns.forEach(c => c.set_visible(visible));
|
||||
column.set_visible(visible);
|
||||
this.save_optional();
|
||||
this.display();
|
||||
this.update_visible();
|
||||
};
|
||||
var menu = evt.data;
|
||||
menu.empty();
|
||||
for (let columns of Object.values(this.optionals)) {
|
||||
let visible = columns.some(c => c.get_visible());
|
||||
let string = [...new Set(columns.map(c => c.attributes.string))]
|
||||
.join(' / ');
|
||||
for (let column of this.optionals) {
|
||||
let visible = column.get_visible();
|
||||
let string = column.attributes.string;
|
||||
menu.append(jQuery('<li/>', {
|
||||
'role': 'presentation',
|
||||
}).append(jQuery('<a/>', {
|
||||
@@ -388,7 +386,7 @@
|
||||
}).append(jQuery('<input/>', {
|
||||
'type': 'checkbox',
|
||||
'checked': visible,
|
||||
}).change(columns, toggle))
|
||||
}).change(column, toggle))
|
||||
.append(' ' + string)))));
|
||||
}
|
||||
if (!jQuery.isEmptyObject(this.optionals)) {
|
||||
@@ -449,12 +447,9 @@
|
||||
})));
|
||||
},
|
||||
save_optional: function(store=true) {
|
||||
if (jQuery.isEmptyObject(this.optionals)) {
|
||||
return;
|
||||
}
|
||||
var fields = {};
|
||||
for (let [name, columns] of Object.entries(this.optionals)) {
|
||||
fields[name] = columns.every(c => !c.get_visible());
|
||||
let fields = {};
|
||||
for (let column of this.optionals) {
|
||||
Sao.setdefault(fields, column.attributes.name, []).push(!column.get_visible());
|
||||
}
|
||||
if (store) {
|
||||
var tree_optional_model = new Sao.Model(
|
||||
@@ -1042,7 +1037,7 @@
|
||||
domain = inversion.simplify(domain);
|
||||
var decoder = new Sao.PYSON.Decoder(this.screen.context);
|
||||
var min_width = [];
|
||||
var tree_column_optional = (
|
||||
var tree_column_optional = structuredClone(
|
||||
Sao.Screen.tree_column_optional[this.view_id] || {});
|
||||
for (const column of this.columns) {
|
||||
visible_columns += 1;
|
||||
@@ -1053,8 +1048,9 @@
|
||||
var optional;
|
||||
if ((column.attributes.optional) &&
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
tree_column_optional, name)) {
|
||||
optional = tree_column_optional[name];
|
||||
tree_column_optional, name) &&
|
||||
!jQuery.isEmptyObject(tree_column_optional[name])) {
|
||||
optional = tree_column_optional[name].shift();
|
||||
} else {
|
||||
optional = Boolean(parseInt(
|
||||
column.attributes.optional || '0', 10));
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
import copy
|
||||
import csv
|
||||
import gettext
|
||||
import json
|
||||
@@ -369,7 +371,7 @@ class TreeXMLViewParser(XMLViewParser):
|
||||
self.view.treeview.append_column(column)
|
||||
|
||||
if 'optional' in attributes and name != self.exclude_field:
|
||||
self.view.optionals[column.name].append(column)
|
||||
self.view.optionals.append(column)
|
||||
|
||||
def _parse_button(self, node, attributes):
|
||||
if self.view.screen.screen_readonly:
|
||||
@@ -499,7 +501,7 @@ class ViewTree(View):
|
||||
|
||||
def __init__(self, view_id, screen, xml, children_field):
|
||||
self.children_field = children_field
|
||||
self.optionals = defaultdict(list)
|
||||
self.optionals = []
|
||||
self.sum_widgets = []
|
||||
self.sum_box = Gtk.HBox()
|
||||
self.hbuttonbox = Gtk.HButtonBox()
|
||||
@@ -590,20 +592,19 @@ class ViewTree(View):
|
||||
self.treeview.append_column(column)
|
||||
|
||||
def optional_menu(self, column):
|
||||
def toggle(menuitem, columns):
|
||||
def toggle(menuitem, column):
|
||||
visible = menuitem.get_active()
|
||||
for column in columns:
|
||||
column.set_visible(visible)
|
||||
column.set_visible(visible)
|
||||
self.save_optional()
|
||||
|
||||
widget = column.get_widget()
|
||||
menu = Gtk.Menu()
|
||||
for name, columns in self.optionals.items():
|
||||
visible = any(c.get_visible() for c in columns)
|
||||
title = ' / '.join({c.get_title() for c in columns})
|
||||
for column in self.optionals:
|
||||
visible = column.get_visible()
|
||||
title = column.get_title()
|
||||
menuitem = Gtk.CheckMenuItem(label=title)
|
||||
menuitem.set_active(visible)
|
||||
menuitem.connect('toggled', toggle, columns)
|
||||
menuitem.connect('toggled', toggle, column)
|
||||
menu.add(menuitem)
|
||||
if self.optionals:
|
||||
menu.add(Gtk.SeparatorMenuItem())
|
||||
@@ -613,9 +614,9 @@ class ViewTree(View):
|
||||
popup(menu, widget)
|
||||
|
||||
def save_optional(self):
|
||||
fields = {}
|
||||
for name, columns in self.optionals.items():
|
||||
fields[name] = all(not c.get_visible() for c in columns)
|
||||
fields = defaultdict(list)
|
||||
for column in self.optionals:
|
||||
fields[column.name].append(not column.get_visible())
|
||||
try:
|
||||
RPCExecute(
|
||||
'model', 'ir.ui.view_tree_optional', 'set_optional',
|
||||
@@ -1287,8 +1288,8 @@ class ViewTree(View):
|
||||
domain.append(tab_domain)
|
||||
domain = simplify(domain)
|
||||
decoder = PYSONDecoder(self.screen.context)
|
||||
tree_column_optional = self.screen.tree_column_optional.get(
|
||||
self.view_id, {})
|
||||
tree_column_optional = copy.deepcopy(
|
||||
self.screen.tree_column_optional.get(self.view_id, {}))
|
||||
for column in self.treeview.get_columns():
|
||||
name = column.name
|
||||
if not name:
|
||||
@@ -1296,8 +1297,8 @@ class ViewTree(View):
|
||||
widget = self.get_column_widget(column)
|
||||
widget.set_editable()
|
||||
if ('optional' in widget.attrs
|
||||
and column.name in tree_column_optional):
|
||||
optional = tree_column_optional[column.name]
|
||||
and tree_column_optional.get(column.name)):
|
||||
optional = tree_column_optional[column.name].pop(0)
|
||||
else:
|
||||
optional = bool(int(widget.attrs.get('optional', '0')))
|
||||
invisible = decoder.decode(widget.attrs.get('tree_invisible', '0'))
|
||||
|
||||
@@ -294,6 +294,10 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="text">A user can set only one width per occurrence of field.</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.message" id="msg_view_tree_optional_field_occurrence_user_unique">
|
||||
<field name="text">A user can set only one optional per occurrence of field on the same view.</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.message" id="msg_action_wrong_wizard_model">
|
||||
<field name="text">Wrong wizard model in keyword action "%(name)s".</field>
|
||||
</record>
|
||||
|
||||
@@ -16,7 +16,7 @@ from sql.operators import Equal
|
||||
from trytond.cache import Cache, MemoryCache
|
||||
from trytond.i18n import gettext
|
||||
from trytond.model import (
|
||||
Exclude, Index, ModelSQL, ModelView, fields, sequence_ordered)
|
||||
Exclude, Index, ModelSQL, ModelView, Unique, fields, sequence_ordered)
|
||||
from trytond.model.exceptions import ValidationError
|
||||
from trytond.pool import Pool
|
||||
from trytond.pyson import PYSON, Bool, Eval, If, PYSONDecoder
|
||||
@@ -466,7 +466,11 @@ class ViewTreeWidth(
|
||||
__name__ = 'ir.ui.view_tree_width'
|
||||
model = fields.Char('Model', required=True)
|
||||
field = fields.Char('Field', required=True)
|
||||
occurrence = fields.Integer("Occurrence", required=True)
|
||||
occurrence = fields.Integer(
|
||||
"Occurrence", required=True,
|
||||
domain=[
|
||||
('occurrence', '>', 0),
|
||||
])
|
||||
user = fields.Many2One('res.user', 'User', required=True,
|
||||
ondelete='CASCADE')
|
||||
screen_width = fields.Integer(
|
||||
@@ -611,7 +615,7 @@ class ViewTreeWidth(
|
||||
for tree_width in records:
|
||||
if tree_width.screen_width == screen_width:
|
||||
index = tree_width.occurrence - 1
|
||||
if index <= len(fields[tree_width.field]):
|
||||
if index < len(fields[tree_width.field]):
|
||||
width = fields[tree_width.field][index]
|
||||
fields[tree_width.field][index] = None
|
||||
if width is not None:
|
||||
@@ -671,6 +675,11 @@ class ViewTreeOptional(
|
||||
'res.user', "User", required=True, ondelete='CASCADE')
|
||||
model = fields.Char("Model", required=True)
|
||||
field = fields.Char("Field", required=True)
|
||||
occurrence = fields.Integer(
|
||||
"Occurrence", required=True,
|
||||
domain=[
|
||||
('occurrence', '>', 0),
|
||||
])
|
||||
value = fields.Boolean("Value")
|
||||
|
||||
@classmethod
|
||||
@@ -680,6 +689,12 @@ class ViewTreeOptional(
|
||||
'set_optional': RPC(readonly=False),
|
||||
})
|
||||
table = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('model_field_occurrence_user_unique',
|
||||
Unique(table,
|
||||
table.view, table.field, table.occurrence, table.user),
|
||||
'ir.msg_view_tree_optional_field_occurrence_user_unique'),
|
||||
]
|
||||
cls._sql_indexes.add(
|
||||
Index(
|
||||
table,
|
||||
@@ -706,6 +721,10 @@ class ViewTreeOptional(
|
||||
[view.select(view.model, where=view.id == table.view)],
|
||||
where=table.model == Null))
|
||||
|
||||
@classmethod
|
||||
def default_occurrence(cls):
|
||||
return 1
|
||||
|
||||
@classmethod
|
||||
def validate_fields(cls, records, fields_names):
|
||||
super().validate_fields(records, fields_names)
|
||||
@@ -726,9 +745,34 @@ class ViewTreeOptional(
|
||||
super().on_modification(mode, record, field_names=field_names)
|
||||
ModelView._fields_view_get_cache.clear()
|
||||
|
||||
@classmethod
|
||||
def get_optional(cls, view_id):
|
||||
user = Transaction().user
|
||||
|
||||
records = cls.search([
|
||||
('view', '=', view_id),
|
||||
('user', '=', user),
|
||||
],
|
||||
order=[('occurrence', 'ASC')])
|
||||
|
||||
optionals = defaultdict(list)
|
||||
for optional in records:
|
||||
if len(optionals[optional.field]) + 1 < optional.occurrence:
|
||||
for _ in range(
|
||||
optional.occurrence
|
||||
- len(optionals[optional.field]) - 1):
|
||||
optionals[optional.field].append(None)
|
||||
optionals[optional.field].insert(
|
||||
optional.occurrence, optional.value)
|
||||
return optionals
|
||||
|
||||
@classmethod
|
||||
def set_optional(cls, view_id, fields):
|
||||
"Store optional field that must be displayed"
|
||||
'''
|
||||
Store optional fields that must be displayed.
|
||||
fields is dictionary with field name as key and a list of boolean as
|
||||
value.
|
||||
'''
|
||||
pool = Pool()
|
||||
View = pool.get('ir.ui.view')
|
||||
user = Transaction().user
|
||||
@@ -736,20 +780,32 @@ class ViewTreeOptional(
|
||||
records = cls.search([
|
||||
('view', '=', view.id),
|
||||
('user', '=', user),
|
||||
('field', 'in', list(fields)),
|
||||
])
|
||||
cls.delete(records)
|
||||
to_create = []
|
||||
for field, value in fields.items():
|
||||
to_create.append({
|
||||
'view': view,
|
||||
'user': user,
|
||||
'model': view.model,
|
||||
'field': field,
|
||||
'value': bool(value),
|
||||
})
|
||||
if to_create:
|
||||
cls.create(to_create)
|
||||
('field', 'in', list(fields.keys())),
|
||||
],
|
||||
order=[('occurrence', 'DESC')])
|
||||
|
||||
fields = copy.deepcopy(fields)
|
||||
to_save = []
|
||||
|
||||
for tree_optional in records:
|
||||
index = tree_optional.occurrence - 1
|
||||
if index < len(fields[tree_optional.field]):
|
||||
tree_optional.value = fields[tree_optional.field][index]
|
||||
fields[tree_optional.field][index] = None
|
||||
to_save.append(tree_optional)
|
||||
|
||||
for name, optionals in fields.items():
|
||||
for occurrence, optional in enumerate(optionals, start=1):
|
||||
if optional is not None:
|
||||
to_save.append(cls(
|
||||
view=view,
|
||||
user=user,
|
||||
model=view.model,
|
||||
field=name,
|
||||
occurrence=occurrence,
|
||||
value=optional))
|
||||
if to_save:
|
||||
cls.save(to_save)
|
||||
|
||||
|
||||
class ViewTreeState(
|
||||
|
||||
@@ -10,7 +10,10 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<label name="model_ref"/>
|
||||
<field name="model_ref"/>
|
||||
<label name="field_ref"/>
|
||||
<field name="field_ref"/>
|
||||
<group id="field" col="-1">
|
||||
<field name="field_ref"/>
|
||||
<field name="occurrence"/>
|
||||
</group>
|
||||
|
||||
<label name="value"/>
|
||||
<field name="value"/>
|
||||
|
||||
@@ -6,5 +6,6 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="user" expand="1"/>
|
||||
<field name="model_ref" expand="1"/>
|
||||
<field name="field_ref" expand="1"/>
|
||||
<field name="occurrence" optional="1"/>
|
||||
<field name="value"/>
|
||||
</tree>
|
||||
|
||||
@@ -494,7 +494,6 @@ class ModelView(Model):
|
||||
if field._type in {'one2many', 'many2many'}})
|
||||
|
||||
if type == 'tree':
|
||||
user = Transaction().user
|
||||
width, _ = Transaction().context.get('screen_size', (None, None))
|
||||
if Transaction().context.get('view_tree_width'):
|
||||
ViewTreeWidth = pool.get('ir.ui.view_tree_width')
|
||||
@@ -502,11 +501,7 @@ class ModelView(Model):
|
||||
|
||||
if view_id:
|
||||
ViewTreeOptional = pool.get('ir.ui.view_tree_optional')
|
||||
viewtreeoptionals = ViewTreeOptional.search([
|
||||
('view', '=', view_id),
|
||||
('user', '=', user),
|
||||
])
|
||||
fields_optional = {o.field: o.value for o in viewtreeoptionals}
|
||||
fields_optional = ViewTreeOptional.get_optional(view_id)
|
||||
|
||||
fields_def = cls.__parse_fields(
|
||||
tree_root, type,
|
||||
@@ -571,7 +566,7 @@ class ModelView(Model):
|
||||
if fields_width is None:
|
||||
fields_width = collections.defaultdict(list)
|
||||
if fields_optional is None:
|
||||
fields_optional = {}
|
||||
fields_optional = collections.defaultdict(list)
|
||||
if _fields_attrs is None:
|
||||
fields_attrs = {}
|
||||
else:
|
||||
@@ -645,10 +640,13 @@ class ModelView(Model):
|
||||
if width is not None:
|
||||
element.set('width', str(width))
|
||||
if element.get('optional'):
|
||||
if element.get('name') in fields_optional:
|
||||
optional = str(int(
|
||||
fields_optional[element.get('name')]))
|
||||
element.set('optional', optional)
|
||||
try:
|
||||
value = fields_optional[element.get('name')].pop(0)
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
if value is not None:
|
||||
element.set('optional', str(int(value)))
|
||||
|
||||
encoder = PYSONEncoder()
|
||||
if element.tag == 'button':
|
||||
|
||||
Reference in New Issue
Block a user