2009-10-20 10:52:23 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
2006-12-07 13:41:40 +00:00
|
|
|
##############################################################################
|
2010-02-02 11:54:22 +00:00
|
|
|
#
|
2008-11-10 10:15:05 +00:00
|
|
|
# OpenERP, Open Source Management Solution
|
2009-10-14 12:32:15 +00:00
|
|
|
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
2008-06-16 11:00:21 +00:00
|
|
|
#
|
2008-11-03 18:27:16 +00:00
|
|
|
# This program is free software: you can redistribute it and/or modify
|
2009-10-14 12:32:15 +00:00
|
|
|
# it under the terms of the GNU Affero General Public License as
|
|
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
|
|
# License, or (at your option) any later version.
|
2006-12-07 13:41:40 +00:00
|
|
|
#
|
2008-11-03 18:27:16 +00:00
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2009-10-14 12:32:15 +00:00
|
|
|
# GNU Affero General Public License for more details.
|
2006-12-07 13:41:40 +00:00
|
|
|
#
|
2009-10-14 12:32:15 +00:00
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
2010-02-02 11:54:22 +00:00
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2006-12-07 13:41:40 +00:00
|
|
|
#
|
|
|
|
##############################################################################
|
2013-04-23 10:58:56 +00:00
|
|
|
import copy
|
2006-12-07 13:41:40 +00:00
|
|
|
|
2012-12-10 15:27:23 +00:00
|
|
|
import logging
|
2013-04-23 15:08:44 +00:00
|
|
|
import itertools
|
2008-07-01 21:31:46 +00:00
|
|
|
from lxml import etree
|
|
|
|
import os
|
2013-04-24 13:58:25 +00:00
|
|
|
import time
|
2012-12-10 15:27:23 +00:00
|
|
|
|
|
|
|
from openerp import tools
|
2013-04-24 13:58:25 +00:00
|
|
|
from openerp.osv import fields, osv, orm
|
2013-04-19 14:14:09 +00:00
|
|
|
from openerp.tools import graph, SKIPPED_ELEMENT_TYPES
|
2012-12-10 15:27:23 +00:00
|
|
|
from openerp.tools.safe_eval import safe_eval as eval
|
2013-04-24 13:09:07 +00:00
|
|
|
from openerp.tools.translate import _
|
2012-12-10 15:27:23 +00:00
|
|
|
from openerp.tools.view_validation import valid_view
|
2006-12-07 13:41:40 +00:00
|
|
|
|
2012-01-24 11:47:30 +00:00
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
2008-08-07 10:57:47 +00:00
|
|
|
class view_custom(osv.osv):
|
2008-08-06 09:01:54 +00:00
|
|
|
_name = 'ir.ui.view.custom'
|
2011-06-01 10:52:09 +00:00
|
|
|
_order = 'create_date desc' # search(limit=1) should return the last customization
|
2008-07-22 14:24:36 +00:00
|
|
|
_columns = {
|
2011-07-27 09:54:14 +00:00
|
|
|
'ref_id': fields.many2one('ir.ui.view', 'Original View', select=True, required=True, ondelete='cascade'),
|
|
|
|
'user_id': fields.many2one('res.users', 'User', select=True, required=True, ondelete='cascade'),
|
2008-08-07 10:57:47 +00:00
|
|
|
'arch': fields.text('View Architecture', required=True),
|
2008-07-22 14:24:36 +00:00
|
|
|
}
|
2010-12-10 12:47:16 +00:00
|
|
|
|
|
|
|
def _auto_init(self, cr, context=None):
|
|
|
|
super(view_custom, self)._auto_init(cr, context)
|
|
|
|
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_ui_view_custom_user_id_ref_id\'')
|
|
|
|
if not cr.fetchone():
|
|
|
|
cr.execute('CREATE INDEX ir_ui_view_custom_user_id_ref_id ON ir_ui_view_custom (user_id, ref_id)')
|
2008-08-06 09:01:54 +00:00
|
|
|
|
|
|
|
class view(osv.osv):
|
|
|
|
_name = 'ir.ui.view'
|
2012-06-19 10:14:25 +00:00
|
|
|
|
2013-04-24 13:09:07 +00:00
|
|
|
class NoViewError(Exception): pass
|
|
|
|
class NoDefaultError(NoViewError): pass
|
|
|
|
|
2012-06-19 10:14:25 +00:00
|
|
|
def _type_field(self, cr, uid, ids, name, args, context=None):
|
2012-08-14 10:00:49 +00:00
|
|
|
result = {}
|
|
|
|
for record in self.browse(cr, uid, ids, context):
|
|
|
|
# Get the type from the inherited view if any.
|
2012-08-08 09:10:02 +00:00
|
|
|
if record.inherit_id:
|
|
|
|
result[record.id] = record.inherit_id.type
|
2012-08-14 10:00:49 +00:00
|
|
|
else:
|
|
|
|
result[record.id] = etree.fromstring(record.arch.encode('utf8')).tag
|
2012-06-19 10:14:25 +00:00
|
|
|
return result
|
|
|
|
|
2008-08-06 09:01:54 +00:00
|
|
|
_columns = {
|
2012-10-13 12:09:24 +00:00
|
|
|
'name': fields.char('View Name', required=True),
|
2010-12-10 12:47:16 +00:00
|
|
|
'model': fields.char('Object', size=64, required=True, select=True),
|
2010-09-27 07:12:29 +00:00
|
|
|
'priority': fields.integer('Sequence', required=True),
|
2012-06-22 13:10:04 +00:00
|
|
|
'type': fields.function(_type_field, type='selection', selection=[
|
2008-08-06 09:01:54 +00:00
|
|
|
('tree','Tree'),
|
|
|
|
('form','Form'),
|
2009-03-08 18:58:44 +00:00
|
|
|
('mdx','mdx'),
|
2008-08-06 09:01:54 +00:00
|
|
|
('graph', 'Graph'),
|
2008-10-10 12:30:23 +00:00
|
|
|
('calendar', 'Calendar'),
|
2010-02-01 09:53:55 +00:00
|
|
|
('diagram','Diagram'),
|
2009-09-17 07:27:12 +00:00
|
|
|
('gantt', 'Gantt'),
|
2011-08-02 09:36:59 +00:00
|
|
|
('kanban', 'Kanban'),
|
2012-06-19 10:14:25 +00:00
|
|
|
('search','Search')], string='View Type', required=True, select=True, store=True),
|
2008-08-06 09:01:54 +00:00
|
|
|
'arch': fields.text('View Architecture', required=True),
|
2010-12-10 12:47:16 +00:00
|
|
|
'inherit_id': fields.many2one('ir.ui.view', 'Inherited View', ondelete='cascade', select=True),
|
2009-01-26 17:40:29 +00:00
|
|
|
'field_parent': fields.char('Child Field',size=64),
|
2011-09-06 07:57:11 +00:00
|
|
|
'xml_id': fields.function(osv.osv.get_xml_id, type='char', size=128, string="External ID",
|
2012-01-04 13:30:27 +00:00
|
|
|
help="ID of the view defined in xml file"),
|
2012-08-29 12:35:39 +00:00
|
|
|
'groups_id': fields.many2many('res.groups', 'ir_ui_view_group_rel', 'view_id', 'group_id',
|
|
|
|
string='Groups', help="If this field is empty, the view applies to all users. Otherwise, the view applies to the users of those groups only."),
|
2013-04-19 12:37:12 +00:00
|
|
|
'model_ids': fields.one2many('ir.model.data', 'res_id', auto_join=True),
|
2008-08-06 09:01:54 +00:00
|
|
|
}
|
|
|
|
_defaults = {
|
2010-06-15 06:08:47 +00:00
|
|
|
'arch': '<?xml version="1.0"?>\n<tree string="My view">\n\t<field name="name"/>\n</tree>',
|
2013-03-27 16:02:58 +00:00
|
|
|
'priority': 16,
|
|
|
|
'type': 'tree',
|
2008-08-06 09:01:54 +00:00
|
|
|
}
|
2010-12-10 22:42:58 +00:00
|
|
|
_order = "priority,name"
|
2011-11-13 19:27:42 +00:00
|
|
|
|
[IMP] modules, ir.ui.view: improve view validation + avoid validation errors during updates
As of 7.0, RNG validation is not possible for form views
that have a version attribute equal to "7.0", due to the
allowed usage of HTML syntax mixed with the regular OpenERP
view syntax. RNG validation is still enabled for regular
form views (@version missing or less than "7.0"), and for
all other views types.
Validation of 7.0 form views should be improved with the
addition of an assertion-based schema, still to be done.
The above is also complemented with an explicit call to fields_view_get()
during view installation, in order to immediately verify
that the updated view hierarchy does not cause any
issue when loaded along with its related views (i.e
parent and siblings, for inheriting views).
In addition to that, fields_view_get() will now only
consider loading views that belong to modules that have
already been loaded. This avoids a lot of validation errors
during a module update operation, which runs on top of
an existing database with all previous views visible,
even those whose module is not loaded yet.
bzr revid: odo@openerp.com-20120611122758-qcw9xdhupl24busq
2012-06-11 12:27:58 +00:00
|
|
|
# Holds the RNG schema
|
2012-10-13 12:09:24 +00:00
|
|
|
_relaxng_validator = None
|
2012-06-04 16:01:24 +00:00
|
|
|
|
2012-06-22 13:10:04 +00:00
|
|
|
def create(self, cr, uid, values, context=None):
|
|
|
|
if 'type' in values:
|
|
|
|
_logger.warning("Setting the `type` field is deprecated in the `ir.ui.view` model.")
|
2012-10-13 12:09:24 +00:00
|
|
|
if not values.get('name'):
|
|
|
|
if values.get('inherit_id'):
|
|
|
|
inferred_type = self.browse(cr, uid, values['inherit_id'], context).type
|
|
|
|
else:
|
|
|
|
inferred_type = etree.fromstring(values['arch'].encode('utf8')).tag
|
|
|
|
values['name'] = "%s %s" % (values['model'], inferred_type)
|
2013-04-15 10:57:44 +00:00
|
|
|
return super(view, self).create(cr, uid, values, context)
|
2012-06-22 13:10:04 +00:00
|
|
|
|
[IMP] modules, ir.ui.view: improve view validation + avoid validation errors during updates
As of 7.0, RNG validation is not possible for form views
that have a version attribute equal to "7.0", due to the
allowed usage of HTML syntax mixed with the regular OpenERP
view syntax. RNG validation is still enabled for regular
form views (@version missing or less than "7.0"), and for
all other views types.
Validation of 7.0 form views should be improved with the
addition of an assertion-based schema, still to be done.
The above is also complemented with an explicit call to fields_view_get()
during view installation, in order to immediately verify
that the updated view hierarchy does not cause any
issue when loaded along with its related views (i.e
parent and siblings, for inheriting views).
In addition to that, fields_view_get() will now only
consider loading views that belong to modules that have
already been loaded. This avoids a lot of validation errors
during a module update operation, which runs on top of
an existing database with all previous views visible,
even those whose module is not loaded yet.
bzr revid: odo@openerp.com-20120611122758-qcw9xdhupl24busq
2012-06-11 12:27:58 +00:00
|
|
|
def _relaxng(self):
|
|
|
|
if not self._relaxng_validator:
|
2011-11-13 19:27:42 +00:00
|
|
|
frng = tools.file_open(os.path.join('base','rng','view.rng'))
|
|
|
|
try:
|
|
|
|
relaxng_doc = etree.parse(frng)
|
[IMP] modules, ir.ui.view: improve view validation + avoid validation errors during updates
As of 7.0, RNG validation is not possible for form views
that have a version attribute equal to "7.0", due to the
allowed usage of HTML syntax mixed with the regular OpenERP
view syntax. RNG validation is still enabled for regular
form views (@version missing or less than "7.0"), and for
all other views types.
Validation of 7.0 form views should be improved with the
addition of an assertion-based schema, still to be done.
The above is also complemented with an explicit call to fields_view_get()
during view installation, in order to immediately verify
that the updated view hierarchy does not cause any
issue when loaded along with its related views (i.e
parent and siblings, for inheriting views).
In addition to that, fields_view_get() will now only
consider loading views that belong to modules that have
already been loaded. This avoids a lot of validation errors
during a module update operation, which runs on top of
an existing database with all previous views visible,
even those whose module is not loaded yet.
bzr revid: odo@openerp.com-20120611122758-qcw9xdhupl24busq
2012-06-11 12:27:58 +00:00
|
|
|
self._relaxng_validator = etree.RelaxNG(relaxng_doc)
|
|
|
|
except Exception:
|
|
|
|
_logger.exception('Failed to load RelaxNG XML schema for views validation')
|
2011-11-13 19:27:42 +00:00
|
|
|
finally:
|
|
|
|
frng.close()
|
[IMP] modules, ir.ui.view: improve view validation + avoid validation errors during updates
As of 7.0, RNG validation is not possible for form views
that have a version attribute equal to "7.0", due to the
allowed usage of HTML syntax mixed with the regular OpenERP
view syntax. RNG validation is still enabled for regular
form views (@version missing or less than "7.0"), and for
all other views types.
Validation of 7.0 form views should be improved with the
addition of an assertion-based schema, still to be done.
The above is also complemented with an explicit call to fields_view_get()
during view installation, in order to immediately verify
that the updated view hierarchy does not cause any
issue when loaded along with its related views (i.e
parent and siblings, for inheriting views).
In addition to that, fields_view_get() will now only
consider loading views that belong to modules that have
already been loaded. This avoids a lot of validation errors
during a module update operation, which runs on top of
an existing database with all previous views visible,
even those whose module is not loaded yet.
bzr revid: odo@openerp.com-20120611122758-qcw9xdhupl24busq
2012-06-11 12:27:58 +00:00
|
|
|
return self._relaxng_validator
|
2012-10-13 12:09:24 +00:00
|
|
|
|
[IMP] modules, ir.ui.view: improve view validation + avoid validation errors during updates
As of 7.0, RNG validation is not possible for form views
that have a version attribute equal to "7.0", due to the
allowed usage of HTML syntax mixed with the regular OpenERP
view syntax. RNG validation is still enabled for regular
form views (@version missing or less than "7.0"), and for
all other views types.
Validation of 7.0 form views should be improved with the
addition of an assertion-based schema, still to be done.
The above is also complemented with an explicit call to fields_view_get()
during view installation, in order to immediately verify
that the updated view hierarchy does not cause any
issue when loaded along with its related views (i.e
parent and siblings, for inheriting views).
In addition to that, fields_view_get() will now only
consider loading views that belong to modules that have
already been loaded. This avoids a lot of validation errors
during a module update operation, which runs on top of
an existing database with all previous views visible,
even those whose module is not loaded yet.
bzr revid: odo@openerp.com-20120611122758-qcw9xdhupl24busq
2012-06-11 12:27:58 +00:00
|
|
|
def _check_render_view(self, cr, uid, view, context=None):
|
|
|
|
"""Verify that the given view's hierarchy is valid for rendering, along with all the changes applied by
|
|
|
|
its inherited views, by rendering it using ``fields_view_get()``.
|
|
|
|
|
|
|
|
@param browse_record view: view to validate
|
2012-06-14 14:46:33 +00:00
|
|
|
@return: the rendered definition (arch) of the view, always utf-8 bytestring (legacy convention)
|
|
|
|
if no error occurred, else False.
|
[IMP] modules, ir.ui.view: improve view validation + avoid validation errors during updates
As of 7.0, RNG validation is not possible for form views
that have a version attribute equal to "7.0", due to the
allowed usage of HTML syntax mixed with the regular OpenERP
view syntax. RNG validation is still enabled for regular
form views (@version missing or less than "7.0"), and for
all other views types.
Validation of 7.0 form views should be improved with the
addition of an assertion-based schema, still to be done.
The above is also complemented with an explicit call to fields_view_get()
during view installation, in order to immediately verify
that the updated view hierarchy does not cause any
issue when loaded along with its related views (i.e
parent and siblings, for inheriting views).
In addition to that, fields_view_get() will now only
consider loading views that belong to modules that have
already been loaded. This avoids a lot of validation errors
during a module update operation, which runs on top of
an existing database with all previous views visible,
even those whose module is not loaded yet.
bzr revid: odo@openerp.com-20120611122758-qcw9xdhupl24busq
2012-06-11 12:27:58 +00:00
|
|
|
"""
|
|
|
|
try:
|
2013-03-29 14:07:23 +00:00
|
|
|
fvg = self.pool[view.model].fields_view_get(cr, uid, view_id=view.id, view_type=view.type, context=context)
|
2012-06-14 14:46:33 +00:00
|
|
|
return fvg['arch']
|
[IMP] modules, ir.ui.view: improve view validation + avoid validation errors during updates
As of 7.0, RNG validation is not possible for form views
that have a version attribute equal to "7.0", due to the
allowed usage of HTML syntax mixed with the regular OpenERP
view syntax. RNG validation is still enabled for regular
form views (@version missing or less than "7.0"), and for
all other views types.
Validation of 7.0 form views should be improved with the
addition of an assertion-based schema, still to be done.
The above is also complemented with an explicit call to fields_view_get()
during view installation, in order to immediately verify
that the updated view hierarchy does not cause any
issue when loaded along with its related views (i.e
parent and siblings, for inheriting views).
In addition to that, fields_view_get() will now only
consider loading views that belong to modules that have
already been loaded. This avoids a lot of validation errors
during a module update operation, which runs on top of
an existing database with all previous views visible,
even those whose module is not loaded yet.
bzr revid: odo@openerp.com-20120611122758-qcw9xdhupl24busq
2012-06-11 12:27:58 +00:00
|
|
|
except:
|
|
|
|
_logger.exception("Can't render view %s for model: %s", view.xml_id, view.model)
|
|
|
|
return False
|
|
|
|
|
|
|
|
def _check_xml(self, cr, uid, ids, context=None):
|
|
|
|
for view in self.browse(cr, uid, ids, context):
|
2012-06-14 14:46:33 +00:00
|
|
|
# Sanity check: the view should not break anything upon rendering!
|
|
|
|
view_arch_utf8 = self._check_render_view(cr, uid, view, context=context)
|
|
|
|
# always utf-8 bytestring - legacy convention
|
|
|
|
if not view_arch_utf8: return False
|
|
|
|
|
[IMP] modules, ir.ui.view: improve view validation + avoid validation errors during updates
As of 7.0, RNG validation is not possible for form views
that have a version attribute equal to "7.0", due to the
allowed usage of HTML syntax mixed with the regular OpenERP
view syntax. RNG validation is still enabled for regular
form views (@version missing or less than "7.0"), and for
all other views types.
Validation of 7.0 form views should be improved with the
addition of an assertion-based schema, still to be done.
The above is also complemented with an explicit call to fields_view_get()
during view installation, in order to immediately verify
that the updated view hierarchy does not cause any
issue when loaded along with its related views (i.e
parent and siblings, for inheriting views).
In addition to that, fields_view_get() will now only
consider loading views that belong to modules that have
already been loaded. This avoids a lot of validation errors
during a module update operation, which runs on top of
an existing database with all previous views visible,
even those whose module is not loaded yet.
bzr revid: odo@openerp.com-20120611122758-qcw9xdhupl24busq
2012-06-11 12:27:58 +00:00
|
|
|
# RNG-based validation is not possible anymore with 7.0 forms
|
2012-06-14 14:46:33 +00:00
|
|
|
# TODO 7.0: provide alternative assertion-based validation of view_arch_utf8
|
|
|
|
view_docs = [etree.fromstring(view_arch_utf8)]
|
[IMP] modules, ir.ui.view: improve view validation + avoid validation errors during updates
As of 7.0, RNG validation is not possible for form views
that have a version attribute equal to "7.0", due to the
allowed usage of HTML syntax mixed with the regular OpenERP
view syntax. RNG validation is still enabled for regular
form views (@version missing or less than "7.0"), and for
all other views types.
Validation of 7.0 form views should be improved with the
addition of an assertion-based schema, still to be done.
The above is also complemented with an explicit call to fields_view_get()
during view installation, in order to immediately verify
that the updated view hierarchy does not cause any
issue when loaded along with its related views (i.e
parent and siblings, for inheriting views).
In addition to that, fields_view_get() will now only
consider loading views that belong to modules that have
already been loaded. This avoids a lot of validation errors
during a module update operation, which runs on top of
an existing database with all previous views visible,
even those whose module is not loaded yet.
bzr revid: odo@openerp.com-20120611122758-qcw9xdhupl24busq
2012-06-11 12:27:58 +00:00
|
|
|
if view_docs[0].tag == 'data':
|
|
|
|
# A <data> element is a wrapper for multiple root nodes
|
|
|
|
view_docs = view_docs[0]
|
|
|
|
validator = self._relaxng()
|
|
|
|
for view_arch in view_docs:
|
|
|
|
if (view_arch.get('version') < '7.0') and validator and not validator.validate(view_arch):
|
|
|
|
for error in validator.error_log:
|
|
|
|
_logger.error(tools.ustr(error))
|
|
|
|
return False
|
2012-06-22 13:10:04 +00:00
|
|
|
if not valid_view(view_arch):
|
|
|
|
return False
|
2011-11-13 19:27:42 +00:00
|
|
|
return True
|
|
|
|
|
2008-08-06 09:01:54 +00:00
|
|
|
_constraints = [
|
2013-04-22 12:27:39 +00:00
|
|
|
#(_check_xml, 'Invalid XML for View Architecture!', ['arch'])
|
2008-08-06 09:01:54 +00:00
|
|
|
]
|
2008-09-23 05:36:21 +00:00
|
|
|
|
2010-12-10 12:47:16 +00:00
|
|
|
def _auto_init(self, cr, context=None):
|
|
|
|
super(view, self)._auto_init(cr, context)
|
|
|
|
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_ui_view_model_type_inherit_id\'')
|
|
|
|
if not cr.fetchone():
|
2012-06-19 10:14:25 +00:00
|
|
|
cr.execute('CREATE INDEX ir_ui_view_model_type_inherit_id ON ir_ui_view (model, inherit_id)')
|
2010-12-10 12:47:16 +00:00
|
|
|
|
2013-04-24 09:51:14 +00:00
|
|
|
def read_combined(self, cr, uid, view_id, view_type, model,
|
2013-04-24 13:09:07 +00:00
|
|
|
fields=None, fallback=None, context=None):
|
2013-04-23 15:08:44 +00:00
|
|
|
"""
|
2013-04-24 09:51:14 +00:00
|
|
|
Utility function stringing together all method calls necessary to get
|
|
|
|
a full, final view:
|
|
|
|
|
|
|
|
* Gets the default view if no view_id is provided
|
|
|
|
* Gets the top of the view tree if a sub-view is requested
|
|
|
|
* Applies all inherited archs on the root view
|
2013-04-24 13:09:07 +00:00
|
|
|
* Applies post-processing
|
2013-04-24 09:51:14 +00:00
|
|
|
* Returns the view with all requested fields
|
|
|
|
|
|
|
|
.. note:: ``arch`` is always added to the fields list even if not
|
|
|
|
requested (similar to ``id``)
|
|
|
|
|
|
|
|
If no view is available (no view_id or invalid view_id provided, or
|
|
|
|
no view stored for (model, view_type)) a view record will be fetched
|
2013-04-24 13:09:07 +00:00
|
|
|
from the ``defaults`` mapping?
|
2013-04-23 15:08:44 +00:00
|
|
|
|
2013-04-24 13:09:07 +00:00
|
|
|
: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 = {}
|
|
|
|
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 and 'arch' not in fields:
|
|
|
|
fields = list(itertools.chain(['arch'], 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)
|
|
|
|
|
|
|
|
if view['model'] != model:
|
|
|
|
context = dict(context, base_model_name=view['model'])
|
|
|
|
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)
|
2013-04-24 09:51:14 +00:00
|
|
|
|
|
|
|
# TODO: post-processing
|
|
|
|
|
2013-04-24 13:09:07 +00:00
|
|
|
return dict(view, arch=etree.tostring(arch, encoding='utf-8'))
|
2013-04-23 15:08:44 +00:00
|
|
|
|
2013-04-19 14:14:09 +00:00
|
|
|
def locate_node(self, arch, spec):
|
|
|
|
""" Locate a node in a source (parent) architecture.
|
|
|
|
|
|
|
|
Given a complete source (parent) architecture (i.e. the field
|
|
|
|
`arch` in a view), and a 'spec' node (a node in an inheriting
|
|
|
|
view that specifies the location in the source view of what
|
|
|
|
should be changed), return (if it exists) the node in the
|
|
|
|
source view matching the specification.
|
|
|
|
|
|
|
|
:param arch: a parent architecture to modify
|
|
|
|
:param spec: a modifying node in an inheriting view
|
|
|
|
:return: a node in the source matching the spec
|
|
|
|
|
|
|
|
"""
|
|
|
|
if spec.tag == 'xpath':
|
|
|
|
nodes = arch.xpath(spec.get('expr'))
|
|
|
|
return nodes[0] if nodes else None
|
|
|
|
elif spec.tag == 'field':
|
|
|
|
# Only compare the field name: a field can be only once in a given view
|
|
|
|
# at a given level (and for multilevel expressions, we should use xpath
|
|
|
|
# inheritance spec anyway).
|
2013-04-22 09:19:58 +00:00
|
|
|
for node in arch.iter('field'):
|
2013-04-19 14:14:09 +00:00
|
|
|
if node.get('name') == spec.get('name'):
|
|
|
|
return node
|
|
|
|
return None
|
|
|
|
|
2013-04-22 09:19:58 +00:00
|
|
|
for node in arch.iter(spec.tag):
|
2013-04-19 14:14:09 +00:00
|
|
|
if isinstance(node, SKIPPED_ELEMENT_TYPES):
|
|
|
|
continue
|
2013-04-22 09:10:14 +00:00
|
|
|
if all(node.get(attr) == spec.get(attr) for attr in spec.attrib
|
|
|
|
if attr not in ('position','version')):
|
2013-04-19 14:14:09 +00:00
|
|
|
# Version spec should match parent's root element's version
|
|
|
|
if spec.get('version') and spec.get('version') != arch.get('version'):
|
|
|
|
return None
|
|
|
|
return node
|
|
|
|
return None
|
|
|
|
|
2011-09-19 15:24:34 +00:00
|
|
|
def get_inheriting_views_arch(self, cr, uid, view_id, model, context=None):
|
[IMP] modules, ir.ui.view: improve view validation + avoid validation errors during updates
As of 7.0, RNG validation is not possible for form views
that have a version attribute equal to "7.0", due to the
allowed usage of HTML syntax mixed with the regular OpenERP
view syntax. RNG validation is still enabled for regular
form views (@version missing or less than "7.0"), and for
all other views types.
Validation of 7.0 form views should be improved with the
addition of an assertion-based schema, still to be done.
The above is also complemented with an explicit call to fields_view_get()
during view installation, in order to immediately verify
that the updated view hierarchy does not cause any
issue when loaded along with its related views (i.e
parent and siblings, for inheriting views).
In addition to that, fields_view_get() will now only
consider loading views that belong to modules that have
already been loaded. This avoids a lot of validation errors
during a module update operation, which runs on top of
an existing database with all previous views visible,
even those whose module is not loaded yet.
bzr revid: odo@openerp.com-20120611122758-qcw9xdhupl24busq
2012-06-11 12:27:58 +00:00
|
|
|
"""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.
|
2011-09-18 23:59:47 +00:00
|
|
|
|
2011-09-19 15:24:34 +00:00
|
|
|
: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), ...]
|
2011-09-18 23:59:47 +00:00
|
|
|
"""
|
2012-08-29 13:37:32 +00:00
|
|
|
user_groups = frozenset(self.pool.get('res.users').browse(cr, 1, uid, context).groups_id)
|
2013-04-19 12:37:12 +00:00
|
|
|
|
|
|
|
conditions = [['inherit_id', '=', view_id], ['model', '=', model]]
|
[IMP] modules, ir.ui.view: improve view validation + avoid validation errors during updates
As of 7.0, RNG validation is not possible for form views
that have a version attribute equal to "7.0", due to the
allowed usage of HTML syntax mixed with the regular OpenERP
view syntax. RNG validation is still enabled for regular
form views (@version missing or less than "7.0"), and for
all other views types.
Validation of 7.0 form views should be improved with the
addition of an assertion-based schema, still to be done.
The above is also complemented with an explicit call to fields_view_get()
during view installation, in order to immediately verify
that the updated view hierarchy does not cause any
issue when loaded along with its related views (i.e
parent and siblings, for inheriting views).
In addition to that, fields_view_get() will now only
consider loading views that belong to modules that have
already been loaded. This avoids a lot of validation errors
during a module update operation, which runs on top of
an existing database with all previous views visible,
even those whose module is not loaded yet.
bzr revid: odo@openerp.com-20120611122758-qcw9xdhupl24busq
2012-06-11 12:27:58 +00:00
|
|
|
if self.pool._init:
|
2013-04-19 12:37:12 +00:00
|
|
|
# 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)
|
|
|
|
|
2012-08-29 13:37:32 +00:00
|
|
|
# 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))]
|
2011-09-18 23:59:47 +00:00
|
|
|
|
2013-04-22 12:50:00 +00:00
|
|
|
def iter(self, cr, uid, view_id, model, exclude_base=False, context=None):
|
2013-04-24 09:51:14 +00:00
|
|
|
""" iterates on all of ``view_id``'s descendants tree depth-first.
|
2013-04-22 12:50:00 +00:00
|
|
|
|
2013-04-24 09:51:14 +00:00
|
|
|
If ``exclude_base`` is ``False``, also yields ``view_id`` itself. It is
|
|
|
|
``False`` by default to match the behavior of etree's Element.iter.
|
2013-04-22 12:50:00 +00:00
|
|
|
|
|
|
|
:param int view_id: database id of the root view
|
|
|
|
:param str model: name of the view's related model (for filtering)
|
2013-04-24 09:51:14 +00:00
|
|
|
: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)
|
2013-04-22 12:50:00 +00:00
|
|
|
"""
|
|
|
|
if not exclude_base:
|
2013-04-23 15:08:44 +00:00
|
|
|
base = self.browse(cr, uid, view_id, context=context)
|
2013-04-22 12:50:00 +00:00
|
|
|
yield base.id, base.arch
|
|
|
|
|
|
|
|
for arch, id in self.get_inheriting_views_arch(
|
|
|
|
cr, uid, view_id, model, context=context):
|
|
|
|
yield id, arch
|
|
|
|
for info in self.iter(
|
|
|
|
cr, uid, id, model, exclude_base=True, context=None):
|
|
|
|
yield info
|
|
|
|
|
2013-04-24 09:51:14 +00:00
|
|
|
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
|
|
|
|
"""
|
2013-04-24 13:09:07 +00:00
|
|
|
ids = self.search(cr, uid, [
|
2013-04-24 09:51:14 +00:00
|
|
|
['model', '=', model],
|
|
|
|
['type', '=', view_type],
|
|
|
|
['inherit_id', '=', False],
|
2013-04-24 13:09:07 +00:00
|
|
|
], 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]
|
2013-04-24 09:51:14 +00:00
|
|
|
|
|
|
|
def root_ancestor(self, cr, uid, view_id, context=None):
|
2013-04-23 12:46:57 +00:00
|
|
|
"""
|
2013-04-24 09:51:14 +00:00
|
|
|
Fetches the id of the root of the view tree of which view_id is part
|
2013-04-23 12:46:57 +00:00
|
|
|
|
|
|
|
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
|
2013-04-23 15:08:44 +00:00
|
|
|
:return: id of the root view for the tree
|
2013-04-23 12:46:57 +00:00
|
|
|
"""
|
2013-04-23 13:57:39 +00:00
|
|
|
view = self.browse(cr, uid, view_id, context=context)
|
2013-04-24 13:09:07 +00:00
|
|
|
if not view.exists():
|
|
|
|
raise self.NoViewError(
|
|
|
|
_("No view for id %s, root ancestor not available") % view_id)
|
2013-04-23 13:57:39 +00:00
|
|
|
|
2013-04-23 12:46:57 +00:00
|
|
|
# Search for a root (i.e. without any parent) view.
|
2013-04-23 13:57:39 +00:00
|
|
|
while view.inherit_id:
|
|
|
|
view = view.inherit_id
|
|
|
|
|
2013-04-23 15:08:44 +00:00
|
|
|
return view.id
|
2013-04-23 12:46:57 +00:00
|
|
|
|
2013-04-24 09:51:14 +00:00
|
|
|
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)
|
2013-04-23 10:58:56 +00:00
|
|
|
|
|
|
|
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 to a source architecture all the spec nodes (i.e. nodes
|
|
|
|
describing where and what changes to apply to some parent
|
|
|
|
architecture) given by an inheriting view.
|
|
|
|
|
2013-04-24 09:51:14 +00:00
|
|
|
:param Element source: a parent architecture to modify
|
2013-04-23 10:58:56 +00:00
|
|
|
:param descendant_id: the database id of the descendant
|
|
|
|
:param specs_arch: a modifying architecture in an inheriting view
|
|
|
|
:return: a modified source where the specs are applied
|
2013-04-24 09:51:14 +00:00
|
|
|
:rtype: Element
|
2013-04-23 10:58:56 +00:00
|
|
|
"""
|
|
|
|
if isinstance(specs_arch, unicode):
|
|
|
|
specs_arch = specs_arch.encode('utf-8')
|
|
|
|
specs_tree = etree.fromstring(specs_arch)
|
|
|
|
# Queue of specification nodes (i.e. nodes describing where and
|
|
|
|
# changes to apply to some parent architecture).
|
|
|
|
specs = [specs_tree]
|
|
|
|
|
|
|
|
while len(specs):
|
|
|
|
spec = specs.pop(0)
|
|
|
|
if isinstance(spec, SKIPPED_ELEMENT_TYPES):
|
|
|
|
continue
|
|
|
|
if spec.tag == 'data':
|
|
|
|
specs += [ c for c in specs_tree ]
|
|
|
|
continue
|
|
|
|
node = self.locate_node(source, spec)
|
|
|
|
if node is not None:
|
|
|
|
pos = spec.get('position', 'inside')
|
|
|
|
if pos == 'replace':
|
|
|
|
if node.getparent() is None:
|
|
|
|
source = copy.deepcopy(spec[0])
|
|
|
|
else:
|
|
|
|
for child in spec:
|
|
|
|
node.addprevious(child)
|
|
|
|
node.getparent().remove(node)
|
|
|
|
elif pos == 'attributes':
|
|
|
|
for child in spec.getiterator('attribute'):
|
|
|
|
attribute = (child.get('name'), child.text and child.text.encode('utf8') or None)
|
|
|
|
if attribute[1]:
|
|
|
|
node.set(attribute[0], attribute[1])
|
|
|
|
else:
|
|
|
|
del(node.attrib[attribute[0]])
|
|
|
|
else:
|
|
|
|
sib = node.getnext()
|
|
|
|
for child in spec:
|
|
|
|
if pos == 'inside':
|
|
|
|
node.append(child)
|
|
|
|
elif pos == 'after':
|
|
|
|
if sib is None:
|
|
|
|
node.addnext(child)
|
|
|
|
node = child
|
|
|
|
else:
|
|
|
|
sib.addprevious(child)
|
|
|
|
elif pos == 'before':
|
|
|
|
node.addprevious(child)
|
|
|
|
else:
|
|
|
|
self.raise_view_error(cr, uid, model, "Invalid position value: '%s'" % pos, root_view_id, descendant_id, context=context)
|
|
|
|
else:
|
|
|
|
attrs = ''.join([
|
|
|
|
' %s="%s"' % (attr, spec.get(attr))
|
|
|
|
for attr in spec.attrib
|
|
|
|
if attr != 'position'
|
|
|
|
])
|
|
|
|
tag = "<%s%s>" % (spec.tag, attrs)
|
|
|
|
if spec.get('version') and spec.get('version') != source.get('version'):
|
|
|
|
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
|
|
|
|
|
2011-11-07 15:19:49 +00:00
|
|
|
def write(self, cr, uid, ids, vals, context=None):
|
2008-08-07 10:57:47 +00:00
|
|
|
if not isinstance(ids, (list, tuple)):
|
2008-09-23 05:36:21 +00:00
|
|
|
ids = [ids]
|
2008-08-07 10:57:47 +00:00
|
|
|
|
2011-01-07 16:03:37 +00:00
|
|
|
# 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)
|
2008-08-07 10:57:47 +00:00
|
|
|
|
2012-09-24 10:40:26 +00:00
|
|
|
return super(view, self).write(cr, uid, ids, vals, context)
|
2010-02-02 11:54:22 +00:00
|
|
|
|
2011-11-07 15:19:49 +00:00
|
|
|
def graph_get(self, cr, uid, id, model, node_obj, conn_obj, src_node, des_node, label, scale, context=None):
|
2010-02-22 10:18:23 +00:00
|
|
|
nodes=[]
|
|
|
|
nodes_name=[]
|
|
|
|
transitions=[]
|
|
|
|
start=[]
|
|
|
|
tres={}
|
2010-04-13 12:57:53 +00:00
|
|
|
labels={}
|
2010-02-22 10:18:23 +00:00
|
|
|
no_ancester=[]
|
2010-02-26 09:53:21 +00:00
|
|
|
blank_nodes = []
|
2010-02-22 10:18:23 +00:00
|
|
|
|
2013-03-29 14:07:23 +00:00
|
|
|
_Model_Obj = self.pool[model]
|
|
|
|
_Node_Obj = self.pool[node_obj]
|
|
|
|
_Arrow_Obj = self.pool[conn_obj]
|
2010-02-05 12:58:52 +00:00
|
|
|
|
|
|
|
for model_key,model_value in _Model_Obj._columns.items():
|
2010-02-05 14:39:02 +00:00
|
|
|
if model_value._type=='one2many':
|
2010-02-05 12:58:52 +00:00
|
|
|
if model_value._obj==node_obj:
|
|
|
|
_Node_Field=model_key
|
2010-02-10 11:45:18 +00:00
|
|
|
_Model_Field=model_value._fields_id
|
2010-02-05 12:58:52 +00:00
|
|
|
flag=False
|
|
|
|
for node_key,node_value in _Node_Obj._columns.items():
|
|
|
|
if node_value._type=='one2many':
|
2010-02-10 10:44:16 +00:00
|
|
|
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
|
2010-02-05 12:58:52 +00:00
|
|
|
|
|
|
|
datas = _Model_Obj.read(cr, uid, id, [],context)
|
|
|
|
for a in _Node_Obj.read(cr,uid,datas[_Node_Field],[]):
|
2010-02-26 08:30:41 +00:00
|
|
|
if a[_Source_Field] or a[_Destination_Field]:
|
|
|
|
nodes_name.append((a['id'],a['name']))
|
|
|
|
nodes.append(a['id'])
|
2010-02-26 09:53:21 +00:00
|
|
|
else:
|
|
|
|
blank_nodes.append({'id': a['id'],'name':a['name']})
|
|
|
|
|
2010-02-10 10:44:16 +00:00
|
|
|
if a.has_key('flow_start') and a['flow_start']:
|
2010-02-05 12:58:52 +00:00
|
|
|
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]))
|
2010-02-18 06:38:58 +00:00
|
|
|
tres[str(t['id'])] = (a['id'],t[des_node][0])
|
2010-04-13 13:18:40 +00:00
|
|
|
label_string = ""
|
2010-04-14 11:59:14 +00:00
|
|
|
if label:
|
|
|
|
for lbl in eval(label):
|
2011-08-03 11:05:59 +00:00
|
|
|
if t.has_key(tools.ustr(lbl)) and tools.ustr(t[lbl])=='False':
|
2012-12-14 13:19:24 +00:00
|
|
|
label_string += ' '
|
2010-04-14 11:59:14 +00:00
|
|
|
else:
|
2011-08-03 11:05:59 +00:00
|
|
|
label_string = label_string + " " + tools.ustr(t[lbl])
|
2010-04-13 13:18:40 +00:00
|
|
|
labels[str(t['id'])] = (a['id'],label_string)
|
2010-02-05 12:58:52 +00:00
|
|
|
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]
|
2010-08-15 11:00:28 +00:00
|
|
|
return {'nodes': results,
|
|
|
|
'transitions': tres,
|
|
|
|
'label' : labels,
|
|
|
|
'blank_nodes': blank_nodes,
|
|
|
|
'node_parent_field': _Model_Field,}
|
2006-12-07 13:41:40 +00:00
|
|
|
|
2013-04-24 13:58:25 +00:00
|
|
|
# this really needs to be refixtored
|
|
|
|
def __view_look_dom(self, cr, user, model, node, view_id, in_tree_view, model_fields, context=None):
|
|
|
|
"""Return the description of the fields in the node.
|
|
|
|
|
|
|
|
In a normal call to this method, node is a complete view architecture
|
|
|
|
but it is actually possible to give some sub-node (this is used so
|
|
|
|
that the method can call itself recursively).
|
|
|
|
|
|
|
|
Originally, the field descriptions are drawn from the node itself.
|
|
|
|
But there is now some code calling fields_get() in order to merge some
|
|
|
|
of those information in the architecture.
|
|
|
|
|
|
|
|
"""
|
|
|
|
if context is None:
|
|
|
|
context = {}
|
|
|
|
result = False
|
|
|
|
fields = {}
|
|
|
|
children = True
|
|
|
|
|
|
|
|
modifiers = {}
|
|
|
|
Model = self.pool[model]
|
|
|
|
|
|
|
|
def encode(s):
|
|
|
|
if isinstance(s, unicode):
|
|
|
|
return s.encode('utf8')
|
|
|
|
return s
|
|
|
|
|
|
|
|
def check_group(node):
|
|
|
|
"""Apply group restrictions, may be set at view level or model level::
|
|
|
|
* at view level this means the element should be made invisible to
|
|
|
|
people who are not members
|
|
|
|
* at model level (exclusively for fields, obviously), this means
|
|
|
|
the field should be completely removed from the view, as it is
|
|
|
|
completely unavailable for non-members
|
|
|
|
|
|
|
|
:return: True if field should be included in the result of fields_view_get
|
|
|
|
"""
|
|
|
|
if node.tag == 'field' and node.get('name') in Model._all_columns:
|
|
|
|
column = Model._all_columns[node.get('name')].column
|
|
|
|
if column.groups and not self.user_has_groups(cr, user,
|
|
|
|
groups=column.groups,
|
|
|
|
context=context):
|
|
|
|
node.getparent().remove(node)
|
|
|
|
fields.pop(node.get('name'), None)
|
|
|
|
# no point processing view-level ``groups`` anymore, return
|
|
|
|
return False
|
|
|
|
if node.get('groups'):
|
|
|
|
can_see = self.user_has_groups(cr, user,
|
|
|
|
groups=node.get('groups'),
|
|
|
|
context=context)
|
|
|
|
if not can_see:
|
|
|
|
node.set('invisible', '1')
|
|
|
|
modifiers['invisible'] = True
|
|
|
|
if 'attrs' in node.attrib:
|
|
|
|
del(node.attrib['attrs']) #avoid making field visible later
|
|
|
|
del(node.attrib['groups'])
|
|
|
|
return True
|
|
|
|
|
|
|
|
if node.tag in ('field', 'node', 'arrow'):
|
|
|
|
if node.get('object'):
|
|
|
|
attrs = {}
|
|
|
|
views = {}
|
|
|
|
xml = "<form>"
|
|
|
|
for f in node:
|
|
|
|
if f.tag == 'field':
|
|
|
|
xml += etree.tostring(f, encoding="utf-8")
|
|
|
|
xml += "</form>"
|
|
|
|
new_xml = etree.fromstring(encode(xml))
|
|
|
|
ctx = context.copy()
|
|
|
|
ctx['base_model_name'] = model
|
|
|
|
xarch, xfields = self.__view_look_dom_arch(cr, user, node.get('object'), new_xml, view_id, ctx)
|
|
|
|
views['form'] = {
|
|
|
|
'arch': xarch,
|
|
|
|
'fields': xfields
|
|
|
|
}
|
|
|
|
attrs = {'views': views}
|
|
|
|
fields = xfields
|
|
|
|
if node.get('name'):
|
|
|
|
attrs = {}
|
|
|
|
try:
|
|
|
|
if node.get('name') in Model._columns:
|
|
|
|
column = Model._columns[node.get('name')]
|
|
|
|
else:
|
|
|
|
column = Model._inherit_fields[node.get('name')][2]
|
|
|
|
except Exception:
|
|
|
|
column = False
|
|
|
|
|
|
|
|
if column:
|
|
|
|
relation = self.pool[column._obj] if column._obj else None
|
|
|
|
|
|
|
|
children = False
|
|
|
|
views = {}
|
|
|
|
for f in node:
|
|
|
|
if f.tag in ('form', 'tree', 'graph', 'kanban'):
|
|
|
|
node.remove(f)
|
|
|
|
ctx = context.copy()
|
|
|
|
ctx['base_model_name'] = Model
|
|
|
|
xarch, xfields = self.__view_look_dom_arch(cr, user, column._obj or None, f, view_id, ctx)
|
|
|
|
views[str(f.tag)] = {
|
|
|
|
'arch': xarch,
|
|
|
|
'fields': xfields
|
|
|
|
}
|
|
|
|
attrs = {'views': views}
|
|
|
|
if node.get('widget') and node.get('widget') == 'selection':
|
|
|
|
# Prepare the cached selection list for the client. This needs to be
|
|
|
|
# done even when the field is invisible to the current user, because
|
|
|
|
# other events could need to change its value to any of the selectable ones
|
|
|
|
# (such as on_change events, refreshes, etc.)
|
|
|
|
|
|
|
|
# If domain and context are strings, we keep them for client-side, otherwise
|
|
|
|
# we evaluate them server-side to consider them when generating the list of
|
|
|
|
# possible values
|
|
|
|
# TODO: find a way to remove this hack, by allow dynamic domains
|
|
|
|
dom = []
|
|
|
|
if column._domain and not isinstance(column._domain, basestring):
|
|
|
|
dom = list(column._domain)
|
|
|
|
dom += eval(node.get('domain', '[]'), {'uid': user, 'time': time})
|
|
|
|
search_context = dict(context)
|
|
|
|
if column._context and not isinstance(column._context, basestring):
|
|
|
|
search_context.update(column._context)
|
|
|
|
attrs['selection'] = relation._name_search(cr, user, '', dom, context=search_context, limit=None, name_get_uid=1)
|
|
|
|
if (node.get('required') and not int(node.get('required'))) or not column.required:
|
|
|
|
attrs['selection'].append((False, ''))
|
|
|
|
fields[node.get('name')] = attrs
|
|
|
|
|
|
|
|
field = model_fields.get(node.get('name'))
|
|
|
|
if field:
|
|
|
|
orm.transfer_field_to_modifiers(field, modifiers)
|
|
|
|
|
|
|
|
|
|
|
|
elif node.tag in ('form', 'tree'):
|
|
|
|
result = Model.view_header_get(cr, user, False, node.tag, context)
|
|
|
|
if result:
|
|
|
|
node.set('string', result)
|
|
|
|
in_tree_view = node.tag == 'tree'
|
|
|
|
|
|
|
|
elif node.tag == 'calendar':
|
|
|
|
for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
|
|
|
|
if node.get(additional_field):
|
|
|
|
fields[node.get(additional_field)] = {}
|
|
|
|
|
|
|
|
if not check_group(node):
|
|
|
|
# node must be removed, no need to proceed further with its children
|
|
|
|
return fields
|
|
|
|
|
|
|
|
# The view architeture overrides the python model.
|
|
|
|
# Get the attrs before they are (possibly) deleted by check_group below
|
|
|
|
orm.transfer_node_to_modifiers(node, modifiers, context, in_tree_view)
|
|
|
|
|
|
|
|
# TODO remove attrs couterpart in modifiers when invisible is true ?
|
|
|
|
|
|
|
|
# translate view
|
|
|
|
if 'lang' in context:
|
|
|
|
Translations = self.pool['ir.translation']
|
|
|
|
if node.text and node.text.strip():
|
|
|
|
trans = Translations._get_source(cr, user, model, 'view', context['lang'], node.text.strip())
|
|
|
|
if trans:
|
|
|
|
node.text = node.text.replace(node.text.strip(), trans)
|
|
|
|
if node.tail and node.tail.strip():
|
|
|
|
trans = Translations._get_source(cr, user, model, 'view', context['lang'], node.tail.strip())
|
|
|
|
if trans:
|
|
|
|
node.tail = node.tail.replace(node.tail.strip(), trans)
|
|
|
|
|
|
|
|
if node.get('string') and not result:
|
|
|
|
trans = Translations._get_source(cr, user, model, 'view', context['lang'], node.get('string'))
|
|
|
|
if trans == node.get('string') and ('base_model_name' in context):
|
|
|
|
# If translation is same as source, perhaps we'd have more luck with the alternative model name
|
|
|
|
# (in case we are in a mixed situation, such as an inherited view where parent_view.model != model
|
|
|
|
trans = Translations._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string'))
|
|
|
|
if trans:
|
|
|
|
node.set('string', trans)
|
|
|
|
|
|
|
|
for attr_name in ('confirm', 'sum', 'avg', 'help', 'placeholder'):
|
|
|
|
attr_value = node.get(attr_name)
|
|
|
|
if attr_value:
|
|
|
|
trans = Translations._get_source(cr, user, model, 'view', context['lang'], attr_value)
|
|
|
|
if trans:
|
|
|
|
node.set(attr_name, trans)
|
|
|
|
|
|
|
|
for f in node:
|
|
|
|
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))
|
|
|
|
|
|
|
|
orm.transfer_modifiers_to_node(modifiers, node)
|
|
|
|
return fields
|
|
|
|
|
|
|
|
def _disable_workflow_buttons(self, cr, user, model, node):
|
|
|
|
""" Set the buttons in node to readonly if the user can't activate them. """
|
|
|
|
if user == 1:
|
|
|
|
# admin user can always activate workflow buttons
|
|
|
|
return node
|
|
|
|
|
|
|
|
# TODO handle the case of more than one workflow for a model or multiple
|
|
|
|
# transitions with different groups and same signal
|
|
|
|
usersobj = self.pool.get('res.users')
|
|
|
|
buttons = (n for n in node.getiterator('button') if n.get('type') != 'object')
|
|
|
|
for button in buttons:
|
|
|
|
user_groups = usersobj.read(cr, user, [user], ['groups_id'])[0]['groups_id']
|
|
|
|
cr.execute("""SELECT DISTINCT t.group_id
|
|
|
|
FROM wkf
|
|
|
|
INNER JOIN wkf_activity a ON a.wkf_id = wkf.id
|
|
|
|
INNER JOIN wkf_transition t ON (t.act_to = a.id)
|
|
|
|
WHERE wkf.osv = %s
|
|
|
|
AND t.signal = %s
|
|
|
|
AND t.group_id is NOT NULL
|
|
|
|
""", (model, button.get('name')))
|
|
|
|
group_ids = [x[0] for x in cr.fetchall() if x[0]]
|
|
|
|
can_click = not group_ids or bool(set(user_groups).intersection(group_ids))
|
|
|
|
button.set('readonly', str(int(not can_click)))
|
|
|
|
return node
|
|
|
|
|
|
|
|
def __view_look_dom_arch(self, cr, user, model, node, view_id, context=None):
|
|
|
|
""" Return an architecture and a description of all the fields.
|
|
|
|
|
|
|
|
The field description combines the result of fields_get() and
|
|
|
|
__view_look_dom().
|
|
|
|
|
|
|
|
:param node: the architecture as as an etree
|
|
|
|
:return: a tuple (arch, fields) where arch is the given node as a
|
|
|
|
string and fields is the description of all the fields.
|
|
|
|
|
|
|
|
"""
|
|
|
|
fields = {}
|
|
|
|
Model = self.pool[model]
|
|
|
|
if node.tag == 'diagram':
|
|
|
|
if node.getchildren()[0].tag == 'node':
|
|
|
|
node_model = self.pool[node.getchildren()[0].get('object')]
|
|
|
|
node_fields = node_model.fields_get(cr, user, None, context)
|
|
|
|
fields.update(node_fields)
|
|
|
|
if not node.get("create") and not node_model.check_access_rights(cr, user, 'create', raise_exception=False):
|
|
|
|
node.set("create", 'false')
|
|
|
|
if node.getchildren()[1].tag == 'arrow':
|
|
|
|
arrow_fields = self.pool[node.getchildren()[1].get('object')].fields_get(cr, user, None, context)
|
|
|
|
fields.update(arrow_fields)
|
|
|
|
else:
|
|
|
|
fields = Model.fields_get(cr, user, None, context)
|
|
|
|
fields_def = self.__view_look_dom(cr, user, model, node, view_id, False, fields, context=context)
|
|
|
|
node = self._disable_workflow_buttons(cr, user, model, node)
|
|
|
|
if node.tag in ('kanban', 'tree', 'form', 'gantt'):
|
|
|
|
for action, operation in (('create', 'create'), ('delete', 'unlink'), ('edit', 'write')):
|
|
|
|
if not node.get(action) and not Model.check_access_rights(cr, user, operation, raise_exception=False):
|
|
|
|
node.set(action, 'false')
|
|
|
|
arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
|
|
|
|
for k in fields.keys():
|
|
|
|
if k not in fields_def:
|
|
|
|
del fields[k]
|
|
|
|
for field in fields_def:
|
|
|
|
if field == 'id':
|
|
|
|
# sometime, the view may contain the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
|
|
|
|
fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
|
|
|
|
elif field in fields:
|
|
|
|
fields[field].update(fields_def[field])
|
|
|
|
else:
|
|
|
|
cr.execute('select name, model from ir_ui_view where (id=%s or inherit_id=%s) and arch like %s', (view_id, view_id, '%%%s%%' % field))
|
|
|
|
res = cr.fetchall()[:]
|
|
|
|
model = res[0][1]
|
|
|
|
res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
|
|
|
|
msg = "\n * ".join([r[0] for r in res])
|
|
|
|
msg += "\n\nEither you wrongly customized this view, or some modules bringing those views are not compatible with your current data model"
|
|
|
|
_logger.error(msg)
|
|
|
|
raise orm.except_orm('View error', msg)
|
|
|
|
return arch, fields
|
|
|
|
|
2006-12-07 13:41:40 +00:00
|
|
|
class view_sc(osv.osv):
|
2008-07-22 14:24:36 +00:00
|
|
|
_name = 'ir.ui.view_sc'
|
|
|
|
_columns = {
|
2011-01-15 04:50:58 +00:00
|
|
|
'name': fields.char('Shortcut Name', size=64), # Kept for backwards compatibility only - resource name used instead (translatable)
|
2011-01-10 13:55:17 +00:00
|
|
|
'res_id': fields.integer('Resource Ref.', help="Reference of the target resource, whose model/table depends on the 'Resource Name' field."),
|
2008-07-22 14:24:36 +00:00
|
|
|
'sequence': fields.integer('Sequence'),
|
2010-12-10 12:47:16 +00:00
|
|
|
'user_id': fields.many2one('res.users', 'User Ref.', required=True, ondelete='cascade', select=True),
|
|
|
|
'resource': fields.char('Resource Name', size=64, required=True, select=True)
|
2008-07-22 14:24:36 +00:00
|
|
|
}
|
2008-06-12 10:45:39 +00:00
|
|
|
|
2010-12-10 12:47:16 +00:00
|
|
|
def _auto_init(self, cr, context=None):
|
|
|
|
super(view_sc, self)._auto_init(cr, context)
|
|
|
|
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_ui_view_sc_user_id_resource\'')
|
|
|
|
if not cr.fetchone():
|
|
|
|
cr.execute('CREATE INDEX ir_ui_view_sc_user_id_resource ON ir_ui_view_sc (user_id, resource)')
|
|
|
|
|
2011-01-15 04:50:58 +00:00
|
|
|
def get_sc(self, cr, uid, user_id, model='ir.ui.menu', context=None):
|
2008-07-22 14:24:36 +00:00
|
|
|
ids = self.search(cr, uid, [('user_id','=',user_id),('resource','=',model)], context=context)
|
2011-06-24 05:40:56 +00:00
|
|
|
results = self.read(cr, uid, ids, ['res_id'], context=context)
|
2013-03-29 14:07:23 +00:00
|
|
|
name_map = dict(self.pool[model].name_get(cr, uid, [x['res_id'] for x in results], context=context))
|
2011-04-06 10:05:07 +00:00
|
|
|
# Make sure to return only shortcuts pointing to exisintg menu items.
|
2011-06-24 05:40:56 +00:00
|
|
|
filtered_results = filter(lambda result: result['res_id'] in name_map, results)
|
|
|
|
for result in filtered_results:
|
|
|
|
result.update(name=name_map[result['res_id']])
|
|
|
|
return filtered_results
|
2007-06-28 18:00:41 +00:00
|
|
|
|
2010-12-10 22:42:58 +00:00
|
|
|
_order = 'sequence,name'
|
2008-07-22 14:24:36 +00:00
|
|
|
_defaults = {
|
2012-11-02 09:47:05 +00:00
|
|
|
'resource': 'ir.ui.menu',
|
2008-07-22 14:24:36 +00:00
|
|
|
'user_id': lambda obj, cr, uid, context: uid,
|
|
|
|
}
|
2010-05-12 11:48:23 +00:00
|
|
|
_sql_constraints = [
|
2010-08-19 09:06:50 +00:00
|
|
|
('shortcut_unique', 'unique(res_id, resource, user_id)', 'Shortcut for this menu already exists!'),
|
2010-05-12 11:48:23 +00:00
|
|
|
]
|
2010-09-27 07:12:29 +00:00
|
|
|
|
2008-07-23 15:01:27 +00:00
|
|
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
|
|
|