Files
tryton/trytond/ir/ui/view.py
T

304 lines
11 KiB
Python

#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
"View"
from trytond.osv import fields, OSV
from difflib import SequenceMatcher
import os
import logging
from lxml import etree
from trytond.backend import TableHandler
class View(OSV):
"View"
_name = 'ir.ui.view'
_description = __doc__
_rec_name = 'model'
model = fields.Char('Model', required=True, select=1)
priority = fields.Integer('Priority', required=True, select=1)
type = fields.Selection([
(False, ''),
('tree','Tree'),
('form','Form'),
('graph', 'Graph'),
('calendar', 'Calendar'),
('board', 'Board'),
], 'View Type', select=1)
arch = fields.Text('View Architecture')
inherit = fields.Many2One('ir.ui.view', 'Inherited View', select=1,
ondelete='CASCADE')
field_childs = fields.Char('Childs Field', states={
'invisible': "type != 'tree'",
})
module = fields.Char('Module', readonly=True)
domain = fields.Char('Domain', states={
'invisible': "not bool(inherit)",
})
def __init__(self):
super(View, self).__init__()
self._constraints += [
('check_xml', 'invalid_xml'),
]
self._error_messages.update({
'invalid_xml': 'Invalid XML for View!',
})
self._order.insert(0, ('priority', 'ASC'))
def init(self, cursor, module_name):
super(View, self).init(cursor, module_name)
table = TableHandler(cursor, self._table, self._name, module_name)
# Migration from 1.0 arch no more required
table.not_null_action('arch', action='remove')
def default_arch(self, cursor, user, context=None):
return '<?xml version="1.0"?>'
def default_priority(self, cursor, user, context=None):
return 16
def default_module(self, cursor, user, context=None):
return context and context.get('module', '') or ''
def check_xml(self, cursor, user, ids):
"Check XML"
views = self.browse(cursor, user, ids)
for view in views:
cursor.execute('SELECT id, name, src FROM ir_translation ' \
'WHERE lang = %s ' \
'AND type = %s ' \
'AND name = %s '\
'AND module = %s',
('en_US', 'view', view.model, view.module))
trans_views = {}
for trans in cursor.dictfetchall():
trans_views[trans['src']] = trans
if not view.arch:
continue
xml = view.arch.strip()
if not xml:
continue
tree = etree.fromstring(xml)
# validate the tree using RelaxNG
rng_name = os.path.join(os.path.dirname(__file__),
(view.inherit and view.inherit.type or view.type) + '.rng')
if hasattr(etree, 'RelaxNG'):
validator = etree.RelaxNG(file=rng_name)
if not validator.validate(tree):
logger = logging.getLogger('ir')
error_log = reduce(lambda x, y: str(x) + '\n' + str(y),
validator.error_log.filter_from_errors())
logger.error(
'Invalid xml view:\n%s' % (str(error_log) + '\n' + xml))
return False
root_element = tree.getroottree().getroot()
strings = self._translate_view(root_element)
view_ids = self.search(cursor, 0, [
('model', '=', view.model),
('id', '!=', view.id),
('module', '=', view.module),
])
for view2 in self.browse(cursor, 0, view_ids):
tree2 = etree.fromstring(view2.arch)
root2_element = tree2.getroottree().getroot()
strings += self._translate_view(root2_element)
if not strings:
continue
for string in {}.fromkeys(strings).keys():
done = False
if string in trans_views:
del trans_views[string]
continue
for string_trans in trans_views:
if string_trans in strings:
continue
seqmatch = SequenceMatcher(lambda x: x == ' ',
string, string_trans)
if seqmatch.ratio() == 1.0:
del trans_views[string_trans]
done = True
break
if seqmatch.ratio() > 0.6:
cursor.execute('UPDATE ir_translation ' \
'SET src = %s, ' \
'fuzzy = True ' \
'WHERE id = %s ',
(string, trans_views[string_trans]['id']))
del trans_views[string_trans]
done = True
break
if not done:
cursor.execute('INSERT INTO ir_translation ' \
'(name, lang, type, src, value, module, fuzzy)' \
'VALUES (%s, %s, %s, %s, %s, %s, false)',
(view.model, 'en_US', 'view', string, '',
view.module))
if strings:
cursor.execute('DELETE FROM ir_translation ' \
'WHERE name = %s ' \
'AND type = %s ' \
'AND module = %s ' \
'AND src NOT IN ' \
'(' + ','.join(['%s' for x in strings]) + ')',
(view.model, 'view', view.module) + tuple(strings))
return True
def delete(self, cursor, user, ids, context=None):
res = super(View, self).delete(cursor, user, ids, context=context)
# Restart the cache
for model in self.pool.object_name_list():
try:
self.pool.get(model).fields_view_get(cursor.dbname)
except:
pass
return res
def create(self, cursor, user, vals, context=None):
res = super(View, self).create(cursor, user, vals, context=context)
# Restart the cache
for model in self.pool.object_name_list():
try:
self.pool.get(model).fields_view_get(cursor.dbname)
except:
pass
return res
def write(self, cursor, user, ids, vals, context=None):
res = super(View, self).write(cursor, user, ids, vals, context=context)
# Restart the cache
for model in self.pool.object_name_list():
try:
self.pool.get(model).fields_view_get(cursor.dbname)
except:
pass
return res
def _translate_view(self, element):
strings = []
for attr in ('string', 'sum', 'confirm', 'help'):
if element.get(attr):
string = element.get(attr)
if string:
strings.append(string)
for child in element:
strings.extend(self._translate_view(child))
return strings
View()
class ViewShortcut(OSV):
"View shortcut"
_name = 'ir.ui.view_sc'
_description = __doc__
name = fields.Char('Shortcut Name', required=True)
res_id = fields.Integer('Resource Ref.', required=True)
sequence = fields.Integer('Sequence')
user_id = fields.Many2One('res.user', 'User Ref.', required=True,
ondelete='cascade')
resource = fields.Char('Resource Name', required=True)
def __init__(self):
super(ViewShortcut, self).__init__()
self._rpc_allowed.append('get_sc')
self._order.insert(0, ('sequence', 'ASC'))
def get_sc(self, cursor, user, user_id, model='ir.ui.menu', context=None):
"Provide user's shortcuts"
ids = self.search(cursor, user, [
('user_id','=',user_id),
('resource','=',model),
], context=context)
return self.read(cursor, user, ids, ['res_id', 'name'], context=context)
def default_resource(self, cursor, user, context=None):
return 'ir.ui.menu'
ViewShortcut()
class ViewTreeWidth(OSV):
"View Tree Width"
_name = 'ir.ui.view_tree_width'
_description = __doc__
_rec_name = 'model'
model = fields.Char('Model', required=True, select=1)
field = fields.Char('Field', required=True, select=1)
user = fields.Many2One('res.user', 'User', required=True,
ondelete='CASCADE', select=1)
width = fields.Integer('Width')
def __init__(self):
super(ViewTreeWidth, self).__init__()
self._rpc_allowed += [
'set_width',
]
def delete(self, cursor, user, ids, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
views = self.browse(cursor, user, ids, context=context)
for view in views:
# Restart the cache
try:
self.pool.get(view.model).fields_view_get(cursor.dbname)
except:
pass
res = super(ViewTreeWidth, self).delete(cursor, user, ids, context=context)
return res
def create(self, cursor, user, vals, context=None):
res = super(ViewTreeWidth, self).create(cursor, user, vals, context=context)
if 'model' in vals:
model = vals['model']
# Restart the cache
try:
self.pool.get(model).fields_view_get(cursor.dbname)
except:
pass
return res
def write(self, cursor, user, ids, vals, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
views = self.browse(cursor, user, ids)
for view in views:
# Restart the cache
try:
self.pool.get(view.model).fields_view_get(cursor.dbname)
except:
pass
res = super(ViewTreeWidth, self).write(cursor, user, ids, vals, context=context)
views = self.browse(cursor, user, ids)
for view in views:
# Restart the cache
try:
self.pool.get(view.model).fields_view_get(cursor.dbname)
except:
pass
return res
def set_width(self, cursor, user, model, fields, context=None):
'''
Set width for the current user on the model.
fields is a dictionary with key: field name and value: width.
'''
ids = self.search(cursor, user, [
('user', '=', user),
('model', '=', model),
('field', 'in', fields.keys()),
], context=context)
self.delete(cursor, user, ids, context=context)
for field in fields.keys():
self.create(cursor, user, {
'model': model,
'field': field,
'user': user,
'width': fields[field],
}, context=context)
ViewTreeWidth()