X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=openerp%2Faddons%2Fbase%2Fir%2Fir_ui_view.py;h=09f73662070c9d59d0b8ccbc7cf1870997ce8837;hb=72efeaa44fb33b541c646467f7d54ae1807a1459;hp=eeb35e306367f730cb6a3c6b7d884cc2f30873d6;hpb=5ef9dd5f3f87855cb6ed9df6f4af6b32b6b54097;p=odoo%2Fodoo.git diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py index eeb35e3..09f7366 100644 --- a/openerp/addons/base/ir/ir_ui_view.py +++ b/openerp/addons/base/ir/ir_ui_view.py @@ -19,13 +19,17 @@ # ############################################################################## -from osv import fields,osv +import logging from lxml import etree -from tools import graph -from tools.safe_eval import safe_eval as eval -import tools import os -import logging + +from openerp import tools +from openerp.osv import fields,osv +from openerp.tools import graph +from openerp.tools.safe_eval import safe_eval as eval +from openerp.tools.view_validation import valid_view + +_logger = logging.getLogger(__name__) class view_custom(osv.osv): _name = 'ir.ui.view.custom' @@ -41,15 +45,25 @@ class view_custom(osv.osv): 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)') -view_custom() class view(osv.osv): _name = 'ir.ui.view' + + 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 + _columns = { - 'name': fields.char('View Name',size=64, required=True), + 'name': fields.char('View Name', required=True), 'model': fields.char('Object', size=64, required=True, select=True), 'priority': fields.integer('Sequence', required=True), - 'type': fields.selection(( + 'type': fields.function(_type_field, type='selection', selection=[ ('tree','Tree'), ('form','Form'), ('mdx','mdx'), @@ -58,33 +72,84 @@ class view(osv.osv): ('diagram','Diagram'), ('gantt', 'Gantt'), ('kanban', 'Kanban'), - ('search','Search')), 'View Type', required=True, select=True), + ('search','Search')], string='View Type', required=True, select=True, store=True), 'arch': fields.text('View Architecture', required=True), 'inherit_id': fields.many2one('ir.ui.view', 'Inherited View', ondelete='cascade', select=True), 'field_parent': fields.char('Child Field',size=64), 'xml_id': fields.function(osv.osv.get_xml_id, type='char', size=128, string="External ID", - method=True, help="ID of the view defined in xml file"), + help="ID of the view defined in xml file"), + '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."), } _defaults = { 'arch': '\n\n\t\n', - 'priority': 16 + 'priority': 16, + 'type': 'tree', } _order = "priority,name" - def _check_xml(self, cr, uid, ids, context=None): - logger = logging.getLogger('init') - for view in self.browse(cr, uid, ids, context): - eview = etree.fromstring(view.arch.encode('utf8')) + # Holds the RNG schema + _relaxng_validator = None + + 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.") + 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) + return super(osv.osv, self).create(cr, uid, values, context) + + def _relaxng(self): + if not self._relaxng_validator: frng = tools.file_open(os.path.join('base','rng','view.rng')) try: relaxng_doc = etree.parse(frng) - relaxng = etree.RelaxNG(relaxng_doc) - if not relaxng.validate(eview): - for error in relaxng.error_log: - logger.error(tools.ustr(error)) - return False + self._relaxng_validator = etree.RelaxNG(relaxng_doc) + except Exception: + _logger.exception('Failed to load RelaxNG XML schema for views validation') finally: frng.close() + return self._relaxng_validator + + 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 + @return: the rendered definition (arch) of the view, always utf-8 bytestring (legacy convention) + if no error occurred, else False. + """ + try: + fvg = self.pool.get(view.model).fields_view_get(cr, uid, view_id=view.id, view_type=view.type, context=context) + return fvg['arch'] + 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): + # 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 + + # RNG-based validation is not possible anymore with 7.0 forms + # TODO 7.0: provide alternative assertion-based validation of view_arch_utf8 + view_docs = [etree.fromstring(view_arch_utf8)] + if view_docs[0].tag == 'data': + # A 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 + if not valid_view(view_arch): + return False return True _constraints = [ @@ -95,25 +160,44 @@ class view(osv.osv): 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(): - cr.execute('CREATE INDEX ir_ui_view_model_type_inherit_id ON ir_ui_view (model, type, inherit_id)') + cr.execute('CREATE INDEX ir_ui_view_model_type_inherit_id ON ir_ui_view (model, inherit_id)') def get_inheriting_views_arch(self, cr, uid, view_id, model, context=None): - """Retrieves the architecture of views that inherit from the given view. + """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), ...] """ - cr.execute("""SELECT arch, id FROM ir_ui_view WHERE inherit_id=%s AND model=%s - ORDER BY priority""", - (view_id, model)) - return cr.fetchall() + user_groups = frozenset(self.pool.get('res.users').browse(cr, 1, uid, context).groups_id) + if self.pool._init: + # Module init currently in progress, only consider views from modules whose code was already loaded + query = """SELECT v.id FROM ir_ui_view v LEFT JOIN ir_model_data md ON (md.model = 'ir.ui.view' AND md.res_id = v.id) + WHERE v.inherit_id=%s AND v.model=%s AND md.module in %s + ORDER BY priority""" + query_params = (view_id, model, tuple(self.pool._init_modules)) + else: + # Modules fully loaded, consider all views + query = """SELECT v.id FROM ir_ui_view v + WHERE v.inherit_id=%s AND v.model=%s + ORDER BY priority""" + query_params = (view_id, model) + cr.execute(query, query_params) + view_ids = [v[0] for v in cr.fetchall()] + # 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 write(self, cr, uid, ids, vals, context=None): if not isinstance(ids, (list, tuple)): ids = [ids] - result = super(view, self).write(cr, uid, ids, vals, context) # drop the corresponding view customizations (used for dashboards for example), otherwise # not all users would see the updated views @@ -121,11 +205,9 @@ class view(osv.osv): if custom_view_ids: self.pool.get('ir.ui.view.custom').unlink(cr, uid, custom_view_ids) - return result + 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): - if not label: - label = [] nodes=[] nodes_name=[] transitions=[] @@ -174,7 +256,7 @@ class view(osv.osv): if label: for lbl in eval(label): if t.has_key(tools.ustr(lbl)) and tools.ustr(t[lbl])=='False': - label_string = label_string + ' ' + label_string += ' ' else: label_string = label_string + " " + tools.ustr(t[lbl]) labels[str(t['id'])] = (a['id'],label_string) @@ -191,7 +273,6 @@ class view(osv.osv): 'label' : labels, 'blank_nodes': blank_nodes, 'node_parent_field': _Model_Field,} -view() class view_sc(osv.osv): _name = 'ir.ui.view_sc' @@ -221,14 +302,12 @@ class view_sc(osv.osv): _order = 'sequence,name' _defaults = { - 'resource': lambda *a: 'ir.ui.menu', + 'resource': 'ir.ui.menu', 'user_id': lambda obj, cr, uid, context: uid, } _sql_constraints = [ ('shortcut_unique', 'unique(res_id, resource, user_id)', 'Shortcut for this menu already exists!'), ] -view_sc() - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: