untangle inheritance and postprocessing

bzr revid: al@openerp.com-20130629213516-pqcl6oz1g3o5tpgn
This commit is contained in:
Antony Lesuisse 2013-06-29 23:35:16 +02:00
parent ca98a61a3d
commit 932efea388
2 changed files with 324 additions and 447 deletions

View File

@ -57,19 +57,6 @@ class view_custom(osv.osv):
class view(osv.osv): class view(osv.osv):
_name = 'ir.ui.view' _name = 'ir.ui.view'
class NoViewError(Exception): pass
class NoDefaultError(NoViewError): pass
def _type_field(self, cr, uid, ids, name, args, context=None):
result = {}
for record in self.browse(cr, uid, ids, context):
# Get the type from the inherited view if any.
if record.inherit_id:
result[record.id] = record.inherit_id.type
else:
result[record.id] = etree.fromstring(record.arch.encode('utf8')).tag
return result
def _arch_get(self, cr, uid, ids, name, arg, context=None): def _arch_get(self, cr, uid, ids, name, arg, context=None):
""" """
For each id being read, return arch_db or the content of arch_file For each id being read, return arch_db or the content of arch_file
@ -130,17 +117,6 @@ class view(osv.osv):
# Holds the RNG schema # Holds the RNG schema
_relaxng_validator = None _relaxng_validator = None
def create(self, cr, uid, values, context=None):
if 'type' not in values:
if values.get('inherit_id'):
values['type'] = self.browse(cr, uid, values['inherit_id'], context).type
else:
values['type'] = etree.fromstring(values['arch']).tag
if not values.get('name'):
values['name'] = "%s %s" % (values['model'], values['type'])
return super(view, self).create(cr, uid, values, context)
def _relaxng(self): def _relaxng(self):
if not self._relaxng_validator: if not self._relaxng_validator:
frng = tools.file_open(os.path.join('base','rng','view.rng')) frng = tools.file_open(os.path.join('base','rng','view.rng'))
@ -157,7 +133,7 @@ class view(osv.osv):
for view in self.browse(cr, uid, ids, context): for view in self.browse(cr, uid, ids, context):
# Sanity check: the view should not break anything upon rendering! # Sanity check: the view should not break anything upon rendering!
try: try:
fvg = self.read_combined(cr, uid, view.id, view.type, view.model, context=context) fvg = self.read_combined(cr, uid, view.id, None, context=context)
view_arch_utf8 = fvg['arch'] view_arch_utf8 = fvg['arch']
except Exception, e: except Exception, e:
_logger.exception(e) _logger.exception(e)
@ -179,15 +155,8 @@ class view(osv.osv):
return False return False
return True return True
def _check_model(self, cr, uid, ids, context=None):
for view in self.browse(cr, uid, ids, context):
if view.model and view.model not in self.pool:
return False
return True
_constraints = [ _constraints = [
(_check_xml, 'Invalid XML for View Architecture!', ['arch']) (_check_xml, 'Invalid XML for View Architecture!', ['arch'])
#(_check_model, 'The model name does not exist.', ['model']),
] ]
def _auto_init(self, cr, context=None): def _auto_init(self, cr, context=None):
@ -196,107 +165,85 @@ class view(osv.osv):
if not cr.fetchone(): if not cr.fetchone():
cr.execute('CREATE INDEX ir_ui_view_model_type_inherit_id ON ir_ui_view (model, inherit_id)') cr.execute('CREATE INDEX ir_ui_view_model_type_inherit_id ON ir_ui_view (model, inherit_id)')
def read_combined(self, cr, uid, view_id, view_type, model, def create(self, cr, uid, values, context=None):
fields=None, fallback=None, context=None): if 'type' not in values:
if values.get('inherit_id'):
values['type'] = self.browse(cr, uid, values['inherit_id'], context).type
else:
values['type'] = etree.fromstring(values['arch']).tag
if not values.get('name'):
values['name'] = "%s %s" % (values['model'], values['type'])
return super(view, self).create(cr, uid, values, context)
def write(self, cr, uid, ids, vals, context=None):
if not isinstance(ids, (list, tuple)):
ids = [ids]
# drop the corresponding view customizations (used for dashboards for example), otherwise
# not all users would see the updated views
custom_view_ids = self.pool.get('ir.ui.view.custom').search(cr, uid, [('ref_id','in',ids)])
if custom_view_ids:
self.pool.get('ir.ui.view.custom').unlink(cr, uid, custom_view_ids)
return super(view, self).write(cr, uid, ids, vals, context)
# default view selection
def default_view(self, cr, uid, model, view_type, context=None):
""" Fetches the default view for the provided (model, view_type) pair:
view with no parent (inherit_id=Fase) with the lowest priority.
:param str model:
:param int view_type:
:return: id of the default view of False if none found
:rtype: int
""" """
Utility function stringing together all method calls necessary to get ids = self.search(cr, uid, [
a full, final view: ['model', '=', model],
['type', '=', view_type],
['inherit_id', '=', False],
], limit=1, order='priority', context=context)
if not ids:
return False
return ids[0]
* Gets the default view if no view_id is provided # inheritance
* Gets the top of the view tree if a sub-view is requested
* Applies all inherited archs on the root view
* Applies post-processing
* Returns the view with all requested fields
.. note:: ``arch`` is always added to the fields list even if not def get_inheriting_views_arch(self, cr, uid, view_id, context=None):
requested (similar to ``id``) """Retrieves the architecture of views that inherit from the given view, from the sets of
views that should currently be used in the system. During the module upgrade phase it
may happen that a view is present in the database but the fields it relies on are not
fully loaded yet. This method only considers views that belong to modules whose code
is already loaded. Custom views defined directly in the database are loaded only
after the module initialization phase is completely finished.
If no view is available (no view_id or invalid view_id provided, or :param int view_id: id of the view whose inheriting views should be retrieved
no view stored for (model, view_type)) a view record will be fetched :param str model: model identifier of the view's related model (for double-checking)
from the ``defaults`` mapping? :rtype: list of tuples
:return: [(view_arch,view_id), ...]
:param fallback: a mapping of {view_type: view_dict}, if no view can
be found (read) will be used to provide a default
before post-processing
:type fallback: mapping
""" """
if context is None: context = {} user_groups = frozenset(self.pool.get('res.users').browse(cr, 1, uid, context).groups_id)
def clean_anotations(arch, parent_info=None): conditions = [['inherit_id', '=', view_id]]
for child in arch: if self.pool._init:
if child.tag == 't' or child.tag == 'field': # Module init currently in progress, only consider views from
# can not anote t and field while cleaning # modules whose code is already loaded
continue conditions.extend([
['model_ids.model', '=', 'ir.ui.view'],
['model_ids.module', 'in', tuple(self.pool._init_modules)],
])
view_ids = self.search(cr, uid, conditions, context=context)
child_text = "".join([etree.tostring(x) for x in child]) # filter views based on user groups
if child.attrib.get('data-edit-model'): return [(view.arch, view.id)
if child_text.find('data-edit-model') != -1 or child_text.find('<t ') != -1: for view in self.browse(cr, 1, view_ids, context)
# enclosed tag, move present tag to childs if not (view.groups_id and user_groups.isdisjoint(view.groups_id))]
parent_info = {
'model': child.attrib.get('data-edit-model'),
'view_id': child.attrib.get('data-edit-view-id'),
'xpath': child.attrib.get('data-edit-xpath')+'/'+child.tag,
}
child.attrib.pop('data-edit-model')
child.attrib.pop('data-edit-view-id')
child.attrib.pop('data-edit-xpath')
child = clean_anotations(child, parent_info)
else:
# tagged node, no enclosed, can keep, don't care if had parent_info
continue
else: def raise_view_error(self, cr, uid, view_id, message, context=None):
if child_text.find('data-edit-model') != -1 or child_text.find('<t ') != -1: view = self.browse(cr, uid, [view_id], context)
# has other nodes to clean message = "Inherit error: %s view_id: %s, xml_id: %s, model: %s, parent_view: %s" % (message, view_id, view.xml_id, view.model, view.inherit_id)
if parent_info: raise AttributeError(message)
# update xpath
parent_info.update({'xpath': parent_info.get('xpath')+'/'+child.tag})
child = clean_anotations(child, parent_info)
elif parent_info:
# put tag from parent, no enclose, higher
child.attrib.update({
'data-edit-model': parent_info.get('model'),
'data-edit-view-id': parent_info.get('view_id'),
'data-edit-xpath': parent_info.get('xpath'),
})
return arch
try:
if not view_id:
view_id = self.default_view(cr, uid, model, view_type, context=context)
root_id = self.root_ancestor(cr, uid, view_id, context=context)
if fields:
needed_fields = ['arch', 'model']
fields = list(itertools.chain(
[field for field in needed_fields if field not in fields],
fields))
[view] = self.read(cr, uid, [root_id], fields=fields, context=context)
arch_tree = etree.fromstring(
view['arch'].encode('utf-8') if isinstance(view['arch'], unicode)
else view['arch'])
descendants = self.iter(
cr, uid, view['id'], model, exclude_base=True, context=context)
arch = self.apply_inherited_archs(
cr, uid, arch_tree, descendants,
model, view['id'], context=context)
arch = self.add_root_tags(arch, model, view['id'])
except self.NoViewError:
# defaultdict is "empty" until first __getattr__
if fallback is None: raise
view = fallback[view_type]
arch = view['arch']
if isinstance(arch, basestring):
arch = etree.fromstring(
arch.encode('utf-8') if isinstance(arch, unicode) else arch)
arch = clean_anotations(arch)
return dict(view, arch=etree.tostring(arch, encoding='utf-8'))
def locate_node(self, arch, spec): def locate_node(self, arch, spec):
""" Locate a node in a source (parent) architecture. """ Locate a node in a source (parent) architecture.
@ -335,144 +282,7 @@ class view(osv.osv):
return node return node
return None return None
def get_inheriting_views_arch(self, cr, uid, view_id, model, context=None): def apply_inheritance_specs(self, cr, uid, source, specs_arch, inherit_id, context=None):
"""Retrieves the architecture of views that inherit from the given view, from the sets of
views that should currently be used in the system. During the module upgrade phase it
may happen that a view is present in the database but the fields it relies on are not
fully loaded yet. This method only considers views that belong to modules whose code
is already loaded. Custom views defined directly in the database are loaded only
after the module initialization phase is completely finished.
:param int view_id: id of the view whose inheriting views should be retrieved
:param str model: model identifier of the view's related model (for double-checking)
:rtype: list of tuples
:return: [(view_arch,view_id), ...]
"""
user_groups = frozenset(self.pool.get('res.users').browse(cr, 1, uid, context).groups_id)
conditions = [['inherit_id', '=', view_id], ['model', '=', model]]
if self.pool._init:
# Module init currently in progress, only consider views from
# modules whose code is already loaded
conditions.extend([
['model_ids.model', '=', 'ir.ui.view'],
['model_ids.module', 'in', tuple(self.pool._init_modules)],
])
view_ids = self.search(cr, uid, conditions, context=context)
# filter views based on user groups
return [(view.arch, view.id)
for view in self.browse(cr, 1, view_ids, context)
if not (view.groups_id and user_groups.isdisjoint(view.groups_id))]
def add_root_tags(self, arch_tree, model, view_id):
for child in arch_tree:
if child.tag == 'data' or child.tag == 'xpath':
child = self.add_root_tags(child, model, view_id)
else:
child.attrib.update({
'data-edit-model': model or 'undefined',
'data-edit-view-id': str(view_id),
'data-edit-xpath': '/'
})
return arch_tree
def iter(self, cr, uid, view_id, model, exclude_base=False, context=None):
""" iterates on all of ``view_id``'s descendants tree depth-first.
If ``exclude_base`` is ``False``, also yields ``view_id`` itself. It is
``False`` by default to match the behavior of etree's Element.iter.
:param int view_id: database id of the root view
:param str model: name of the view's related model (for filtering)
:param bool exclude_base: whether ``view_id`` should be excluded
from the iteration
:return: iterator of (database_id, arch_string) pairs for all
descendants of ``view_id`` (including ``view_id`` itself if
``exclude_base`` is ``False``, the default)
"""
if not exclude_base:
base = self.browse(cr, uid, view_id, context=context)
# should we do add_root_tags this one as well ?
yield base.id, base.arch
for arch, id in self.get_inheriting_views_arch(
cr, uid, view_id, model, context=context):
arch = self.add_root_tags(etree.fromstring(arch), model, view_id)
yield id, etree.tostring(arch)
for info in self.iter(
cr, uid, id, model, exclude_base=True, context=None):
yield info
def default_view(self, cr, uid, model, view_type, context=None):
""" Fetches the default view for the provided (model, view_type) pair:
view with no parent (inherit_id=Fase) with the lowest priority.
:param str model:
:param int view_type:
:return: id of the default view for the (model, view_type) pair
:rtype: int
"""
ids = self.search(cr, uid, [
['model', '=', model],
['type', '=', view_type],
['inherit_id', '=', False],
], limit=1, order='priority', context=context)
if not ids:
raise self.NoDefaultError(
_("No default view of type %s for model %s") % (
view_type, model))
return ids[0]
def root_ancestor(self, cr, uid, view_id, context=None):
"""
Fetches the id of the root of the view tree of which view_id is part
If view_id is specified, view_type and model aren't needed (and the
other way around)
:param view_id: id of view to search the root ancestor of
:return: id of the root view for the tree
"""
view = self.browse(cr, uid, view_id, context=context)
if not view.exists():
raise self.NoViewError(
_("No view for id %s, root ancestor not available") % view_id)
# Search for a root (i.e. without any parent) view.
while view.inherit_id:
view = view.inherit_id
return view.id
def apply_inherited_archs(self, cr, uid, source, descendants,
model, source_view_id, context=None):
""" Applies descendants to the ``source`` view, returns the result of
the application.
:param Element source: source arch to apply descendant on
:param descendants: iterable of (id, arch_string) pairs of all
descendants in the view tree, depth-first,
excluding the base view
:type descendants: iter((int, str))
:return: new architecture etree produced by applying all descendants
on ``source``
:rtype: Element
"""
return reduce(
lambda current_arch, descendant: self.apply_inheritance_specs(
cr, uid, model, source_view_id, current_arch,
*descendant, context=context),
descendants, source)
def raise_view_error(self, cr, uid, model, error_msg, view_id, child_view_id, context=None):
view, child_view = self.browse(cr, uid, [view_id, child_view_id], context)
error_msg = error_msg % {'parent_xml_id': view.xml_id}
raise AttributeError("View definition error for inherited view '%s' on model '%s': %s"
% (child_view.xml_id, model, error_msg))
def apply_inheritance_specs(self, cr, uid, model, root_view_id, source, descendant_id, specs_arch, context=None):
""" Apply an inheriting view (a descendant of the base view) """ Apply an inheriting view (a descendant of the base view)
Apply to a source architecture all the spec nodes (i.e. nodes Apply to a source architecture all the spec nodes (i.e. nodes
@ -530,7 +340,7 @@ class view(osv.osv):
elif pos == 'before': elif pos == 'before':
node.addprevious(child) node.addprevious(child)
else: else:
self.raise_view_error(cr, uid, model, "Invalid position value: '%s'" % pos, root_view_id, descendant_id, context=context) self.raise_view_error(cr, uid, "Invalid position value: '%s'" % pos, inherit_id, context=context)
else: else:
attrs = ''.join([ attrs = ''.join([
' %s="%s"' % (attr, spec.get(attr)) ' %s="%s"' % (attr, spec.get(attr))
@ -538,94 +348,69 @@ class view(osv.osv):
if attr != 'position' if attr != 'position'
]) ])
tag = "<%s%s>" % (spec.tag, attrs) tag = "<%s%s>" % (spec.tag, attrs)
if spec.get('version') and spec.get('version') != source.get('version'): self.raise_view_error(cr, uid, "Element '%s' not found in parent view " % tag, inherit_id, context=context)
self.raise_view_error(cr, uid, model, "Mismatching view API version for element '%s': %r vs %r in parent view '%%(parent_xml_id)s'" % \
(tag, spec.get('version'), source.get('version')), root_view_id, descendant_id, context=context)
self.raise_view_error(cr, uid, model, "Element '%s' not found in parent view '%%(parent_xml_id)s'" % tag, root_view_id, descendant_id, context=context)
return source return source
def write(self, cr, uid, ids, vals, context=None): def add_root_tags(self, arch_tree, model, view_id):
if not isinstance(ids, (list, tuple)): for child in arch_tree:
ids = [ids] if child.tag == 'data' or child.tag == 'xpath':
child = self.add_root_tags(child, model, view_id)
# drop the corresponding view customizations (used for dashboards for example), otherwise
# not all users would see the updated views
custom_view_ids = self.pool.get('ir.ui.view.custom').search(cr, uid, [('ref_id','in',ids)])
if custom_view_ids:
self.pool.get('ir.ui.view.custom').unlink(cr, uid, custom_view_ids)
return super(view, self).write(cr, uid, ids, vals, context)
def graph_get(self, cr, uid, id, model, node_obj, conn_obj, src_node, des_node, label, scale, context=None):
nodes=[]
nodes_name=[]
transitions=[]
start=[]
tres={}
labels={}
no_ancester=[]
blank_nodes = []
_Model_Obj = self.pool[model]
_Node_Obj = self.pool[node_obj]
_Arrow_Obj = self.pool[conn_obj]
for model_key,model_value in _Model_Obj._columns.items():
if model_value._type=='one2many':
if model_value._obj==node_obj:
_Node_Field=model_key
_Model_Field=model_value._fields_id
flag=False
for node_key,node_value in _Node_Obj._columns.items():
if node_value._type=='one2many':
if node_value._obj==conn_obj:
if src_node in _Arrow_Obj._columns and flag:
_Source_Field=node_key
if des_node in _Arrow_Obj._columns and not flag:
_Destination_Field=node_key
flag = True
datas = _Model_Obj.read(cr, uid, id, [],context)
for a in _Node_Obj.read(cr,uid,datas[_Node_Field],[]):
if a[_Source_Field] or a[_Destination_Field]:
nodes_name.append((a['id'],a['name']))
nodes.append(a['id'])
else: else:
blank_nodes.append({'id': a['id'],'name':a['name']}) child.attrib.update({
'data-edit-model': model or 'undefined',
'data-edit-view-id': str(view_id),
'data-edit-xpath': '/'
})
return arch_tree
if a.has_key('flow_start') and a['flow_start']: def apply_view_inheritance(self, cr, uid, source, inherit_id, context=None):
start.append(a['id']) """ Apply all the (directly and indirectly) inheriting views.
else:
if not a[_Source_Field]:
no_ancester.append(a['id'])
for t in _Arrow_Obj.read(cr,uid, a[_Destination_Field],[]):
transitions.append((a['id'], t[des_node][0]))
tres[str(t['id'])] = (a['id'],t[des_node][0])
label_string = ""
if label:
for lbl in eval(label):
if t.has_key(tools.ustr(lbl)) and tools.ustr(t[lbl])=='False':
label_string += ' '
else:
label_string = label_string + " " + tools.ustr(t[lbl])
labels[str(t['id'])] = (a['id'],label_string)
g = graph(nodes, transitions, no_ancester)
g.process(start)
g.scale(*scale)
result = g.result_get()
results = {}
for node in nodes_name:
results[str(node[0])] = result[node[0]]
results[str(node[0])]['name'] = node[1]
return {'nodes': results,
'transitions': tres,
'label' : labels,
'blank_nodes': blank_nodes,
'node_parent_field': _Model_Field,}
# this really needs to be refixtored :param source: a parent architecture to modify (with parent modifications already applied)
def __view_look_dom(self, cr, user, model, node, view_id, in_tree_view, model_fields, context=None): :param inherit_id: the database view_id of the parent view
:return: a modified source where all the modifying architecture are applied
"""
sql_inherit = self.pool.get('ir.ui.view').get_inheriting_views_arch(cr, uid, inherit_id)
for (view_arch, view_id) in sql_inherit:
source = self.apply_inheritance_specs(cr, uid, source, view_arch, view_id, context=context)
source = self.apply_view_inheritance(cr, uid, source, view_id, context=context)
return source
def read_combined(self, cr, uid, view_id, fields=None, context=None):
"""
Utility function to get a view combined with its inherited views.
* Gets the top of the view tree if a sub-view is requested
* Applies all inherited archs on the root view
* Returns the view with all requested fields
.. note:: ``arch`` is always added to the fields list even if not
requested (similar to ``id``)
"""
if context is None: context = {}
# if view_id is not a root view, climb back to the top.
v = self.browse(cr, uid, view_id, context=context)
while v.inherit_id:
v = v.inherit_id
root_id = v.id
# arch and model fields are always returned
if fields:
fields = list(set(fields) | set(['arch', 'model']))
# read the view arch
[view] = self.read(cr, uid, [root_id], fields=fields, context=context)
arch_tree = etree.fromstring( view['arch'].encode('utf-8') if isinstance(view['arch'], unicode) else view['arch'])
# and apply inheritance
arch = self.apply_view_inheritance(cr, uid, arch_tree, root_id, context=context)
return dict(view, arch=etree.tostring(arch, encoding='utf-8'))
# post processing
def postprocess(self, cr, user, model, node, view_id, in_tree_view, model_fields, context=None):
"""Return the description of the fields in the node. """Return the description of the fields in the node.
In a normal call to this method, node is a complete view architecture In a normal call to this method, node is a complete view architecture
@ -692,7 +477,7 @@ class view(osv.osv):
new_xml = etree.fromstring(encode(xml)) new_xml = etree.fromstring(encode(xml))
ctx = context.copy() ctx = context.copy()
ctx['base_model_name'] = model ctx['base_model_name'] = model
xarch, xfields = self.__view_look_dom_arch(cr, user, node.get('object'), new_xml, view_id, ctx) xarch, xfields = self.postprocess_and_fields(cr, user, node.get('object'), new_xml, view_id, ctx)
views['form'] = { views['form'] = {
'arch': xarch, 'arch': xarch,
'fields': xfields 'fields': xfields
@ -719,7 +504,7 @@ class view(osv.osv):
node.remove(f) node.remove(f)
ctx = context.copy() ctx = context.copy()
ctx['base_model_name'] = Model ctx['base_model_name'] = Model
xarch, xfields = self.__view_look_dom_arch(cr, user, column._obj or None, f, view_id, ctx) xarch, xfields = self.postprocess_and_fields(cr, user, column._obj or None, f, view_id, ctx)
views[str(f.tag)] = { views[str(f.tag)] = {
'arch': xarch, 'arch': xarch,
'fields': xfields 'fields': xfields
@ -803,7 +588,7 @@ class view(osv.osv):
for f in node: for f in node:
if children or (node.tag == 'field' and f.tag in ('filter','separator')): if children or (node.tag == 'field' and f.tag in ('filter','separator')):
fields.update(self.__view_look_dom(cr, user, model, f, view_id, in_tree_view, model_fields, context)) fields.update(self.postprocess(cr, user, model, f, view_id, in_tree_view, model_fields, context))
orm.transfer_modifiers_to_node(modifiers, node) orm.transfer_modifiers_to_node(modifiers, node)
return fields return fields
@ -833,11 +618,11 @@ class view(osv.osv):
button.set('readonly', str(int(not can_click))) button.set('readonly', str(int(not can_click)))
return node return node
def __view_look_dom_arch(self, cr, user, model, node, view_id, context=None): def postprocess_and_fields(self, cr, user, model, node, view_id, context=None):
""" Return an architecture and a description of all the fields. """ Return an architecture and a description of all the fields.
The field description combines the result of fields_get() and The field description combines the result of fields_get() and
__view_look_dom(). postprocess().
:param node: the architecture as as an etree :param node: the architecture as as an etree
:return: a tuple (arch, fields) where arch is the given node as a :return: a tuple (arch, fields) where arch is the given node as a
@ -860,7 +645,7 @@ class view(osv.osv):
elif Model: elif Model:
fields = Model.fields_get(cr, user, None, context) fields = Model.fields_get(cr, user, None, context)
fields_def = self.__view_look_dom(cr, user, model, node, view_id, False, fields, context=context) fields_def = self.postprocess(cr, user, model, node, view_id, False, fields, context=context)
node = self._disable_workflow_buttons(cr, user, model, node) node = self._disable_workflow_buttons(cr, user, model, node)
if node.tag in ('kanban', 'tree', 'form', 'gantt'): if node.tag in ('kanban', 'tree', 'form', 'gantt'):
for action, operation in (('create', 'create'), ('delete', 'unlink'), ('edit', 'write')): for action, operation in (('create', 'create'), ('delete', 'unlink'), ('edit', 'write')):
@ -887,7 +672,49 @@ class view(osv.osv):
raise orm.except_orm('View error', msg) raise orm.except_orm('View error', msg)
return arch, fields return arch, fields
def _get_arch(self, cr, uid, id_, context=None): # view used as templates
def clean_anotations(arch, parent_info=None):
for child in arch:
if child.tag == 't' or child.tag == 'field':
# can not anote t and field while cleaning
continue
child_text = "".join([etree.tostring(x) for x in child])
if child.attrib.get('data-edit-model'):
if child_text.find('data-edit-model') != -1 or child_text.find('<t ') != -1:
# enclosed tag, move present tag to childs
parent_info = {
'model': child.attrib.get('data-edit-model'),
'view_id': child.attrib.get('data-edit-view-id'),
'xpath': child.attrib.get('data-edit-xpath')+'/'+child.tag,
}
child.attrib.pop('data-edit-model')
child.attrib.pop('data-edit-view-id')
child.attrib.pop('data-edit-xpath')
child = clean_anotations(child, parent_info)
else:
# tagged node, no enclosed, can keep, don't care if had parent_info
continue
else:
if child_text.find('data-edit-model') != -1 or child_text.find('<t ') != -1:
# has other nodes to clean
if parent_info:
# update xpath
parent_info.update({'xpath': parent_info.get('xpath')+'/'+child.tag})
child = clean_anotations(child, parent_info)
elif parent_info:
# put tag from parent, no enclose, higher
child.attrib.update({
'data-edit-model': parent_info.get('model'),
'data-edit-view-id': parent_info.get('view_id'),
'data-edit-xpath': parent_info.get('xpath'),
})
return arch
def read_template(self, cr, uid, id_, context=None):
from pprint import pprint as pp from pprint import pprint as pp
pp(id_) pp(id_)
try: try:
@ -921,7 +748,7 @@ class view(osv.osv):
def render(self, cr, uid, id_or_xml_id, values, context=None): def render(self, cr, uid, id_or_xml_id, values, context=None):
def loader(name): def loader(name):
arch = self._get_arch(cr, uid, name, context=context) arch = self.read_template(cr, uid, name, context=context)
# parse arch # parse arch
# on the root tag of arch add the attribute t-name="<name>" # on the root tag of arch add the attribute t-name="<name>"
arch = u'<?xml version="1.0" encoding="utf-8"?><tpl><t t-name="{0}">{1}</t></tpl>'.format(name, arch) arch = u'<?xml version="1.0" encoding="utf-8"?><tpl><t t-name="{0}">{1}</t></tpl>'.format(name, arch)
@ -929,6 +756,75 @@ class view(osv.osv):
engine = qweb.QWebXml(loader) engine = qweb.QWebXml(loader)
return engine.render(id_or_xml_id, values) return engine.render(id_or_xml_id, values)
# maybe used to print the workflow ?
def graph_get(self, cr, uid, id, model, node_obj, conn_obj, src_node, des_node, label, scale, context=None):
nodes=[]
nodes_name=[]
transitions=[]
start=[]
tres={}
labels={}
no_ancester=[]
blank_nodes = []
_Model_Obj = self.pool[model]
_Node_Obj = self.pool[node_obj]
_Arrow_Obj = self.pool[conn_obj]
for model_key,model_value in _Model_Obj._columns.items():
if model_value._type=='one2many':
if model_value._obj==node_obj:
_Node_Field=model_key
_Model_Field=model_value._fields_id
flag=False
for node_key,node_value in _Node_Obj._columns.items():
if node_value._type=='one2many':
if node_value._obj==conn_obj:
if src_node in _Arrow_Obj._columns and flag:
_Source_Field=node_key
if des_node in _Arrow_Obj._columns and not flag:
_Destination_Field=node_key
flag = True
datas = _Model_Obj.read(cr, uid, id, [],context)
for a in _Node_Obj.read(cr,uid,datas[_Node_Field],[]):
if a[_Source_Field] or a[_Destination_Field]:
nodes_name.append((a['id'],a['name']))
nodes.append(a['id'])
else:
blank_nodes.append({'id': a['id'],'name':a['name']})
if a.has_key('flow_start') and a['flow_start']:
start.append(a['id'])
else:
if not a[_Source_Field]:
no_ancester.append(a['id'])
for t in _Arrow_Obj.read(cr,uid, a[_Destination_Field],[]):
transitions.append((a['id'], t[des_node][0]))
tres[str(t['id'])] = (a['id'],t[des_node][0])
label_string = ""
if label:
for lbl in eval(label):
if t.has_key(tools.ustr(lbl)) and tools.ustr(t[lbl])=='False':
label_string += ' '
else:
label_string = label_string + " " + tools.ustr(t[lbl])
labels[str(t['id'])] = (a['id'],label_string)
g = graph(nodes, transitions, no_ancester)
g.process(start)
g.scale(*scale)
result = g.result_get()
results = {}
for node in nodes_name:
results[str(node[0])] = result[node[0]]
results[str(node[0])]['name'] = node[1]
return {'nodes': results,
'transitions': tres,
'label' : labels,
'blank_nodes': blank_nodes,
'node_parent_field': _Model_Field,}
class view_sc(osv.osv): class view_sc(osv.osv):
_name = 'ir.ui.view_sc' _name = 'ir.ui.view_sc'
_columns = { _columns = {

View File

@ -634,27 +634,6 @@ class MetaModel(type):
if not self._custom: if not self._custom:
self.module_to_models.setdefault(self._module, []).append(self) self.module_to_models.setdefault(self._module, []).append(self)
class FallbackViewMapping(collections.defaultdict):
def __init__(self, cr, uid, model, context=None):
super(FallbackViewMapping, self).__init__()
self.cr = cr
self.uid = uid
self.model = model
self.context = context
def __missing__(self, view_type):
try:
arch = getattr(self.model, '_get_default_%s_view' % view_type)(
self.cr, self.uid, self.context)
except AttributeError:
raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type)
return {
'id': 0,
'type': view_type,
'name': 'default',
'field_parent': False,
'arch': arch,
}
# Definition of log access columns, automatically added to models if # Definition of log access columns, automatically added to models if
# self._log_access is True # self._log_access is True
@ -1796,15 +1775,12 @@ class BaseModel(object):
return view return view
def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
""" """
Get the detailed composition of the requested view like fields, model, view architecture Get the detailed composition of the requested view like fields, model, view architecture
:param cr: database cursor
:param user: current user id
:param view_id: id of the view or None :param view_id: id of the view or None
:param view_type: type of the view to return if view_id is None ('form', tree', ...) :param view_type: type of the view to return if view_id is None ('form', tree', ...)
:param context: context arguments, like lang, time zone
:param toolbar: true to include contextual actions :param toolbar: true to include contextual actions
:param submenu: deprecated :param submenu: deprecated
:return: dictionary describing the composition of the requested view (including inherited views and extensions) :return: dictionary describing the composition of the requested view (including inherited views and extensions)
@ -1812,69 +1788,72 @@ class BaseModel(object):
* if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace' * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
* if some tag other than 'position' is found in parent view * if some tag other than 'position' is found in parent view
:raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
""" """
if context is None: if context is None:
context = {} context = {}
View = self.pool['ir.ui.view'] View = self.pool['ir.ui.view']
view_ref = context.get(view_type + '_view_ref')
if view_ref and not view_id and '.' in view_ref:
module, view_ref = view_ref.split('.', 1)
cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
view_ref_res = cr.fetchone()
if view_ref_res:
view_id = view_ref_res[0]
root_view = View.read_combined(
cr, user, view_id, view_type, self._name, fields=[
'id', 'name', 'field_parent', 'type', 'model', 'arch'
], fallback=FallbackViewMapping(cr, user, self, context=context),
context=context)
result = { result = {
'model': self._name, 'model': self._name,
'arch': root_view['arch'], 'field_parent': False,
'type': root_view['type'],
'view_id': root_view['id'],
'name': root_view['name'],
'field_parent': root_view['field_parent'] or False
} }
# try to find a view_id if none provided
if not view_id:
# <view_type>_view_ref in context can be used to overrride the default view
view_ref = context.get(view_type + '_view_ref')
if view_ref and '.' in view_ref:
module, view_ref = view_ref.split('.', 1)
cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
view_ref_res = cr.fetchone()
if view_ref_res:
view_id = view_ref_res[0]
else:
# otherwise try to find the lowest priority matching ir.ui.view
view_id = View.default_view(cr, uid, self._name, view_type, context=context)
if view_id:
# read the view with inherited views applied
root_view = View.read_combined(cr, uid, view_id, fields=['id', 'name', 'field_parent', 'type', 'model', 'arch'], context=context)
result['arch'] = root_view['arch']
result['name'] = root_view['name']
result['type'] = root_view['type']
result['view_id'] = root_view['id']
result['field_parent'] = root_view['field_parent']
else:
# fallback on default views methods if no ir.ui.view could be found
try:
get_func = getattr(self, '_get_default_%s_view' % view_type)
arch_etree = get_func(self.cr, self.uid, self.context)
result['arch'] = etree.tostring(arch_etree, encoding='utf-8')
result['type'] = view_type
result['name'] = 'default'
except AttributeError:
raise except_orm(_('Invalid Architecture!'), _("No default view of type '%s' could be found !") % view_type)
# Apply post processing, groups and modifiers etc...
ctx = context ctx = context
if root_view.get('model') != self._name: if root_view.get('model') != self._name:
ctx = dict(context, base_model_name=root_view.get('model')) ctx = dict(context, base_model_name=root_view.get('model'))
xarch, xfields = View._view__view_look_dom_arch( xarch, xfields = View.postprocess_and_fields( cr, uid, self._name, etree.fromstring(result['arch']), result['view_id'], context=ctx)
cr, user, self._name, etree.fromstring(result['arch']),
result['view_id'], context=ctx)
result['arch'] = xarch result['arch'] = xarch
result['fields'] = xfields result['fields'] = xfields
# Add related action information if aksed
if toolbar: if toolbar:
toclean = ('report_sxw_content', 'report_rml_content', 'report_sxw', 'report_rml', 'report_sxw_content_data', 'report_rml_content_data')
def clean(x): def clean(x):
x = x[2] x = x[2]
for key in ('report_sxw_content', 'report_rml_content', for key in toclean:
'report_sxw', 'report_rml',
'report_sxw_content_data', 'report_rml_content_data'):
if key in x: if key in x:
del x[key] del x[key]
return x return x
ir_values_obj = self.pool.get('ir.values') ir_values_obj = self.pool.get('ir.values')
resprint = ir_values_obj.get(cr, user, 'action', resprint = ir_values_obj.get(cr, uid, 'action', 'client_print_multi', [(self._name, False)], False, context)
'client_print_multi', [(self._name, False)], False, resaction = ir_values_obj.get(cr, uid, 'action', 'client_action_multi', [(self._name, False)], False, context)
context) resrelate = ir_values_obj.get(cr, uid, 'action', 'client_action_relate', [(self._name, False)], False, context)
resaction = ir_values_obj.get(cr, user, 'action', resaction = [clean(action) for action in resaction if view_type == 'tree' or not action[2].get('multi')]
'client_action_multi', [(self._name, False)], False, resprint = [clean(print_) for print_ in resprint if view_type == 'tree' or not print_[2].get('multi')]
context)
resrelate = ir_values_obj.get(cr, user, 'action',
'client_action_relate', [(self._name, False)], False,
context)
resaction = [clean(action) for action in resaction
if view_type == 'tree' or not action[2].get('multi')]
resprint = [clean(print_) for print_ in resprint
if view_type == 'tree' or not print_[2].get('multi')]
#When multi="True" set it will display only in More of the list view #When multi="True" set it will display only in More of the list view
resrelate = [clean(action) for action in resrelate resrelate = [clean(action) for action in resrelate
if (action[2].get('multi') and view_type == 'tree') or (not action[2].get('multi') and view_type == 'form')] if (action[2].get('multi') and view_type == 'tree') or (not action[2].get('multi') and view_type == 'form')]
@ -1890,7 +1869,7 @@ class BaseModel(object):
return result return result
def _view_look_dom_arch(self, cr, uid, node, view_id, context=None): def _view_look_dom_arch(self, cr, uid, node, view_id, context=None):
return self['ir.ui.view']._view__view_look_dom_arch( return self['ir.ui.view'].postprocess_and_fields(
cr, uid, self._name, node, view_id, context=context) cr, uid, self._name, node, view_id, context=context)
def search_count(self, cr, user, args, context=None): def search_count(self, cr, user, args, context=None):
@ -3045,31 +3024,6 @@ class BaseModel(object):
self._columns[field_name].required = True self._columns[field_name].required = True
self._columns[field_name].ondelete = "cascade" self._columns[field_name].ondelete = "cascade"
#def __getattr__(self, name):
# """
# Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
# (though inherits doesn't use Python inheritance).
# Handles translating between local ids and remote ids.
# Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance
# when you have inherits.
# """
# for model, field in self._inherits.iteritems():
# proxy = self.pool.get(model)
# if hasattr(proxy, name):
# attribute = getattr(proxy, name)
# if not hasattr(attribute, '__call__'):
# return attribute
# break
# else:
# return super(orm, self).__getattr__(name)
# def _proxy(cr, uid, ids, *args, **kwargs):
# objects = self.browse(cr, uid, ids, kwargs.get('context', None))
# lst = [obj[field].id for obj in objects if obj[field]]
# return getattr(proxy, name)(cr, uid, lst, *args, **kwargs)
# return _proxy
def fields_get(self, cr, user, allfields=None, context=None, write_access=True): def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
""" Return the definition of each field. """ Return the definition of each field.
@ -4911,6 +4865,32 @@ class BaseModel(object):
""" stuff to do right after the registry is built """ """ stuff to do right after the registry is built """
pass pass
#def __getattr__(self, name):
# """
# Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
# (though inherits doesn't use Python inheritance).
# Handles translating between local ids and remote ids.
# Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance
# when you have inherits.
# """
# for model, field in self._inherits.iteritems():
# proxy = self.pool.get(model)
# if hasattr(proxy, name):
# attribute = getattr(proxy, name)
# if not hasattr(attribute, '__call__'):
# return attribute
# break
# else:
# return super(orm, self).__getattr__(name)
# def _proxy(cr, uid, ids, *args, **kwargs):
# objects = self.browse(cr, uid, ids, kwargs.get('context', None))
# lst = [obj[field].id for obj in objects if obj[field]]
# return getattr(proxy, name)(cr, uid, lst, *args, **kwargs)
# return _proxy
def __getattr__(self, name): def __getattr__(self, name):
if name.startswith('signal_'): if name.startswith('signal_'):
signal_name = name[len('signal_'):] signal_name = name[len('signal_'):]
@ -4976,12 +4956,12 @@ def itemgetter_tuple(items):
if len(items) == 1: if len(items) == 1:
return lambda gettable: (gettable[items[0]],) return lambda gettable: (gettable[items[0]],)
return operator.itemgetter(*items) return operator.itemgetter(*items)
class ImportWarning(Warning): class ImportWarning(Warning):
""" Used to send warnings upwards the stack during the import process """ Used to send warnings upwards the stack during the import process
""" """
pass pass
def convert_pgerror_23502(model, fields, info, e): def convert_pgerror_23502(model, fields, info, e):
m = re.match(r'^null value in column "(?P<field>\w+)" violates ' m = re.match(r'^null value in column "(?P<field>\w+)" violates '
r'not-null constraint\n', r'not-null constraint\n',
@ -4997,6 +4977,7 @@ def convert_pgerror_23502(model, fields, info, e):
'message': message, 'message': message,
'field': field_name, 'field': field_name,
} }
def convert_pgerror_23505(model, fields, info, e): def convert_pgerror_23505(model, fields, info, e):
m = re.match(r'^duplicate key (?P<field>\w+) violates unique constraint', m = re.match(r'^duplicate key (?P<field>\w+) violates unique constraint',
str(e)) str(e))