@@ -76,4 +74,4 @@
-
\ No newline at end of file
+
diff --git a/openerp/addons/base/ir/ir_ui_menu.py b/openerp/addons/base/ir/ir_ui_menu.py
index 15d250a7489..77c0c4d738a 100644
--- a/openerp/addons/base/ir/ir_ui_menu.py
+++ b/openerp/addons/base/ir/ir_ui_menu.py
@@ -255,6 +255,9 @@ class ir_ui_menu(osv.osv):
return res
+ def _get_needaction_info(self, cr, uid, id, domain=[], context={}):
+ return [False, 0]
+
def _get_needaction(self, cr, uid, ids, field_names, args, context=None):
if context is None:
context = {}
@@ -278,7 +281,7 @@ class ir_ui_menu(osv.osv):
'menu_id', 'gid', 'Groups', help="If you have groups, the visibility of this menu will be based on these groups. "\
"If this field is empty, OpenERP will compute visibility based on the related object's read access."),
'complete_name': fields.function(_get_full_name,
- string='Complete Name', type='char', size=128),
+ string='Full Path', type='char', size=128),
'icon': fields.selection(tools.icons, 'Icon', size=64),
'icon_pict': fields.function(_get_icon_pict, type='char', size=32),
'web_icon': fields.char('Web Icon File', size=128),
diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py
index 2baf988297f..ac1fc5c1129 100644
--- a/openerp/addons/base/ir/ir_ui_view.py
+++ b/openerp/addons/base/ir/ir_ui_view.py
@@ -73,21 +73,54 @@ class view(osv.osv):
}
_order = "priority,name"
- def _check_xml(self, cr, uid, ids, context=None):
- for view in self.browse(cr, uid, ids, context):
- eview = etree.fromstring(view.arch.encode('utf8'))
- if eview.get('layout') or eview.get('validate'):
- continue
+ # Holds the RNG schema
+ _relaxng_validator = None
+
+ 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: True if the view hierarchy was rendered without any error, False if an error occurred.
+ """
+ try:
+ self.pool.get(view.model).fields_view_get(cr, uid, view_id=view.id, view_type=view.type, context=context)
+ return True
+ 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):
+ # RNG-based validation is not possible anymore with 7.0 forms
+ # TODO 7.0: provide alternative assertion-based validation!
+ view_docs = [etree.fromstring(view.arch.encode('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
+
+ # Second sanity check: the view should not break anything upon rendering!
+ if not self._check_render_view(cr, uid, view, context=context):
+ return False
return True
_constraints = [
@@ -101,16 +134,31 @@ class view(osv.osv):
cr.execute('CREATE INDEX ir_ui_view_model_type_inherit_id ON ir_ui_view (model, type, 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))
+ if self.pool._init:
+ # Module init currently in progress, only consider views from modules whose code was already loaded
+ query = """SELECT v.arch, 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.arch, 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)
return cr.fetchall()
def write(self, cr, uid, ids, vals, context=None):
diff --git a/openerp/addons/base/ir/wizard/wizard_menu_view.xml b/openerp/addons/base/ir/wizard/wizard_menu_view.xml
index d99138d00e6..a9c841a6d69 100644
--- a/openerp/addons/base/ir/wizard/wizard_menu_view.xml
+++ b/openerp/addons/base/ir/wizard/wizard_menu_view.xml
@@ -1,21 +1,19 @@
-
Create Menuwizard.ir.model.menu.createform
-
@@ -107,14 +107,14 @@
search
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py
index 29298c2028c..5eb4f1232ef 100644
--- a/openerp/addons/base/res/res_partner.py
+++ b/openerp/addons/base/res/res_partner.py
@@ -143,7 +143,6 @@ class res_partner(osv.osv):
'comment': fields.text('Notes'),
'address': fields.one2many('res.partner.address', 'partner_id', 'Contacts'), # should be removed in version 7, but kept until then for backward compatibility
'category_id': fields.many2many('res.partner.category', 'res_partner_category_rel', 'partner_id', 'category_id', 'Tags'),
- 'events': fields.one2many('res.partner.event', 'partner_id', 'Events'),
'credit_limit': fields.float(string='Credit Limit'),
'ean13': fields.char('EAN13', size=13),
'active': fields.boolean('Active'),
@@ -205,7 +204,7 @@ class res_partner(osv.osv):
if default is None:
default = {}
name = self.read(cr, uid, [id], ['name'], context)[0]['name']
- default.update({'name': _('%s (copy)')%(name), 'events':[]})
+ default.update({'name': _('%s (copy)')%(name)})
return super(res_partner, self).copy(cr, uid, id, default, context)
def onchange_type(self, cr, uid, ids, is_company, context=None):
diff --git a/openerp/addons/base/res/res_partner_event.py b/openerp/addons/base/res/res_partner_event.py
deleted file mode 100644
index 4425b7e6984..00000000000
--- a/openerp/addons/base/res/res_partner_event.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2009 Tiny SPRL ().
-#
-# This program is free software: you can redistribute it and/or modify
-# 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.
-#
-# 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
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-
-import time
-from osv import osv
-from osv import fields
-
-class res_partner_event(osv.osv):
- _name = "res.partner.event"
- _columns = {
- 'name': fields.char('Events', size=64, required=True),
- 'description': fields.text('Description'),
- 'partner_id': fields.many2one('res.partner', 'Partner', select=True),
- 'date': fields.datetime('Date', size=16),
- 'user_id': fields.many2one('res.users', 'User'),
- }
- _order = 'date desc'
- _defaults = {
- 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
- 'user_id': lambda self, cr, uid, context: uid,
- }
-res_partner_event()
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/addons/base/res/res_partner_event_view.xml b/openerp/addons/base/res/res_partner_event_view.xml
deleted file mode 100644
index 4c164176202..00000000000
--- a/openerp/addons/base/res/res_partner_event_view.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
- res.partner.event.form
- res.partner.event
- form
-
-
-
-
-
-
-
-
-
- Addresses
- ir.actions.act_window
- res.partner.address
- form
- tree,form,kanban
- {"search_default_customer":1}
-
- Customers (also called Partners in other areas of the system) helps you manage your address book of companies whether they are prospects, customers and/or suppliers. The partner form allows you to track and record all the necessary information to interact with your partners from the company address to their contacts as well as pricelists, and much more. If you installed the CRM, with the history tab, you can track all the interactions with a partner such as opportunities, emails, or sales orders issued.
-
-
-
- tree
-
-
-
-
-
- form
-
-
-
-
-
-
- res.partner.address.form2
- res.partner.address
- form
-
-
-
diff --git a/openerp/addons/base/res/wizard/partner_wizard_ean_check.py b/openerp/addons/base/res/wizard/partner_wizard_ean_check.py
deleted file mode 100644
index 14b863439bb..00000000000
--- a/openerp/addons/base/res/wizard/partner_wizard_ean_check.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
-#
-# This program is free software: you can redistribute it and/or modify
-# 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.
-#
-# 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
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-
-import math
-from osv import osv
-from tools.misc import UpdateableStr
-import pooler
-
-class partner_wizard_ean_check(osv.osv_memory):
- """ Ean check """
-
- _name = "partner.wizard.ean.check"
- _description = "Ean Check"
-
- def is_pair(x):
- return not x%2
-
- def get_ean_key(string):
- if not string or string=='':
- return '0'
- if len(string)!=12:
- return '0'
- sum = 0
- for i in range(12):
- if is_pair(i):
- sum = sum + int(string[i])
- else:
- sum = sum + 3*int(string[i])
- return str(int(math.ceil(sum/10.0)*10-sum))
-
- def default_get(self, cr, fields, context):
- partner_table = self.pool.get('res.partner')
- ids = context.get('active_ids')
- partners = partner_table.browse(cr, uid, ids)
- _check_arch_lst = ['', '
', '', '','', '']
- for partner in partners:
- if partner['ean13'] and len(partner['ean13'])>11 and len(partner['ean13'])<14:
- _check_arch_lst.append('' % partner['ean13']);
- key = get_ean_key(partner['ean13'][:12])
- _check_arch_lst.append('')
- if len(partner['ean13'])==12:
- _check_arch_lst.append('');
- else:
- _check_arch_lst.append('' % partner['ean13'][12])
- _check_arch_lst.append('' % key)
- _check_arch_lst.append('
-
-
-
-
- Ean check
- ir.actions.act_window
- partner.wizard.ean.check
- form
- form
- new
-
-
-
-
\ No newline at end of file
diff --git a/openerp/addons/base/res/wizard/partner_wizard_massmail.py b/openerp/addons/base/res/wizard/partner_wizard_massmail.py
deleted file mode 100644
index cc1b80a1710..00000000000
--- a/openerp/addons/base/res/wizard/partner_wizard_massmail.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
-#
-# This program is free software: you can redistribute it and/or modify
-# 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.
-#
-# 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
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-
-from osv import fields, osv
-import re
-import logging
-
-_logger = logging.getLogger(__name__)
-
-class partner_massmail_wizard(osv.osv_memory):
- """ Mass Mailing """
-
- _name = "partner.massmail.wizard"
- _description = "Mass Mailing"
-
- _columns = {
- 'email_from': fields.char("Sender's email", size=256, required=True),
- 'subject': fields.char('Subject', size=256,required=True),
- 'text': fields.text('Message',required=True),
- }
-
- def mass_mail_send(self, cr, uid, ids, context):
- """Send the given mail to all partners whose ids
- are present in ``context['active_ids']``, to
- all addresses with an email set.
-
- :param dict context: ``context['active_ids']``
- should contain the list of
- ids of the partners who should
- receive the mail.
- """
- nbr = 0
- partner_pool = self.pool.get('res.partner')
- data = self.browse(cr, uid, ids[0], context=context)
- event_pool = self.pool.get('res.partner.event')
- assert context['active_model'] == 'res.partner', 'This wizard must be started on a list of Partners'
- active_ids = context.get('active_ids', [])
- partners = partner_pool.browse(cr, uid, active_ids, context)
- subtype = 'plain'
- if re.search('(<(pre)|[pubi].*>)', data.text):
- subtype = 'html'
- ir_mail_server = self.pool.get('ir.mail_server')
- emails_seen = set()
- for partner in partners:
- for adr in partner.child_ids:
- if adr.is_company:
- #we don't want to consider child companies but only the contacts
- continue
- if adr.email and not adr.email in emails_seen:
- try:
- emails_seen.add(adr.email)
- name = adr.name or partner.name
- to = '"%s" <%s>' % (name, adr.email)
- msg = ir_mail_server.build_email(data.email_from, [to], data.subject, data.text, subtype=subtype)
- if ir_mail_server.send_email(cr, uid, msg):
- nbr += 1
- except Exception:
- #ignore failed deliveries, will be logged anyway
- pass
- event_pool.create(cr, uid,
- {'name': 'Email(s) sent through mass mailing',
- 'partner_id': partner.id,
- 'description': data.text })
- _logger.info('Mass-mailing wizard sent %s emails', nbr)
- return {'email_sent': nbr}
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/addons/base/res/wizard/partner_wizard_massmail_view.xml b/openerp/addons/base/res/wizard/partner_wizard_massmail_view.xml
deleted file mode 100644
index 864550b4396..00000000000
--- a/openerp/addons/base/res/wizard/partner_wizard_massmail_view.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
-
- Mass Mailing
- partner.massmail.wizard
- form
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/openerp/addons/base/security/ir.model.access.csv b/openerp/addons/base/security/ir.model.access.csv
index 8b906ba48a5..cc382fcbd11 100644
--- a/openerp/addons/base/security/ir.model.access.csv
+++ b/openerp/addons/base/security/ir.model.access.csv
@@ -63,8 +63,6 @@
"access_res_partner_bank_type_field_group_user","res_partner_bank_type_field group_user","model_res_partner_bank_type_field",,1,0,0,0
"access_res_partner_category_group_user","res_partner_category group_user","model_res_partner_category","group_user",1,0,0,0
"access_res_partner_category_group_partner_manager","res_partner_category group_partner_manager","model_res_partner_category","group_partner_manager",1,1,1,1
-"access_res_partner_event_group_user","res_partner_event group_user","model_res_partner_event","group_user",1,0,0,0
-"access_res_partner_event_group_partner_manager","res_partner_event group_partner_manager","model_res_partner_event","group_partner_manager",1,1,1,1
"access_res_partner_title_group_user","res_partner_title group_user","model_res_partner_title","group_partner_manager",1,1,1,1
"access_res_partner_title_group_partner_manager","res_partner_title group_partner_manager","model_res_partner_title",,1,0,0,0
"access_res_request_group_user","res_request group_user","model_res_request","base.group_user",1,1,1,1
diff --git a/openerp/addons/base/static/src/css/modules.css b/openerp/addons/base/static/src/css/modules.css
index 054d86644a5..6e6ee44ea0c 100644
--- a/openerp/addons/base/static/src/css/modules.css
+++ b/openerp/addons/base/static/src/css/modules.css
@@ -37,3 +37,4 @@
.oe_module_desc .oe_button {
min-width: 70px;
}
+
diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py
index 8ed79a4d615..874a6ce7063 100644
--- a/openerp/modules/loading.py
+++ b/openerp/modules/loading.py
@@ -154,7 +154,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
loaded_modules.append(package.name)
if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
init_module_models(cr, package.name, models)
-
+ pool._init_modules.add(package.name)
status['progress'] = float(index) / len(graph)
# Can't put this line out of the loop: ir.module.module will be
diff --git a/openerp/modules/registry.py b/openerp/modules/registry.py
index 82928c3911b..7c7a5c96f8d 100644
--- a/openerp/modules/registry.py
+++ b/openerp/modules/registry.py
@@ -48,6 +48,10 @@ class Registry(object):
self._store_function = {}
self._init = True
self._init_parent = {}
+
+ # modules fully loaded (maintained during init phase by `loading` module)
+ self._init_modules = set()
+
self.db_name = db_name
self.db = openerp.sql_db.db_connect(db_name)
diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py
index e332799efdc..7d6947a6255 100644
--- a/openerp/osv/orm.py
+++ b/openerp/osv/orm.py
@@ -1859,6 +1859,24 @@ class BaseModel(object):
etree.SubElement(view, 'newline')
return view
+ def _get_default_search_view(self, cr, user, context=None):
+ """ Generates a single-field tree view, using _rec_name if
+ it's one of the columns or the first column it finds otherwise
+
+ :param cr: database cursor
+ :param int user: user id
+ :param dict context: connection context
+ :returns: a tree view as an lxml document
+ :rtype: etree._Element
+ """
+ _rec_name = self._rec_name
+ if _rec_name not in self._columns:
+ _rec_name = self._columns.keys()[0] if len(self._columns.keys()) > 0 else "id"
+
+ view = etree.Element('search', string=self._description)
+ etree.SubElement(view, 'field', name=_rec_name)
+ return view
+
def _get_default_tree_view(self, cr, user, context=None):
""" Generates a single-field tree view, using _rec_name if
it's one of the columns or the first column it finds otherwise
@@ -1928,38 +1946,6 @@ class BaseModel(object):
return view
- def _get_default_search_view(self, cr, uid, context=None):
- """
- :param cr: database cursor
- :param int user: user id
- :param dict context: connection context
- :returns: an lxml document of the view
- :rtype: etree._Element
- """
- form_view = self.fields_view_get(cr, uid, False, 'form', context=context)
- tree_view = self.fields_view_get(cr, uid, False, 'tree', context=context)
-
- # TODO it seems _all_columns could be used instead of fields_get (no need for translated fields info)
- fields = self.fields_get(cr, uid, context=context)
- fields_to_search = set(
- field for field, descriptor in fields.iteritems()
- if descriptor.get('select'))
-
- for view in (form_view, tree_view):
- view_root = etree.fromstring(view['arch'])
- # Only care about select=1 in xpath below, because select=2 is covered
- # by the custom advanced search in clients
- fields_to_search.update(view_root.xpath("//field[@select=1]/@name"))
-
- tree_view_root = view_root # as provided by loop above
- search_view = etree.Element("search", string=tree_view_root.get("string", ""))
-
- field_group = etree.SubElement(search_view, "group")
- for field_name in fields_to_search:
- etree.SubElement(field_group, "field", name=field_name)
-
- return search_view
-
#
# if view_id, view_type is not required
#
@@ -1991,6 +1977,7 @@ class BaseModel(object):
def raise_view_error(error_msg, child_view_id):
view, child_view = self.pool.get('ir.ui.view').browse(cr, user, [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, self._name, error_msg))
@@ -2019,16 +2006,18 @@ class BaseModel(object):
if node.get('name') == spec.get('name'):
return node
return None
- else:
- for node in source.getiterator(spec.tag):
- good = True
- for attr in spec.attrib:
- if attr != 'position' and (not node.get(attr) or node.get(attr) != spec.get(attr)):
- good = False
- break
- if good:
- return node
- return None
+
+ for node in source.getiterator(spec.tag):
+ if isinstance(node, SKIPPED_ELEMENT_TYPES):
+ continue
+ if all(node.get(attr) == spec.get(attr) \
+ for attr in spec.attrib
+ if attr not in ('position','version')):
+ # Version spec should match parent's root element's version
+ if spec.get('version') and spec.get('version') != source.get('version'):
+ return None
+ return node
+ return None
def apply_inheritance_specs(source, specs_arch, inherit_id=None):
""" Apply an inheriting view.
@@ -2094,7 +2083,11 @@ class BaseModel(object):
if attr != 'position'
])
tag = "<%s%s>" % (spec.tag, attrs)
+ if spec.get('version') and spec.get('version') != source.get('version'):
+ raise_view_error("Mismatching view API version for element '%s': %r vs %r in parent view '%%(parent_xml_id)s'" % \
+ (tag, spec.get('version'), source.get('version')), inherit_id)
raise_view_error("Element '%s' not found in parent view '%%(parent_xml_id)s'" % tag, inherit_id)
+
return source
def apply_view_inheritance(cr, user, source, inherit_id):