Modify some styles of the SAO of Tryton to match the style of Naliia
This commit is contained in:
@@ -6,6 +6,7 @@ ARG TRYTOND_VERSION
|
||||
ENV SERIES=${TRYTOND_VERSION}
|
||||
RUN npm install -g bower
|
||||
RUN curl https://downloads.tryton.org/${SERIES}/tryton-sao-last.tgz | tar zxf - -C /
|
||||
COPY naliia_sao_custom/sao.less /package/src/sao.less
|
||||
RUN cd /package && bower install --allow-root
|
||||
|
||||
FROM python:3.11-bullseye
|
||||
@@ -15,4 +16,4 @@ RUN apt-get update && apt-get install -y postgresql-client
|
||||
|
||||
# TOMADO DE: https://hg.tryton.org/tryton-docker/file/tip/6.6/Dockerfile
|
||||
COPY --from=builder-node /package /var/lib/trytond/www
|
||||
|
||||
COPY naliia_sao_custom/ /var/lib/trytond/www/
|
||||
|
||||
11051
naliia_sao_custom/custom.css
Normal file
11051
naliia_sao_custom/custom.css
Normal file
File diff suppressed because it is too large
Load Diff
9
naliia_sao_custom/custom.js
Normal file
9
naliia_sao_custom/custom.js
Normal file
@@ -0,0 +1,9 @@
|
||||
//Mudar cores dos ícones do menu lateral
|
||||
Sao.config.icon_colors = {
|
||||
'toolbar_icons':'white',
|
||||
'default':'#487b50'
|
||||
};
|
||||
|
||||
//Configuração do título do programa (canto superior esquerdo)
|
||||
Sao.config.bug_url = 'https://ecovida.org.br';
|
||||
Sao.config.title = 'RedeEcovida';
|
||||
BIN
naliia_sao_custom/images/body_background.png
Normal file
BIN
naliia_sao_custom/images/body_background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
naliia_sao_custom/images/tryton-icon.png
Normal file
BIN
naliia_sao_custom/images/tryton-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
80
naliia_sao_custom/images/tryton-menu.svg
Normal file
80
naliia_sao_custom/images/tryton-menu.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 22 KiB |
3010
naliia_sao_custom/model.js
Normal file
3010
naliia_sao_custom/model.js
Normal file
File diff suppressed because it is too large
Load Diff
1637
naliia_sao_custom/sao.less
Normal file
1637
naliia_sao_custom/sao.less
Normal file
File diff suppressed because it is too large
Load Diff
881
naliia_sao_custom/view.py
Normal file
881
naliia_sao_custom/view.py
Normal file
@@ -0,0 +1,881 @@
|
||||
# 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 json
|
||||
import os
|
||||
|
||||
from lxml import etree
|
||||
from sql import Literal, Null
|
||||
|
||||
from trytond.cache import Cache, MemoryCache
|
||||
from trytond.i18n import gettext
|
||||
from trytond.model import Index, ModelSQL, ModelView, fields, sequence_ordered
|
||||
from trytond.model.exceptions import ValidationError
|
||||
from trytond.pool import Pool
|
||||
from trytond.pyson import PYSON, Bool, Eval, If, PYSONDecoder
|
||||
from trytond.rpc import RPC
|
||||
from trytond.tools import file_open
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.wizard import Button, StateView, Wizard
|
||||
|
||||
from ..action import DomainError, ViewError
|
||||
|
||||
# Numbers taken from Bootstrap's breakpoints
|
||||
WIDTH_BREAKPOINTS = [
|
||||
1400,
|
||||
1200,
|
||||
992,
|
||||
768,
|
||||
576,
|
||||
]
|
||||
|
||||
|
||||
class XMLError(ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class View(
|
||||
fields.fmany2one(
|
||||
'model_ref', 'model', 'ir.model,name', "Model",
|
||||
ondelete='CASCADE'),
|
||||
fields.fmany2one(
|
||||
'field_children', 'field_childs,model',
|
||||
'ir.model.field,name,model',
|
||||
"Children Field",
|
||||
domain=[
|
||||
('model', '=', Eval('model')),
|
||||
],
|
||||
states={
|
||||
'invisible': Eval('type') != 'tree',
|
||||
}),
|
||||
fields.fmany2one(
|
||||
'module_ref', 'module', 'ir.module,name', "Module",
|
||||
readonly=True, ondelete='CASCADE'),
|
||||
sequence_ordered('priority', "Priority"), ModelSQL, ModelView):
|
||||
"View"
|
||||
__name__ = 'ir.ui.view'
|
||||
model = fields.Char('Model', states={
|
||||
'required': Eval('type') != 'board',
|
||||
})
|
||||
type = fields.Selection([
|
||||
(None, ''),
|
||||
('tree', 'Tree'),
|
||||
('form', 'Form'),
|
||||
('graph', 'Graph'),
|
||||
('calendar', 'Calendar'),
|
||||
('board', 'Board'),
|
||||
('list-form', "List Form"),
|
||||
], 'View Type',
|
||||
domain=[
|
||||
If(Bool(Eval('inherit')),
|
||||
('type', '=', None),
|
||||
('type', '!=', None)),
|
||||
],
|
||||
depends=['inherit'])
|
||||
type_string = type.translated('type')
|
||||
data = fields.Text('Data')
|
||||
name = fields.Char('Name', states={
|
||||
'invisible': ~(Eval('module') & Eval('name')),
|
||||
}, depends=['module'], readonly=True)
|
||||
arch = fields.Function(fields.Text('View Architecture', states={
|
||||
'readonly': Bool(Eval('name')),
|
||||
}, depends=['name']), 'get_arch', setter='set_arch')
|
||||
basis = fields.Function(fields.Boolean("Basis"), 'get_basis')
|
||||
inherit = fields.Many2One('ir.ui.view', 'Inherited View',
|
||||
ondelete='CASCADE')
|
||||
extensions = fields.One2Many(
|
||||
'ir.ui.view', 'inherit', "Extensions",
|
||||
filter=[
|
||||
('basis', '=', False),
|
||||
],
|
||||
domain=[
|
||||
('model', '=', Eval('model')),
|
||||
('type', '=', None),
|
||||
],
|
||||
states={
|
||||
'invisible': ~Eval('type'),
|
||||
},
|
||||
order=[('id', None)])
|
||||
field_childs = fields.Char('Children Field', states={
|
||||
'invisible': Eval('type') != 'tree',
|
||||
}, depends=['type'])
|
||||
module = fields.Char('Module', states={
|
||||
'invisible': ~Eval('module'),
|
||||
}, readonly=True)
|
||||
domain = fields.Char('Domain', states={
|
||||
'invisible': ~Eval('inherit'),
|
||||
}, depends=['inherit'])
|
||||
_get_rng_cache = MemoryCache('ir_ui_view.get_rng', context=False)
|
||||
_view_get_cache = Cache('ir_ui_view.view_get')
|
||||
__module_index = None
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
table = cls.__table__()
|
||||
cls.priority.required = True
|
||||
|
||||
cls.__rpc__['view_get'] = RPC(instantiate=0, cache=dict(days=1))
|
||||
cls._buttons.update({
|
||||
'show': {
|
||||
'readonly': Eval('type') != 'form',
|
||||
'invisible': ~Eval('basis', False),
|
||||
'depends': ['type', 'basis'],
|
||||
},
|
||||
})
|
||||
cls._sql_indexes.update({
|
||||
Index(table,
|
||||
(table.model, Index.Equality()),
|
||||
(table.inherit, Index.Range())),
|
||||
Index(
|
||||
table,
|
||||
(table.id, Index.Range()),
|
||||
(table.inherit, Index.Range())),
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def default_priority():
|
||||
return 16
|
||||
|
||||
@staticmethod
|
||||
def default_module():
|
||||
return Transaction().context.get('module')
|
||||
|
||||
def get_basis(self, name):
|
||||
return not self.inherit or self.model != self.inherit.model
|
||||
|
||||
@classmethod
|
||||
def domain_basis(cls, domain, tables):
|
||||
table, _ = tables[None]
|
||||
if 'inherit' not in tables:
|
||||
inherit = cls.__table__()
|
||||
tables['inherit'] = {
|
||||
None: (inherit, table.inherit == inherit.id),
|
||||
}
|
||||
else:
|
||||
inherit, _ = tables['inherit'][None]
|
||||
expression = (table.inherit == Null) | (table.model != inherit.model)
|
||||
|
||||
_, operator, value = domain
|
||||
if operator in {'=', '!='}:
|
||||
if (operator == '=') != value:
|
||||
expression = ~expression
|
||||
elif operator in {'in', 'not in'}:
|
||||
if True in value and False not in value:
|
||||
pass
|
||||
elif False in value and True not in value:
|
||||
expression = ~expression
|
||||
else:
|
||||
expression = Literal(True)
|
||||
else:
|
||||
expression = Literal(True)
|
||||
return expression
|
||||
|
||||
def get_rec_name(self, name):
|
||||
return ' '.join(filter(None, [
|
||||
self.model_ref.rec_name if self.model_ref else '',
|
||||
'(%s)' % (
|
||||
self.inherit.rec_name if self.inherit else
|
||||
self.type_string),
|
||||
]))
|
||||
|
||||
@classmethod
|
||||
def search_rec_name(cls, name, clause):
|
||||
return [('model_ref.rec_name', *clause[1:])]
|
||||
|
||||
@classmethod
|
||||
@ModelView.button_action('ir.act_view_show')
|
||||
def show(cls, views):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_rng(cls, type_):
|
||||
key = (cls.__name__, type_)
|
||||
rng = cls._get_rng_cache.get(key)
|
||||
if rng is None:
|
||||
if type_ == 'list-form':
|
||||
type_ = 'form'
|
||||
rng_name = os.path.join(os.path.dirname(__file__), type_ + '.rng')
|
||||
with open(rng_name, 'rb') as fp:
|
||||
rng = etree.fromstring(fp.read())
|
||||
cls._get_rng_cache.set(key, rng)
|
||||
return rng
|
||||
|
||||
@property
|
||||
def rng_type(self):
|
||||
if self.inherit:
|
||||
return self.inherit.rng_type
|
||||
return self.type
|
||||
|
||||
@classmethod
|
||||
def validate(cls, views):
|
||||
super().validate(views)
|
||||
cls.check_xml(views)
|
||||
|
||||
@classmethod
|
||||
def check_xml(cls, views):
|
||||
"Check XML"
|
||||
for view in views:
|
||||
if not view.arch:
|
||||
continue
|
||||
xml = view.arch.strip()
|
||||
if not xml:
|
||||
continue
|
||||
tree = etree.fromstring(xml)
|
||||
|
||||
if hasattr(etree, 'RelaxNG'):
|
||||
validator = etree.RelaxNG(etree=cls.get_rng(view.rng_type))
|
||||
if not validator.validate(tree):
|
||||
error_log = '\n'.join(map(str,
|
||||
validator.error_log.filter_from_errors()))
|
||||
raise XMLError(
|
||||
gettext('ir.msg_view_invalid_xml', name=view.rec_name),
|
||||
error_log)
|
||||
root_element = tree.getroottree().getroot()
|
||||
|
||||
# validate pyson attributes
|
||||
validates = {
|
||||
'states': fields.states_validate,
|
||||
}
|
||||
|
||||
def encode(element):
|
||||
for attr in ('states', 'domain', 'spell'):
|
||||
if not element.get(attr):
|
||||
continue
|
||||
try:
|
||||
value = PYSONDecoder().decode(element.get(attr))
|
||||
validates.get(attr, lambda a: True)(value)
|
||||
except Exception as e:
|
||||
error_log = '%s: <%s %s="%s"/>' % (
|
||||
e, element.get('id') or element.get('name'), attr,
|
||||
element.get(attr))
|
||||
raise XMLError(
|
||||
gettext(
|
||||
'ir.msg_view_invalid_xml', name=view.rec_name),
|
||||
error_log) from e
|
||||
for child in element:
|
||||
encode(child)
|
||||
encode(root_element)
|
||||
|
||||
def get_arch(self, name):
|
||||
value = None
|
||||
if self.name and self.module:
|
||||
path = os.path.join(self.module, 'view', self.name + '.xml')
|
||||
try:
|
||||
with file_open(path,
|
||||
subdir='modules', mode='r', encoding='utf-8') as fp:
|
||||
value = fp.read()
|
||||
except IOError:
|
||||
pass
|
||||
if not value:
|
||||
value = self.data
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def set_arch(cls, views, name, value):
|
||||
cls.write(views, {'data': value})
|
||||
|
||||
@classmethod
|
||||
def on_modification(cls, mode, records, field_names=None):
|
||||
super().on_modification(mode, records, field_names=field_names)
|
||||
cls._view_get_cache.clear()
|
||||
ModelView._fields_view_get_cache.clear()
|
||||
|
||||
@property
|
||||
def _module_index(self):
|
||||
from trytond.modules import create_graph, get_modules
|
||||
if self.__class__.__module_index is None:
|
||||
graph = create_graph(get_modules(with_test=Pool.test))
|
||||
modules = [m.name for m in graph]
|
||||
self.__class__.__module_index = {
|
||||
m: i for i, m in enumerate(reversed(modules))}
|
||||
return self.__class__.__module_index
|
||||
|
||||
def view_get(self, model=None):
|
||||
key = (self.id, model)
|
||||
result = self._view_get_cache.get(key)
|
||||
if result:
|
||||
return result
|
||||
if self.inherit:
|
||||
if self.inherit.model == model:
|
||||
return self.inherit.view_get(model=model)
|
||||
else:
|
||||
arch = self.inherit.view_get(self.inherit.model)['arch']
|
||||
else:
|
||||
arch = self.arch
|
||||
|
||||
views = self.__class__.search(['OR', [
|
||||
('inherit', '=', self.id),
|
||||
('model', '=', model),
|
||||
], [
|
||||
('id', '=', self.id),
|
||||
('inherit', '!=', None),
|
||||
],
|
||||
])
|
||||
views.sort(
|
||||
key=lambda v: self._module_index.get(v.module, -1), reverse=True)
|
||||
parser = etree.XMLParser(remove_comments=True, resolve_entities=False)
|
||||
tree = etree.fromstring(arch, parser=parser)
|
||||
decoder = PYSONDecoder({'context': Transaction().context})
|
||||
for view in views:
|
||||
if view.domain and not decoder.decode(view.domain):
|
||||
continue
|
||||
if not view.arch or not view.arch.strip():
|
||||
continue
|
||||
tree_inherit = etree.fromstring(view.arch, parser=parser)
|
||||
tree = self.inherit_apply(tree, tree_inherit)
|
||||
if model:
|
||||
root = tree.getroottree().getroot()
|
||||
self._translate(root, model, Transaction().language)
|
||||
arch = etree.tostring(tree, encoding='utf-8').decode('utf-8')
|
||||
result = {
|
||||
'type': self.rng_type,
|
||||
'view_id': self.id,
|
||||
'arch': arch,
|
||||
'field_childs': self.field_childs,
|
||||
}
|
||||
self._view_get_cache.set(key, result)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def inherit_apply(cls, tree, inherit):
|
||||
root_inherit = inherit.getroottree().getroot()
|
||||
for element in root_inherit:
|
||||
expr = element.get('expr')
|
||||
targets = tree.xpath(expr)
|
||||
assert targets, "No elements found for expression %r" % expr
|
||||
for target in targets:
|
||||
position = element.get('position', 'inside')
|
||||
new_tree = getattr(cls, '_inherit_apply_%s' % position)(
|
||||
tree, element, target)
|
||||
if new_tree:
|
||||
tree = new_tree
|
||||
return tree
|
||||
|
||||
@classmethod
|
||||
def _inherit_apply_replace(cls, tree, element, target):
|
||||
parent = target.getparent()
|
||||
if parent is None:
|
||||
tree, = element
|
||||
return tree
|
||||
cls._inherit_apply_after(tree, element, target)
|
||||
parent.remove(target)
|
||||
|
||||
@classmethod
|
||||
def _inherit_apply_replace_attributes(cls, tree, element, target):
|
||||
child, = element
|
||||
for attr in child.attrib:
|
||||
target.set(attr, child.get(attr))
|
||||
|
||||
@classmethod
|
||||
def _inherit_apply_inside(cls, tree, element, target):
|
||||
target.extend(list(element))
|
||||
|
||||
@classmethod
|
||||
def _inherit_apply_after(cls, tree, element, target):
|
||||
parent = target.getparent()
|
||||
next_ = target.getnext()
|
||||
if next_ is not None:
|
||||
for child in element:
|
||||
index = parent.index(next_)
|
||||
parent.insert(index, child)
|
||||
else:
|
||||
parent.extend(list(element))
|
||||
|
||||
@classmethod
|
||||
def _inherit_apply_before(cls, tree, element, target):
|
||||
parent = target.getparent()
|
||||
for child in element:
|
||||
index = parent.index(target)
|
||||
parent.insert(index, child)
|
||||
|
||||
@classmethod
|
||||
def _translate(cls, element, model, language):
|
||||
pool = Pool()
|
||||
Translation = pool.get('ir.translation')
|
||||
for attr in ['string', 'sum', 'confirm', 'help']:
|
||||
if element.get(attr):
|
||||
translation = Translation.get_source(
|
||||
model, 'view', language, element.get(attr))
|
||||
if translation:
|
||||
element.set(attr, translation)
|
||||
for child in element:
|
||||
cls._translate(child, model, language)
|
||||
|
||||
|
||||
class ShowViewStart(ModelView):
|
||||
'Show view'
|
||||
__name__ = 'ir.ui.view.show.start'
|
||||
__no_slots__ = True
|
||||
|
||||
|
||||
class ShowView(Wizard):
|
||||
'Show view'
|
||||
__name__ = 'ir.ui.view.show'
|
||||
|
||||
class ShowStateView(StateView):
|
||||
|
||||
def __init__(self, model_name, buttons):
|
||||
StateView.__init__(self, model_name, None, buttons)
|
||||
|
||||
def get_view(self, wizard, state_name):
|
||||
pool = Pool()
|
||||
View = pool.get('ir.ui.view')
|
||||
view_id = Transaction().context.get('active_id')
|
||||
if not view_id:
|
||||
# Set type to please ModuleTestCase.test_wizards
|
||||
return {'type': 'form'}
|
||||
view = View(view_id)
|
||||
Model = pool.get(view.model)
|
||||
return Model.fields_view_get(view_id=view.id)
|
||||
|
||||
def get_defaults(self, wizard, state_name, fields):
|
||||
return {}
|
||||
|
||||
start = ShowStateView('ir.ui.view.show.start', [
|
||||
Button('Close', 'end', 'tryton-close', default=True),
|
||||
])
|
||||
|
||||
|
||||
class ViewTreeWidth(
|
||||
fields.fmany2one(
|
||||
'model_ref', 'model', 'ir.model,name', "Model",
|
||||
required=True, ondelete='CASCADE'),
|
||||
fields.fmany2one(
|
||||
'field_ref', 'field,model', 'ir.model.field,name,model', "Field",
|
||||
required=True, ondelete='CASCADE',
|
||||
domain=[
|
||||
('model', '=', Eval('model')),
|
||||
]),
|
||||
ModelSQL, ModelView):
|
||||
"View Tree Width"
|
||||
__name__ = 'ir.ui.view_tree_width'
|
||||
model = fields.Char('Model', required=True)
|
||||
field = fields.Char('Field', required=True)
|
||||
user = fields.Many2One('res.user', 'User', required=True,
|
||||
ondelete='CASCADE')
|
||||
screen_width = fields.Integer("Screen Width")
|
||||
width = fields.Integer('Width', required=True)
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
table = cls.__table__()
|
||||
cls.__rpc__.update({
|
||||
'set_width': RPC(readonly=False),
|
||||
'reset_width': RPC(readonly=False),
|
||||
})
|
||||
cls._sql_indexes.add(
|
||||
Index(
|
||||
table,
|
||||
(table.user, Index.Range()),
|
||||
(table.model, Index.Equality()),
|
||||
(table.field, Index.Equality())))
|
||||
|
||||
def get_rec_name(self, name):
|
||||
return f'{self.field_ref.rec_name} @ {self.model_ref.rec_name}'
|
||||
|
||||
@classmethod
|
||||
def search_rec_name(cls, name, clause):
|
||||
operator = clause[1]
|
||||
if operator.startswith('!') or operator.startswith('not '):
|
||||
bool_op = 'AND'
|
||||
else:
|
||||
bool_op = 'OR'
|
||||
return [bool_op,
|
||||
('model_ref.rec_name', *clause[1:]),
|
||||
('field_ref.rec_name', *clause[1:]),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def on_modification(cls, mode, records, field_names=None):
|
||||
super().on_modification(mode, records, field_names=field_names)
|
||||
ModelView._fields_view_get_cache.clear()
|
||||
|
||||
@classmethod
|
||||
def get_width(cls, model, width):
|
||||
for screen_width in WIDTH_BREAKPOINTS:
|
||||
if width >= screen_width:
|
||||
break
|
||||
else:
|
||||
screen_width = 0
|
||||
|
||||
user = Transaction().user
|
||||
records = cls.search([
|
||||
('user', '=', user),
|
||||
('model', '=', model),
|
||||
('screen_width', '=', screen_width),
|
||||
])
|
||||
|
||||
if not records:
|
||||
records = cls.search([
|
||||
('user', '=', user),
|
||||
('model', '=', model),
|
||||
['OR',
|
||||
('screen_width', '<=', screen_width),
|
||||
('screen_width', '=', None),
|
||||
],
|
||||
],
|
||||
order=[
|
||||
('screen_width', 'DESC NULLS LAST'),
|
||||
])
|
||||
widths = {}
|
||||
for width in records:
|
||||
if width.field not in widths:
|
||||
widths[width.field] = width.width
|
||||
return widths
|
||||
|
||||
@classmethod
|
||||
def set_width(cls, model, fields, width):
|
||||
'''
|
||||
Set width for the current user on the model.
|
||||
fields is a dictionary with key: field name and value: width.
|
||||
'''
|
||||
for screen_width in WIDTH_BREAKPOINTS:
|
||||
if width >= screen_width:
|
||||
break
|
||||
else:
|
||||
screen_width = 0
|
||||
|
||||
user_id = Transaction().user
|
||||
records = cls.search([
|
||||
('user', '=', user_id),
|
||||
('model', '=', model),
|
||||
('field', 'in', list(fields.keys())),
|
||||
['OR',
|
||||
('screen_width', '=', screen_width),
|
||||
('screen_width', '=', None),
|
||||
],
|
||||
])
|
||||
|
||||
fields = fields.copy()
|
||||
to_save, to_delete = [], []
|
||||
for tree_width in records:
|
||||
if tree_width.screen_width == screen_width:
|
||||
if tree_width.field in fields:
|
||||
tree_width.width = fields.pop(tree_width.field)
|
||||
to_save.append(tree_width)
|
||||
else:
|
||||
to_delete.append(tree_width)
|
||||
|
||||
for name, width in fields.items():
|
||||
to_save.append(cls(
|
||||
user=user_id,
|
||||
model=model,
|
||||
field=name,
|
||||
screen_width=screen_width,
|
||||
width=width))
|
||||
|
||||
if to_save:
|
||||
cls.save(to_save)
|
||||
if to_delete:
|
||||
cls.delete(to_delete)
|
||||
|
||||
@classmethod
|
||||
def reset_width(cls, model, width):
|
||||
for screen_width in WIDTH_BREAKPOINTS:
|
||||
if width >= screen_width:
|
||||
break
|
||||
else:
|
||||
screen_width = 0
|
||||
|
||||
user_id = Transaction().user
|
||||
records = cls.search([
|
||||
('user', '=', user_id),
|
||||
('model', '=', model),
|
||||
['OR',
|
||||
('screen_width', '=', screen_width),
|
||||
('screen_width', '=', None),
|
||||
],
|
||||
])
|
||||
cls.delete(records)
|
||||
|
||||
|
||||
class ViewTreeOptional(
|
||||
fields.fmany2one(
|
||||
'model_ref', 'model', 'ir.model,name', "Model",
|
||||
required=True, ondelete='CASCADE'),
|
||||
fields.fmany2one(
|
||||
'field_ref', 'field,model', 'ir.model.field,name,model', "Field",
|
||||
required=True, ondelete='CASCADE',
|
||||
domain=[
|
||||
('model', '=', Eval('model')),
|
||||
]),
|
||||
ModelSQL, ModelView):
|
||||
"View Tree Optional"
|
||||
__name__ = 'ir.ui.view_tree_optional'
|
||||
view = fields.Many2One(
|
||||
'ir.ui.view', "View", required=True, ondelete='CASCADE')
|
||||
user = fields.Many2One(
|
||||
'res.user', "User", required=True, ondelete='CASCADE')
|
||||
model = fields.Char("Model", required=True)
|
||||
field = fields.Char("Field", required=True)
|
||||
value = fields.Boolean("Value")
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.__rpc__.update({
|
||||
'set_optional': RPC(readonly=False),
|
||||
})
|
||||
table = cls.__table__()
|
||||
cls._sql_indexes.add(
|
||||
Index(
|
||||
table,
|
||||
(table.user, Index.Range()),
|
||||
(table.view, Index.Range())))
|
||||
|
||||
@classmethod
|
||||
def __register__(cls, module):
|
||||
pool = Pool()
|
||||
View = pool.get('ir.ui.view')
|
||||
table = cls.__table__()
|
||||
view = View.__table__()
|
||||
table_h = cls.__table_handler__(module)
|
||||
cursor = Transaction().connection.cursor()
|
||||
|
||||
# Migration from 7.2: rename view_id into view
|
||||
table_h.column_rename('view_id', 'view')
|
||||
|
||||
super().__register__(module)
|
||||
|
||||
# Migration from 7.2: add model
|
||||
cursor.execute(*table.update(
|
||||
[table.model],
|
||||
[view.select(view.model, where=view.id == table.view)],
|
||||
where=table.model == Null))
|
||||
|
||||
@classmethod
|
||||
def validate_fields(cls, records, fields_names):
|
||||
super().validate_fields(records, fields_names)
|
||||
cls.check_view(records, fields_names)
|
||||
|
||||
@classmethod
|
||||
def check_view(cls, records, fields_names=None):
|
||||
if fields_names and 'view' not in fields_names:
|
||||
return
|
||||
for record in records:
|
||||
if record.view and record.view.rng_type != 'tree':
|
||||
raise ViewError(gettext(
|
||||
'ir.msg_view_tree_optional_type',
|
||||
view=record.view.rec_name))
|
||||
|
||||
@classmethod
|
||||
def on_modification(cls, mode, record, field_names=None):
|
||||
super().on_modification(mode, record, field_names=field_names)
|
||||
ModelView._fields_view_get_cache.clear()
|
||||
|
||||
@classmethod
|
||||
def set_optional(cls, view_id, fields):
|
||||
"Store optional field that must be displayed"
|
||||
pool = Pool()
|
||||
View = pool.get('ir.ui.view')
|
||||
user = Transaction().user
|
||||
view = View(view_id)
|
||||
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)
|
||||
|
||||
|
||||
class ViewTreeState(
|
||||
fields.fmany2one(
|
||||
'model_ref', 'model', 'ir.model,name', "Model",
|
||||
required=True, ondelete='CASCADE'),
|
||||
fields.fmany2one(
|
||||
'child_field', 'child_name,model', 'ir.model.field,name,model',
|
||||
"Child Field", ondelete='CASCADE',
|
||||
domain=[
|
||||
('model', '=', Eval('model')),
|
||||
]),
|
||||
ModelSQL, ModelView):
|
||||
'View Tree State'
|
||||
__name__ = 'ir.ui.view_tree_state'
|
||||
_rec_name = 'model'
|
||||
model = fields.Char('Model', required=True)
|
||||
domain = fields.Char('Domain', required=True)
|
||||
user = fields.Many2One('res.user', 'User', required=True,
|
||||
ondelete='CASCADE')
|
||||
child_name = fields.Char('Child Name')
|
||||
nodes = fields.Text('Expanded Nodes')
|
||||
selected_nodes = fields.Text('Selected Nodes')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.__rpc__.update({
|
||||
'set': RPC(readonly=False, check_access=False),
|
||||
'get': RPC(check_access=False, cache=dict(days=1)),
|
||||
})
|
||||
|
||||
table = cls.__table__()
|
||||
cls._sql_indexes.add(
|
||||
Index(
|
||||
table,
|
||||
(table.user, Index.Range()),
|
||||
(table.model, Index.Equality()),
|
||||
(table.child_name, Index.Equality()),
|
||||
(table.domain, Index.Equality())))
|
||||
|
||||
@staticmethod
|
||||
def default_nodes():
|
||||
return '[]'
|
||||
|
||||
@staticmethod
|
||||
def default_selected_nodes():
|
||||
return '[]'
|
||||
|
||||
@classmethod
|
||||
def set(cls, model, domain, child_name, nodes, selected_nodes):
|
||||
# Normalize the json domain
|
||||
domain = json.dumps(json.loads(domain), separators=(',', ':'))
|
||||
current_user = Transaction().user
|
||||
records = cls.search([
|
||||
('user', '=', current_user),
|
||||
('model', '=', model),
|
||||
('domain', '=', domain),
|
||||
('child_name', '=', child_name),
|
||||
])
|
||||
cls.delete(records)
|
||||
cls.create([{
|
||||
'user': current_user,
|
||||
'model': model,
|
||||
'domain': domain,
|
||||
'child_name': child_name,
|
||||
'nodes': nodes,
|
||||
'selected_nodes': selected_nodes,
|
||||
}])
|
||||
|
||||
@classmethod
|
||||
def get(cls, model, domain, child_name):
|
||||
# Normalize the json domain
|
||||
domain = json.dumps(json.loads(domain), separators=(',', ':'))
|
||||
current_user = Transaction().user
|
||||
try:
|
||||
expanded_info, = cls.search([
|
||||
('user', '=', current_user),
|
||||
('model', '=', model),
|
||||
('domain', '=', domain),
|
||||
('child_name', '=', child_name),
|
||||
],
|
||||
limit=1)
|
||||
except ValueError:
|
||||
return (cls.default_nodes(), cls.default_selected_nodes())
|
||||
state = cls(expanded_info)
|
||||
return (state.nodes or cls.default_nodes(),
|
||||
state.selected_nodes or cls.default_selected_nodes())
|
||||
|
||||
|
||||
class ViewSearch(
|
||||
fields.fmany2one(
|
||||
'model_ref', 'model', 'ir.model,name', "Model",
|
||||
required=True, ondelete='CASCADE'),
|
||||
ModelSQL, ModelView):
|
||||
"View Search"
|
||||
__name__ = 'ir.ui.view_search'
|
||||
|
||||
name = fields.Char('Name', required=True)
|
||||
model = fields.Char('Model', required=True)
|
||||
domain = fields.Char('Domain', help="The PYSON domain.")
|
||||
user = fields.Many2One('res.user', 'User', ondelete='CASCADE')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.__rpc__.update({
|
||||
'get': RPC(check_access=False),
|
||||
'set': RPC(check_access=False, readonly=False),
|
||||
'unset': RPC(check_access=False, readonly=False),
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def default_user():
|
||||
return Transaction().user
|
||||
|
||||
@classmethod
|
||||
def validate_fields(cls, searches, field_names):
|
||||
super().validate_fields(searches, field_names)
|
||||
cls.check_domain(searches, field_names)
|
||||
|
||||
@classmethod
|
||||
def check_domain(cls, searches, field_names):
|
||||
decoder = PYSONDecoder()
|
||||
if field_names and 'domain' not in field_names:
|
||||
return
|
||||
for search in searches:
|
||||
try:
|
||||
value = decoder.decode(search.domain)
|
||||
except Exception as exception:
|
||||
raise DomainError(
|
||||
gettext('ir.msg_view_search_invalid_domain',
|
||||
domain=search.domain,
|
||||
search=search.rec_name)) from exception
|
||||
if isinstance(value, PYSON):
|
||||
if not value.types() == set([list]):
|
||||
raise DomainError(
|
||||
gettext('ir.msg_view_search_invalid_domain',
|
||||
domain=search.domain,
|
||||
search=search.rec_name))
|
||||
elif not isinstance(value, list):
|
||||
raise DomainError(
|
||||
gettext('ir.msg_view_search_invalid_domain',
|
||||
domain=search.domain,
|
||||
search=search.rec_name))
|
||||
else:
|
||||
try:
|
||||
fields.domain_validate(value)
|
||||
except Exception as exception:
|
||||
raise DomainError(
|
||||
gettext('ir.msg_view_search_invalid_domain',
|
||||
domain=search.domain,
|
||||
search=search.rec_name)) from exception
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
decoder = PYSONDecoder()
|
||||
user = Transaction().user
|
||||
searches = cls.search_read(['OR',
|
||||
('user', '=', user),
|
||||
('user', '=', None),
|
||||
],
|
||||
order=[('model', 'ASC'), ('name', 'ASC')],
|
||||
fields_names=['id', 'name', 'model', 'domain', '_delete'])
|
||||
result = {}
|
||||
for search in searches:
|
||||
result.setdefault(search['model'], []).append((
|
||||
search['id'],
|
||||
search['name'],
|
||||
decoder.decode(search['domain']),
|
||||
search['_delete']))
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def set(cls, name, model, domain):
|
||||
user = Transaction().user
|
||||
search, = cls.create([{
|
||||
'name': name,
|
||||
'model': model,
|
||||
'domain': domain,
|
||||
'user': user,
|
||||
}])
|
||||
return search.id
|
||||
|
||||
@classmethod
|
||||
def unset(cls, id):
|
||||
user = Transaction().user
|
||||
cls.delete(cls.search([
|
||||
('id', '=', id),
|
||||
('user', '=', user),
|
||||
]))
|
||||
2882
naliia_sao_custom/window.js
Normal file
2882
naliia_sao_custom/window.js
Normal file
File diff suppressed because it is too large
Load Diff
2
web_app
2
web_app
Submodule web_app updated: 7670838215...49c5fffa01
Reference in New Issue
Block a user