2008-11-28 16:58:11 +01:00
|
|
|
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
|
|
|
#this repository contains the full copyright notices and license terms.
|
2007-12-10 01:04:13 +01:00
|
|
|
"View"
|
|
|
|
|
from trytond.osv import fields, OSV
|
2008-02-11 14:01:47 +01:00
|
|
|
from difflib import SequenceMatcher
|
2008-04-22 13:54:58 +02:00
|
|
|
import os
|
2008-09-12 19:57:33 +02:00
|
|
|
import logging
|
2008-06-16 23:15:00 +02:00
|
|
|
from lxml import etree
|
2009-02-04 13:30:00 +01:00
|
|
|
from trytond.backend import TableHandler
|
2007-12-10 01:04:13 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class View(OSV):
|
|
|
|
|
"View"
|
|
|
|
|
_name = 'ir.ui.view'
|
|
|
|
|
_description = __doc__
|
2007-12-29 19:02:56 +01:00
|
|
|
_rec_name = 'model'
|
2008-06-10 14:05:37 +02:00
|
|
|
model = fields.Char('Model', required=True, select=1)
|
2008-04-25 13:09:16 +02:00
|
|
|
priority = fields.Integer('Priority', required=True, select=1)
|
2008-04-17 15:01:43 +02:00
|
|
|
type = fields.Selection([
|
2008-10-03 00:59:51 +02:00
|
|
|
(False, ''),
|
2008-10-03 01:02:02 +02:00
|
|
|
('tree','Tree'),
|
|
|
|
|
('form','Form'),
|
|
|
|
|
('graph', 'Graph'),
|
|
|
|
|
('calendar', 'Calendar'),
|
|
|
|
|
('board', 'Board'),
|
|
|
|
|
], 'View Type', select=1)
|
2008-12-10 22:22:17 +01:00
|
|
|
arch = fields.Text('View Architecture')
|
2008-08-19 09:51:02 +02:00
|
|
|
inherit = fields.Many2One('ir.ui.view', 'Inherited View', select=1,
|
|
|
|
|
ondelete='CASCADE')
|
2008-11-28 17:00:14 +01:00
|
|
|
field_childs = fields.Char('Childs Field', states={
|
|
|
|
|
'invisible': "type != 'tree'",
|
|
|
|
|
})
|
2008-06-10 14:05:37 +02:00
|
|
|
module = fields.Char('Module', readonly=True)
|
2008-11-28 16:58:11 +01:00
|
|
|
domain = fields.Char('Domain', states={
|
|
|
|
|
'invisible': "not bool(inherit)",
|
|
|
|
|
})
|
2008-03-04 11:49:32 +01:00
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
super(View, self).__init__()
|
|
|
|
|
self._constraints += [
|
2008-08-05 11:34:45 +02:00
|
|
|
('check_xml', 'invalid_xml'),
|
2008-03-04 11:49:32 +01:00
|
|
|
]
|
2008-08-05 11:34:45 +02:00
|
|
|
self._error_messages.update({
|
2008-10-03 01:00:13 +02:00
|
|
|
'invalid_xml': 'Invalid XML for View!',
|
2008-08-05 11:34:45 +02:00
|
|
|
})
|
2008-04-22 17:47:24 +02:00
|
|
|
self._order.insert(0, ('priority', 'ASC'))
|
2007-12-10 01:04:13 +01:00
|
|
|
|
2009-02-02 13:08:25 +01:00
|
|
|
def init(self, cursor, module_name):
|
|
|
|
|
super(View, self).init(cursor, module_name)
|
2009-02-04 13:30:00 +01:00
|
|
|
table = TableHandler(cursor, self._table, self._name, module_name)
|
2008-12-10 22:22:17 +01:00
|
|
|
|
|
|
|
|
# Migration from 1.0 arch no more required
|
2009-02-04 13:30:00 +01:00
|
|
|
table.not_null_action('arch', action='remove')
|
2008-12-10 22:22:17 +01:00
|
|
|
|
2008-02-26 16:23:05 +01:00
|
|
|
def default_arch(self, cursor, user, context=None):
|
2008-04-22 13:54:58 +02:00
|
|
|
return '<?xml version="1.0"?>'
|
2008-02-26 16:23:05 +01:00
|
|
|
|
|
|
|
|
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 ''
|
|
|
|
|
|
2008-02-15 15:48:41 +01:00
|
|
|
def check_xml(self, cursor, user, ids):
|
2007-12-10 01:04:13 +01:00
|
|
|
"Check XML"
|
2008-02-11 14:01:47 +01:00
|
|
|
views = self.browse(cursor, user, ids)
|
|
|
|
|
for view in views:
|
2008-11-14 01:07:35 +01:00
|
|
|
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
|
2008-12-10 22:22:17 +01:00
|
|
|
if not view.arch:
|
|
|
|
|
continue
|
2008-06-16 23:15:00 +02:00
|
|
|
xml = view.arch.strip()
|
2008-12-10 22:22:17 +01:00
|
|
|
if not xml:
|
|
|
|
|
continue
|
2008-06-17 13:05:52 +02:00
|
|
|
tree = etree.fromstring(xml)
|
|
|
|
|
|
2008-06-17 13:17:48 +02:00
|
|
|
# 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):
|
2008-09-12 19:57:33 +02:00
|
|
|
logger = logging.getLogger('ir')
|
2008-06-17 13:05:52 +02:00
|
|
|
error_log = reduce(lambda x, y: str(x) + '\n' + str(y),
|
2008-06-17 13:17:48 +02:00
|
|
|
validator.error_log.filter_from_errors())
|
2008-09-12 19:57:33 +02:00
|
|
|
logger.error(
|
|
|
|
|
'Invalid xml view:\n%s' % (str(error_log) + '\n' + xml))
|
2008-06-17 13:05:52 +02:00
|
|
|
return False
|
2008-06-16 23:15:00 +02:00
|
|
|
root_element = tree.getroottree().getroot()
|
|
|
|
|
strings = self._translate_view(root_element)
|
2008-02-11 14:01:47 +01:00
|
|
|
view_ids = self.search(cursor, 0, [
|
|
|
|
|
('model', '=', view.model),
|
|
|
|
|
('id', '!=', view.id),
|
2008-11-01 22:26:31 +01:00
|
|
|
('module', '=', view.module),
|
2008-02-11 14:01:47 +01:00
|
|
|
])
|
|
|
|
|
for view2 in self.browse(cursor, 0, view_ids):
|
2008-06-16 23:15:00 +02:00
|
|
|
tree2 = etree.fromstring(view2.arch)
|
|
|
|
|
root2_element = tree2.getroottree().getroot()
|
|
|
|
|
strings += self._translate_view(root2_element)
|
2008-02-11 14:01:47 +01:00
|
|
|
if not strings:
|
|
|
|
|
continue
|
|
|
|
|
for string in {}.fromkeys(strings).keys():
|
|
|
|
|
done = False
|
2008-11-14 01:07:35 +01:00
|
|
|
if string in trans_views:
|
|
|
|
|
del trans_views[string]
|
2008-02-11 14:01:47 +01:00
|
|
|
continue
|
2008-11-14 01:07:35 +01:00
|
|
|
for string_trans in trans_views:
|
2008-11-13 16:00:39 +01:00
|
|
|
if string_trans in strings:
|
|
|
|
|
continue
|
2008-02-11 14:01:47 +01:00
|
|
|
seqmatch = SequenceMatcher(lambda x: x == ' ',
|
|
|
|
|
string, string_trans)
|
|
|
|
|
if seqmatch.ratio() == 1.0:
|
2008-11-14 01:07:35 +01:00
|
|
|
del trans_views[string_trans]
|
2008-02-11 14:01:47 +01:00
|
|
|
done = True
|
|
|
|
|
break
|
|
|
|
|
if seqmatch.ratio() > 0.6:
|
|
|
|
|
cursor.execute('UPDATE ir_translation ' \
|
|
|
|
|
'SET src = %s, ' \
|
|
|
|
|
'fuzzy = True ' \
|
2008-12-05 18:30:54 +01:00
|
|
|
'WHERE id = %s ',
|
|
|
|
|
(string, trans_views[string_trans]['id']))
|
2008-11-14 01:07:35 +01:00
|
|
|
del trans_views[string_trans]
|
2008-02-11 14:01:47 +01:00
|
|
|
done = True
|
|
|
|
|
break
|
|
|
|
|
if not done:
|
|
|
|
|
cursor.execute('INSERT INTO ir_translation ' \
|
2008-07-30 12:26:32 +02:00
|
|
|
'(name, lang, type, src, value, module, fuzzy)' \
|
|
|
|
|
'VALUES (%s, %s, %s, %s, %s, %s, false)',
|
2008-02-11 14:01:47 +01:00
|
|
|
(view.model, 'en_US', 'view', string, '',
|
|
|
|
|
view.module))
|
2008-06-10 14:05:37 +02:00
|
|
|
if strings:
|
|
|
|
|
cursor.execute('DELETE FROM ir_translation ' \
|
|
|
|
|
'WHERE name = %s ' \
|
|
|
|
|
'AND type = %s ' \
|
2008-11-01 22:26:31 +01:00
|
|
|
'AND module = %s ' \
|
2008-06-10 14:05:37 +02:00
|
|
|
'AND src NOT IN ' \
|
|
|
|
|
'(' + ','.join(['%s' for x in strings]) + ')',
|
2008-11-01 22:26:31 +01:00
|
|
|
(view.model, 'view', view.module) + tuple(strings))
|
2007-12-10 01:04:13 +01:00
|
|
|
return True
|
|
|
|
|
|
2008-07-25 12:49:00 +02:00
|
|
|
def delete(self, cursor, user, ids, context=None):
|
2009-01-22 12:17:10 +01:00
|
|
|
res = super(View, self).delete(cursor, user, ids, context=context)
|
2008-12-11 19:27:16 +01:00
|
|
|
# Restart the cache
|
|
|
|
|
for model in self.pool.object_name_list():
|
2007-12-27 23:42:01 +01:00
|
|
|
try:
|
2008-12-11 19:27:16 +01:00
|
|
|
self.pool.get(model).fields_view_get(cursor.dbname)
|
2007-12-27 23:42:01 +01:00
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
def create(self, cursor, user, vals, context=None):
|
|
|
|
|
res = super(View, self).create(cursor, user, vals, context=context)
|
2008-12-11 19:27:16 +01:00
|
|
|
# Restart the cache
|
|
|
|
|
for model in self.pool.object_name_list():
|
2007-12-27 23:42:01 +01:00
|
|
|
try:
|
2008-04-24 17:20:41 +02:00
|
|
|
self.pool.get(model).fields_view_get(cursor.dbname)
|
2007-12-27 23:42:01 +01:00
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
def write(self, cursor, user, ids, vals, context=None):
|
|
|
|
|
res = super(View, self).write(cursor, user, ids, vals, context=context)
|
2008-12-11 19:27:16 +01:00
|
|
|
# Restart the cache
|
|
|
|
|
for model in self.pool.object_name_list():
|
2007-12-27 23:42:01 +01:00
|
|
|
try:
|
2008-12-11 19:27:16 +01:00
|
|
|
self.pool.get(model).fields_view_get(cursor.dbname)
|
2007-12-27 23:42:01 +01:00
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
return res
|
|
|
|
|
|
2008-06-16 23:15:00 +02:00
|
|
|
def _translate_view(self, element):
|
2008-02-11 14:01:47 +01:00
|
|
|
strings = []
|
2009-01-10 14:52:53 +01:00
|
|
|
for attr in ('string', 'sum', 'confirm', 'help'):
|
2008-12-30 14:12:22 +01:00
|
|
|
if element.get(attr):
|
|
|
|
|
string = element.get(attr)
|
|
|
|
|
if string:
|
|
|
|
|
strings.append(string)
|
2008-06-16 23:15:00 +02:00
|
|
|
for child in element:
|
2008-02-11 14:01:47 +01:00
|
|
|
strings.extend(self._translate_view(child))
|
|
|
|
|
return strings
|
|
|
|
|
|
2007-12-10 01:04:13 +01:00
|
|
|
View()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ViewShortcut(OSV):
|
|
|
|
|
"View shortcut"
|
|
|
|
|
_name = 'ir.ui.view_sc'
|
|
|
|
|
_description = __doc__
|
2008-06-10 14:05:37 +02:00
|
|
|
name = fields.Char('Shortcut Name', required=True)
|
2008-02-26 12:40:40 +01:00
|
|
|
res_id = fields.Integer('Resource Ref.', required=True)
|
|
|
|
|
sequence = fields.Integer('Sequence')
|
|
|
|
|
user_id = fields.Many2One('res.user', 'User Ref.', required=True,
|
|
|
|
|
ondelete='cascade')
|
2008-06-10 14:05:37 +02:00
|
|
|
resource = fields.Char('Resource Name', required=True)
|
2007-12-10 01:04:13 +01:00
|
|
|
|
2008-03-03 18:30:39 +01:00
|
|
|
def __init__(self):
|
|
|
|
|
super(ViewShortcut, self).__init__()
|
2008-03-03 19:10:57 +01:00
|
|
|
self._rpc_allowed.append('get_sc')
|
2008-04-22 17:47:24 +02:00
|
|
|
self._order.insert(0, ('sequence', 'ASC'))
|
2008-02-15 18:26:51 +01:00
|
|
|
|
2007-12-10 01:04:13 +01:00
|
|
|
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)
|
|
|
|
|
|
2008-02-26 16:23:05 +01:00
|
|
|
def default_resource(self, cursor, user, context=None):
|
|
|
|
|
return 'ir.ui.menu'
|
2007-12-10 01:04:13 +01:00
|
|
|
|
|
|
|
|
ViewShortcut()
|
2008-06-26 19:03:04 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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',
|
|
|
|
|
]
|
|
|
|
|
|
2008-07-25 12:49:00 +02:00
|
|
|
def delete(self, cursor, user, ids, context=None):
|
2008-06-26 19:03:04 +02:00
|
|
|
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
|
2008-07-25 12:49:00 +02:00
|
|
|
res = super(ViewTreeWidth, self).delete(cursor, user, ids, context=context)
|
2008-06-26 19:03:04 +02:00
|
|
|
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)
|
2008-07-25 12:49:00 +02:00
|
|
|
self.delete(cursor, user, ids, context=context)
|
2008-06-26 19:03:04 +02:00
|
|
|
|
|
|
|
|
for field in fields.keys():
|
|
|
|
|
self.create(cursor, user, {
|
|
|
|
|
'model': model,
|
|
|
|
|
'field': field,
|
|
|
|
|
'user': user,
|
|
|
|
|
'width': fields[field],
|
|
|
|
|
}, context=context)
|
|
|
|
|
|
|
|
|
|
ViewTreeWidth()
|