Files
tryton/trytond/model/modelview.py
T

728 lines
28 KiB
Python
Raw Normal View History

2014-12-02 12:54:01 +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.
2009-01-29 20:34:26 +01:00
from lxml import etree
2012-03-25 00:15:17 +01:00
from functools import wraps
import copy
2014-10-29 20:38:17 +01:00
import collections
2014-10-29 20:38:17 +01:00
from trytond.model import Model, fields
from trytond.tools import ClassProperty, is_instance_method
2015-03-09 19:40:41 +01:00
from trytond.pyson import PYSONDecoder, PYSONEncoder
2010-08-19 22:01:12 +02:00
from trytond.transaction import Transaction
from trytond.cache import Cache
2011-07-03 13:20:58 +02:00
from trytond.pool import Pool
2012-03-25 00:15:17 +01:00
from trytond.exceptions import UserError
2012-09-24 22:16:53 +02:00
from trytond.rpc import RPC
__all__ = ['ModelView']
2012-03-25 00:15:17 +01:00
2009-01-29 20:34:26 +01:00
def _find(tree, element):
if element.tag == 'xpath':
res = tree.xpath(element.get('expr'))
if res:
return res[0]
return None
2015-12-31 14:13:03 +01:00
def _inherit_apply(tree, inherit):
root_inherit = inherit.getroottree().getroot()
2009-01-29 20:34:26 +01:00
for element2 in root_inherit:
if element2.tag != 'xpath':
continue
2015-12-31 14:13:03 +01:00
element = _find(tree, element2)
2009-01-29 20:34:26 +01:00
if element is not None:
pos = element2.get('position', 'inside')
if pos == 'replace':
parent = element.getparent()
if parent is None:
2015-12-31 14:13:03 +01:00
tree, = element2
continue
2010-02-18 18:50:35 +01:00
enext = element.getnext()
if enext is not None:
2009-01-29 20:34:26 +01:00
for child in element2:
2010-02-18 18:50:35 +01:00
index = parent.index(enext)
2009-01-29 20:34:26 +01:00
parent.insert(index, child)
else:
2015-03-15 15:49:37 +01:00
parent.extend(list(element2))
2009-01-29 20:34:26 +01:00
parent.remove(element)
elif pos == 'replace_attributes':
2015-03-15 15:49:37 +01:00
child = element2[0]
2009-01-29 20:34:26 +01:00
for attr in child.attrib:
element.set(attr, child.get(attr))
elif pos == 'inside':
2015-03-15 15:49:37 +01:00
element.extend(list(element2))
2009-01-29 20:34:26 +01:00
elif pos == 'after':
parent = element.getparent()
2010-02-18 18:50:35 +01:00
enext = element.getnext()
if enext is not None:
2015-03-15 15:49:37 +01:00
for child in list(element2):
2010-02-18 18:50:35 +01:00
index = parent.index(enext)
2009-01-29 20:34:26 +01:00
parent.insert(index, child)
else:
2015-03-15 15:49:37 +01:00
parent.extend(list(element2))
2009-01-29 20:34:26 +01:00
elif pos == 'before':
parent = element.getparent()
2015-03-15 15:49:37 +01:00
for child in list(element2):
2009-01-29 20:34:26 +01:00
index = parent.index(element)
parent.insert(index, child)
else:
2013-02-27 22:50:31 +01:00
raise AttributeError(
'Unknown position in inherited view %s!' % pos)
2009-01-29 20:34:26 +01:00
else:
raise AttributeError(
2013-02-27 22:50:31 +01:00
'Couldn\'t find tag (%s: %s) in parent view!'
% (element2.tag, element2.get('expr')))
2015-12-31 14:13:03 +01:00
return tree
2009-01-29 20:34:26 +01:00
2014-10-29 20:38:17 +01:00
def on_change(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
result = func(self, *args, **kwargs)
assert result is None, func
return self
wrapper.on_change = True
return wrapper
def on_change_result(record):
return record._changed_values
2009-01-29 20:34:26 +01:00
class ModelView(Model):
"""
Define a model with views in Tryton.
"""
2012-04-01 23:08:15 +02:00
__modules_list = None # Cache for the modules list sorted by dependency
2012-09-24 22:16:53 +02:00
_fields_view_get_cache = Cache('modelview.fields_view_get')
_view_toolbar_get_cache = Cache('modelview.view_toolbar_get')
@staticmethod
def _reset_modules_list():
ModelView.__modules_list = None
2012-09-24 22:16:53 +02:00
@ClassProperty
@classmethod
def _modules_list(cls):
from trytond.modules import create_graph, get_module_list
if ModelView.__modules_list:
return ModelView.__modules_list
graph = create_graph(get_module_list())[0]
ModelView.__modules_list = [x.name for x in graph] + [None]
return ModelView.__modules_list
2012-09-24 22:16:53 +02:00
@classmethod
def __setup__(cls):
super(ModelView, cls).__setup__()
cls.__rpc__['fields_view_get'] = RPC()
cls.__rpc__['view_toolbar_get'] = RPC()
2014-10-29 20:38:17 +01:00
cls.__rpc__['on_change'] = RPC(instantiate=0)
cls.__rpc__['on_change_with'] = RPC(instantiate=0)
2012-09-24 22:16:53 +02:00
cls._buttons = {}
2009-01-29 20:34:26 +01:00
2014-10-29 20:38:17 +01:00
if hasattr(cls, '__depend_methods'):
cls.__depend_methods = cls.__depend_methods.copy()
else:
cls.__depend_methods = collections.defaultdict(set)
2015-03-16 12:56:11 +01:00
if hasattr(cls, '__change_buttons'):
cls.__change_buttons = cls.__change_buttons.copy()
else:
cls.__change_buttons = collections.defaultdict(set)
def setup_field(field, field_name):
2014-10-29 20:38:17 +01:00
for attribute in ('on_change', 'on_change_with', 'autocomplete',
'selection_change_with'):
if attribute == 'selection_change_with':
if isinstance(
getattr(field, 'selection', None), basestring):
function_name = field.selection
else:
continue
else:
function_name = '%s_%s' % (attribute, field_name)
if not getattr(cls, function_name, None):
continue
# Search depends on all parent class because field has been
# copied with the original definition
for parent_cls in cls.__mro__:
function = getattr(parent_cls, function_name, None)
if not function:
continue
if getattr(function, 'depends', None):
setattr(field, attribute,
getattr(field, attribute) | function.depends)
if getattr(function, 'depend_methods', None):
cls.__depend_methods[(field_name, attribute)] |= \
function.depend_methods
function = getattr(cls, function_name, None)
if (attribute == 'on_change'
and not getattr(function, 'on_change', None)):
# Decorate on_change to always return self
setattr(cls, function_name, on_change(function))
2015-03-16 12:56:11 +01:00
def setup_callable(function, name):
if hasattr(function, 'change'):
cls.__change_buttons[name] |= function.change
for name in dir(cls):
attr = getattr(cls, name)
if isinstance(attr, fields.Field):
setup_field(attr, name)
elif isinstance(attr, collections.Callable):
setup_callable(attr, name)
2014-10-29 20:38:17 +01:00
@classmethod
def __post_setup__(cls):
super(ModelView, cls).__post_setup__()
# Update __rpc__
for field_name, field in cls._fields.iteritems():
if isinstance(field, (fields.Selection, fields.Reference)) \
and not isinstance(field.selection, (list, tuple)) \
and field.selection not in cls.__rpc__:
instantiate = 0 if field.selection_change_with else None
cls.__rpc__.setdefault(field.selection,
RPC(instantiate=instantiate))
for attribute in ('on_change', 'on_change_with', 'autocomplete'):
function_name = '%s_%s' % (attribute, field_name)
if getattr(cls, function_name, None):
result = None
if attribute == 'on_change':
result = on_change_result
cls.__rpc__.setdefault(function_name,
RPC(instantiate=0, result=result))
2015-03-16 12:56:11 +01:00
for button in cls._buttons:
if not is_instance_method(cls, button):
2015-03-16 12:56:11 +01:00
cls.__rpc__.setdefault(button,
RPC(readonly=False, instantiate=0))
else:
cls.__rpc__.setdefault(button,
RPC(instantiate=0, result=on_change_result))
2014-10-29 20:38:17 +01:00
# Update depend on methods
for (field_name, attribute), others in (
cls.__depend_methods.iteritems()):
field = getattr(cls, field_name)
for other in others:
other_field = getattr(cls, other)
setattr(field, attribute,
getattr(field, attribute)
| getattr(other_field, attribute))
2012-09-24 22:16:53 +02:00
@classmethod
def fields_view_get(cls, view_id=None, view_type='form'):
2009-01-29 20:34:26 +01:00
'''
2009-09-13 15:37:11 +02:00
Return a view definition.
2012-09-24 22:16:53 +02:00
If view_id is None the first one will be used of view_type.
The definition is a dictionary with keys:
2009-03-01 13:37:31 +01:00
- model: the model name
- type: the type of the view
- view_id: the id of the view
2009-03-01 13:37:31 +01:00
- arch: the xml description of the view
2009-09-13 15:37:11 +02:00
- fields: a dictionary with the definition of each field in the view
- field_childs: the name of the childs field for tree
2009-01-29 20:34:26 +01:00
'''
2012-09-24 22:16:53 +02:00
key = (cls.__name__, view_id, view_type)
result = cls._fields_view_get_cache.get(key)
if result:
return result
result = {'model': cls.__name__}
2011-07-03 13:20:58 +02:00
pool = Pool()
2012-09-26 19:38:20 +02:00
View = pool.get('ir.ui.view')
2009-01-29 20:34:26 +01:00
2012-09-26 19:38:20 +02:00
view = None
2015-10-15 10:44:30 +02:00
inherit_view_id = None
if view_id:
view = View(view_id)
else:
domain = [
('model', '=', cls.__name__),
['OR',
('inherit', '=', None),
('inherit.model', '!=', cls.__name__),
],
]
views = View.search(domain)
2015-12-31 14:15:51 +01:00
views = filter(lambda v: v.rng_type == view_type, views)
2015-10-15 10:44:30 +02:00
if views:
view = views[0]
if view:
if view.inherit:
2012-09-26 19:38:20 +02:00
inherit_view_id = view.id
2015-10-15 10:44:30 +02:00
view = view.inherit
view_id = view.id
2009-01-29 20:34:26 +01:00
# if a view was found
2012-09-26 19:38:20 +02:00
if view:
2015-12-31 14:15:51 +01:00
result['type'] = view.rng_type
2009-01-29 20:34:26 +01:00
result['view_id'] = view_id
2012-09-26 19:38:20 +02:00
result['arch'] = view.arch
result['field_childs'] = view.field_childs
2009-01-29 20:34:26 +01:00
# Check if view is not from an inherited model
2012-09-26 19:38:20 +02:00
if view.model != cls.__name__:
Inherit = pool.get(view.model)
2012-09-24 22:16:53 +02:00
result['arch'] = Inherit.fields_view_get(
2010-08-19 22:01:12 +02:00
result['view_id'])['arch']
2009-01-29 20:34:26 +01:00
view_id = inherit_view_id
# get all views which inherit from (ie modify) this view
2012-09-26 19:38:20 +02:00
views = View.search([
'OR', [
('inherit', '=', view_id),
('model', '=', cls.__name__),
], [
('id', '=', view_id),
('inherit', '!=', None),
],
])
raise_p = False
while True:
try:
2012-09-26 19:38:20 +02:00
views.sort(key=lambda x:
cls._modules_list.index(x.module or None))
break
except ValueError:
if raise_p:
raise
# There is perhaps a new module in the directory
ModelView._reset_modules_list()
raise_p = True
2015-12-31 14:13:03 +01:00
parser = etree.XMLParser(remove_comments=True)
tree = etree.fromstring(result['arch'], parser=parser)
2012-09-26 19:38:20 +02:00
for view in views:
if view.domain:
2015-03-09 19:40:41 +01:00
if not PYSONDecoder({'context': Transaction().context}
).decode(view.domain):
2009-01-29 20:34:26 +01:00
continue
2012-09-26 19:38:20 +02:00
if not view.arch or not view.arch.strip():
2009-01-29 20:34:26 +01:00
continue
2015-12-31 14:13:03 +01:00
tree_inherit = etree.fromstring(view.arch, parser=parser)
tree = _inherit_apply(tree, tree_inherit)
2016-02-07 16:46:16 +01:00
result['arch'] = etree.tostring(
tree, encoding='utf-8').decode('utf-8')
2009-01-29 20:34:26 +01:00
# otherwise, build some kind of default view
else:
if view_type == 'form':
2012-09-24 22:16:53 +02:00
res = cls.fields_get()
xml = '''<?xml version="1.0"?>''' \
2013-02-27 22:50:31 +01:00
'''<form string="%s" col="4">''' % (cls.__doc__,)
2009-01-29 20:34:26 +01:00
for i in res:
if i in ('create_uid', 'create_date',
'write_uid', 'write_date', 'id', 'rec_name'):
2009-01-29 20:34:26 +01:00
continue
if res[i]['type'] not in ('one2many', 'many2many'):
xml += '<label name="%s"/>' % (i,)
xml += '<field name="%s"/>' % (i,)
if res[i]['type'] == 'text':
xml += "<newline/>"
2012-05-14 15:27:06 +02:00
else:
xml += '<field name="%s" colspan="4"/>' % (i,)
2009-01-29 20:34:26 +01:00
xml += "</form>"
elif view_type == 'tree':
field = 'id'
2012-09-24 22:16:53 +02:00
if cls._rec_name in cls._fields:
field = cls._rec_name
xml = '''<?xml version="1.0"?>''' \
2013-02-27 22:50:31 +01:00
'''<tree string="%s"><field name="%s"/></tree>''' \
% (cls.__doc__, field)
2009-01-29 20:34:26 +01:00
else:
xml = ''
result['type'] = view_type
result['arch'] = xml
result['field_childs'] = None
2009-01-29 20:34:26 +01:00
result['view_id'] = 0
# Update arch and compute fields from arch
parser = etree.XMLParser(remove_blank_text=True)
tree = etree.fromstring(result['arch'], parser)
2012-09-24 22:16:53 +02:00
xarch, xfields = cls._view_look_dom_arch(tree, result['type'],
2011-04-03 15:02:28 +02:00
result['field_childs'])
2009-01-29 20:34:26 +01:00
result['arch'] = xarch
result['fields'] = xfields
2012-09-24 22:16:53 +02:00
cls._fields_view_get_cache.set(key, result)
2009-01-29 20:34:26 +01:00
return result
2012-09-24 22:16:53 +02:00
@classmethod
def view_toolbar_get(cls):
2011-07-20 18:34:11 +02:00
"""
Returns the model specific actions.
2012-09-24 22:16:53 +02:00
A dictionary with keys:
2011-07-20 18:34:11 +02:00
- print: a list of available reports
- action: a list of available actions
- relate: a list of available relations
"""
2012-09-24 22:16:53 +02:00
Action = Pool().get('ir.action.keyword')
key = cls.__name__
2012-09-24 22:16:53 +02:00
result = cls._view_toolbar_get_cache.get(key)
if result:
return result
prints = Action.get_keyword('form_print', (cls.__name__, -1))
actions = Action.get_keyword('form_action', (cls.__name__, -1))
relates = Action.get_keyword('form_relate', (cls.__name__, -1))
result = {
2011-07-20 18:34:11 +02:00
'print': prints,
'action': actions,
'relate': relates,
2012-09-24 22:16:53 +02:00
}
cls._view_toolbar_get_cache.set(key, result)
return result
2011-07-20 18:34:11 +02:00
2012-09-24 22:16:53 +02:00
@classmethod
def view_header_get(cls, value, view_type='form'):
2009-01-29 20:34:26 +01:00
"""
2009-09-13 15:37:11 +02:00
Overload this method if you need a window title.
2009-01-29 20:34:26 +01:00
which depends on the context
:param value: the default header string
:param view_type: the type of the view
:return: the header string of the view
"""
return value
2015-03-09 19:40:41 +01:00
@classmethod
def view_attributes(cls):
'Return a list of xpath, attribute name and value'
return []
2012-09-24 22:16:53 +02:00
@classmethod
def _view_look_dom_arch(cls, tree, type, field_children=None):
2011-07-03 13:20:58 +02:00
pool = Pool()
2012-09-24 22:16:53 +02:00
ModelAccess = pool.get('ir.model.access')
FieldAccess = pool.get('ir.model.field.access')
2011-01-22 23:56:13 +01:00
2015-03-09 19:40:41 +01:00
encoder = PYSONEncoder()
for xpath, attribute, value in cls.view_attributes():
for element in tree.xpath(xpath):
element.set(attribute, encoder.encode(value))
2009-01-29 20:34:26 +01:00
fields_width = {}
tree_root = tree.getroottree().getroot()
2011-01-22 23:56:13 +01:00
# Find field without read access
2012-09-24 22:16:53 +02:00
fread_accesses = FieldAccess.check(cls.__name__,
cls._fields.keys(), 'read', access=True)
2011-01-22 23:56:13 +01:00
fields_to_remove = list(x for x, y in fread_accesses.iteritems()
if not y)
# Find relation field without read access
2012-09-24 22:16:53 +02:00
for name, field in cls._fields.iteritems():
if not ModelAccess.check_relation(cls.__name__, name, mode='read'):
fields_to_remove.append(name)
2012-09-24 22:16:53 +02:00
for name, field in cls._fields.iteritems():
2011-01-22 23:56:13 +01:00
for field_to_remove in fields_to_remove:
if field_to_remove in field.depends:
fields_to_remove.append(name)
# Remove field without read access
for field in fields_to_remove:
xpath = ('//field[@name="%(field)s"] | //label[@name="%(field)s"]'
' | //page[@name="%(field)s"] | //group[@name="%(field)s"]'
' | //separator[@name="%(field)s"]') % {'field': field}
2015-12-27 20:01:03 +01:00
for i, element in enumerate(tree.xpath(xpath)):
if type == 'tree' or element.tag == 'page':
2011-01-22 23:56:13 +01:00
parent = element.getparent()
parent.remove(element)
elif type == 'form':
element.tag = 'label'
element.attrib.clear()
2015-12-27 20:01:03 +01:00
element.attrib['id'] = 'hidden %s-%s' % (field, i)
2011-01-22 23:56:13 +01:00
2009-01-29 20:34:26 +01:00
if type == 'tree':
2012-09-24 22:16:53 +02:00
ViewTreeWidth = pool.get('ir.ui.view_tree_width')
viewtreewidth_ids = ViewTreeWidth.search([
('model', '=', cls.__name__),
2010-08-19 22:01:12 +02:00
('user', '=', Transaction().user),
])
2012-09-24 22:16:53 +02:00
for viewtreewidth in ViewTreeWidth.browse(viewtreewidth_ids):
2009-01-29 20:34:26 +01:00
if viewtreewidth.width > 0:
fields_width[viewtreewidth.field] = viewtreewidth.width
2012-09-24 22:16:53 +02:00
fields_def = cls.__view_look_dom(tree_root, type,
2010-08-19 22:01:12 +02:00
fields_width=fields_width)
2009-01-29 20:34:26 +01:00
2011-04-03 15:02:28 +02:00
if field_children:
fields_def.setdefault(field_children, {'name': field_children})
2012-09-24 22:16:53 +02:00
if field_children in cls._fields:
field = cls._fields[field_children]
2014-11-24 16:36:58 +00:00
if hasattr(field, 'field'):
fields_def.setdefault(field.field, {'name': field.field})
2011-04-03 15:02:28 +02:00
2009-01-29 20:34:26 +01:00
for field_name in fields_def.keys():
2012-09-24 22:16:53 +02:00
if field_name in cls._fields:
field = cls._fields[field_name]
2009-01-29 20:34:26 +01:00
else:
continue
for depend in field.depends:
fields_def.setdefault(depend, {'name': depend})
2013-03-20 22:49:16 +01:00
if 'active' in cls._fields:
2011-10-03 09:45:21 +02:00
fields_def.setdefault('active', {'name': 'active'})
2009-01-29 20:34:26 +01:00
2016-02-07 16:46:16 +01:00
arch = etree.tostring(
tree, encoding='utf-8', pretty_print=False).decode('utf-8')
2012-09-24 22:16:53 +02:00
fields2 = cls.fields_get(fields_def.keys())
2009-01-29 20:34:26 +01:00
for field in fields_def:
if field in fields2:
fields2[field].update(fields_def[field])
return arch, fields2
2012-09-24 22:16:53 +02:00
@classmethod
def __view_look_dom(cls, element, type, fields_width=None,
fields_attrs=None):
2011-07-03 13:20:58 +02:00
pool = Pool()
2012-09-24 22:16:53 +02:00
Translation = pool.get('ir.translation')
ModelData = pool.get('ir.model.data')
Button = pool.get('ir.model.button')
User = pool.get('res.user')
2009-01-29 20:34:26 +01:00
if fields_width is None:
fields_width = {}
if not fields_attrs:
fields_attrs = {}
else:
fields_attrs = copy.deepcopy(fields_attrs)
2009-01-29 20:34:26 +01:00
2015-11-10 12:55:06 +01:00
def set_view_ids(element):
view_ids = []
if element.get('view_ids'):
for view_id in element.get('view_ids').split(','):
2009-01-29 20:34:26 +01:00
try:
2015-11-10 12:55:06 +01:00
view_ids.append(int(view_id))
except ValueError:
view_ids.append(ModelData.get_id(*view_id.split('.')))
element.attrib['view_ids'] = ','.join(map(str, view_ids))
2015-11-10 12:55:06 +01:00
return view_ids
def get_relation(field):
if hasattr(field, 'model_name'):
return field.model_name
elif hasattr(field, 'get_target'):
return field.get_target().__name__
def get_views(relation, view_ids, mode):
Relation = pool.get(relation)
views = {}
if field._type in ['one2many', 'many2many']:
# Prefetch only the first view to prevent infinite loop
if view_ids:
for view_id in view_ids:
view = Relation.fields_view_get(view_id=view_id)
views[str(view_id)] = view
break
else:
for view_type in mode:
views[view_type] = (
Relation.fields_view_get(view_type=view_type))
break
return views
for attr in ('name', 'icon'):
if not element.get(attr):
continue
fields_attrs.setdefault(element.get(attr), {})
if element.tag == 'field' and type in ['tree', 'form']:
for attr in ('name', 'icon'):
fname = element.get(attr)
if not fname:
continue
view_ids = set_view_ids(element)
if type != 'form':
continue
field = cls._fields[fname]
relation = get_relation(field)
if not relation:
continue
mode = (
element.attrib.pop('mode', None) or 'tree,form').split(',')
views = get_views(relation, view_ids, mode)
element.attrib['mode'] = ','.join(mode)
fields_attrs[fname].setdefault('views', {}).update(views)
if type == 'tree' and element.get('name') in fields_width:
2009-01-29 20:34:26 +01:00
element.set('width', str(fields_width[element.get('name')]))
encoder = PYSONEncoder()
2012-03-25 00:15:17 +01:00
if element.tag == 'button':
2012-09-10 15:26:34 +02:00
button_name = element.attrib['name']
2012-09-24 22:16:53 +02:00
if button_name in cls._buttons:
states = cls._buttons[button_name]
2012-09-10 15:26:34 +02:00
else:
states = {}
2012-09-24 22:16:53 +02:00
groups = set(User.get_groups())
button_groups = Button.get_groups(cls.__name__, button_name)
2012-09-10 15:26:34 +02:00
if button_groups and not groups & button_groups:
states = states.copy()
states['readonly'] = True
element.set('states', encoder.encode(states))
2012-03-25 00:15:17 +01:00
2015-03-16 12:56:11 +01:00
change = cls.__change_buttons[button_name]
if change:
element.set('change', encoder.encode(list(change)))
if not is_instance_method(cls, button_name):
2015-03-16 12:56:11 +01:00
element.set('type', 'class')
else:
element.set('type', 'instance')
2009-01-29 20:34:26 +01:00
# translate view
2012-04-09 17:48:28 +02:00
if Transaction().language != 'en_US':
2009-01-29 20:34:26 +01:00
for attr in ('string', 'sum', 'confirm', 'help'):
if element.get(attr):
2012-09-24 22:16:53 +02:00
trans = Translation.get_source(cls.__name__, 'view',
2010-08-19 22:01:12 +02:00
Transaction().language, element.get(attr))
2009-01-29 20:34:26 +01:00
if trans:
element.set(attr, trans)
# Set header string
if element.tag in ('form', 'tree', 'graph'):
2012-09-24 22:16:53 +02:00
element.set('string', cls.view_header_get(
2010-08-19 22:01:12 +02:00
element.get('string') or '', view_type=element.tag))
2009-01-29 20:34:26 +01:00
if element.tag == 'tree' and element.get('sequence'):
fields_attrs.setdefault(element.get('sequence'), {})
2013-06-09 20:14:14 +02:00
if element.tag == 'calendar':
for attr in ('dtstart', 'dtend'):
if element.get(attr):
fields_attrs.setdefault(element.get(attr), {})
2015-11-10 12:55:06 +01:00
for field in element:
fields_attrs = cls.__view_look_dom(field, type,
fields_width=fields_width, fields_attrs=fields_attrs)
2009-01-29 20:34:26 +01:00
return fields_attrs
2012-03-25 00:15:17 +01:00
@staticmethod
def button(func):
@wraps(func)
2012-09-24 22:16:53 +02:00
def wrapper(cls, *args, **kwargs):
2012-03-25 00:15:17 +01:00
pool = Pool()
2014-09-17 21:43:53 +02:00
ModelAccess = pool.get('ir.model.access')
2012-09-24 22:16:53 +02:00
Button = pool.get('ir.model.button')
User = pool.get('res.user')
2012-03-25 00:15:17 +01:00
2014-09-17 21:43:53 +02:00
if ((Transaction().user != 0)
and Transaction().context.get('_check_access')):
ModelAccess.check(cls.__name__, 'read')
ModelAccess.check(cls.__name__, 'write')
2012-09-24 22:16:53 +02:00
groups = set(User.get_groups())
button_groups = Button.get_groups(cls.__name__,
2012-04-01 23:08:15 +02:00
func.__name__)
2012-03-25 00:15:17 +01:00
if button_groups and not groups & button_groups:
raise UserError('Calling button %s on %s is not allowed!'
2012-09-24 22:16:53 +02:00
% (func.__name__, cls.__name__))
2014-09-17 21:43:53 +02:00
with Transaction().set_context(_check_access=False):
return func(cls, *args, **kwargs)
2012-03-25 00:15:17 +01:00
return wrapper
2012-09-10 15:26:34 +02:00
@staticmethod
def button_action(action):
def decorator(func):
func = ModelView.button(func)
@wraps(func)
def wrapper(*args, **kwargs):
pool = Pool()
2012-09-24 22:16:53 +02:00
ModelData = pool.get('ir.model.data')
Action = pool.get('ir.action')
2012-09-10 15:26:34 +02:00
func(*args, **kwargs)
module, fs_id = action.split('.')
2012-09-24 22:16:53 +02:00
action_id = Action.get_action_id(
ModelData.get_id(module, fs_id))
2012-09-10 15:26:34 +02:00
return action_id
return wrapper
return decorator
2014-10-29 20:38:17 +01:00
2015-03-16 12:56:11 +01:00
@staticmethod
def button_change(*fields):
def decorator(func):
func = ModelView.button(func)
func = on_change(func)
func.change = set(fields)
return func
return decorator
2014-10-29 20:38:17 +01:00
def on_change(self, fieldnames):
for fieldname in sorted(fieldnames):
method = getattr(self, 'on_change_%s' % fieldname, None)
if method:
method()
# XXX remove backward compatibility
return [self._changed_values]
def on_change_with(self, fieldnames):
changes = {}
for fieldname in fieldnames:
method_name = 'on_change_with_%s' % fieldname
changes[fieldname] = getattr(self, method_name)()
return changes
@property
def _changed_values(self):
"""Return the values changed since the instantiation.
By default, the value of a field is its internal representation except:
- for Many2One and One2One field: the id.
- for Reference field: the string model,id
- for Many2Many: the list of ids
- for One2Many: a dictionary composed of three keys:
- add: a list of tuple, the first element is the index where
the new line is added, the second element is
`_default_values`
- update: a list of dictionary of `_changed_values` including
the `id`
- remove: a list of ids
"""
from .modelstorage import ModelStorage
changed = {}
init_values = self._init_values or {}
if not self._values:
return changed
for fname, value in self._values.iteritems():
field = self._fields[fname]
# Always test key presence in case value is None
if (fname in init_values
and value == init_values[fname]
2014-11-21 10:08:35 +01:00
and field._type != 'one2many'):
continue
2014-10-29 20:38:17 +01:00
if field._type in ('many2one', 'one2one', 'reference'):
if value:
if isinstance(value, ModelStorage):
changed['%s.rec_name' % fname] = value.rec_name
if value.id is None:
# Don't consider temporary instance as a change
continue
2014-10-29 20:38:17 +01:00
if field._type == 'reference':
value = str(value)
else:
value = value.id
elif field._type == 'one2many':
targets = value
init_targets = list(init_values.get(fname, []))
value = collections.defaultdict(list)
value['remove'] = [t.id for t in init_targets if t.id]
for i, target in enumerate(targets):
if target.id in value['remove']:
value['remove'].remove(target.id)
target_changed = target._changed_values
if target_changed:
target_changed['id'] = target.id
value['update'].append(target_changed)
else:
value['add'].append((i, target._default_values))
if not value['remove']:
del value['remove']
2014-11-21 10:08:35 +01:00
if not value:
continue
2014-10-29 20:38:17 +01:00
elif field._type == 'many2many':
value = [r.id for r in value]
changed[fname] = value
return changed