diff --git a/openerp/addons/base/base_data.xml b/openerp/addons/base/base_data.xml index c1cdd163ec2..b6c12e111ff 100644 --- a/openerp/addons/base/base_data.xml +++ b/openerp/addons/base/base_data.xml @@ -129,19 +129,21 @@ Administrator /dev/null all + + + Public user + + Public user public - + + - - - - diff --git a/openerp/addons/base/ir/ir_http.py b/openerp/addons/base/ir/ir_http.py index a022bb0edec..18699110605 100644 --- a/openerp/addons/base/ir/ir_http.py +++ b/openerp/addons/base/ir/ir_http.py @@ -62,7 +62,6 @@ class ir_http(osv.AbstractModel): raise http.SessionExpiredException("Session expired") def _auth_method_none(self): - request.disable_db = True request.uid = None def _auth_method_public(self): @@ -136,7 +135,7 @@ class ir_http(osv.AbstractModel): m = request.registry.get('ir.module.module') ids = m.search(cr, openerp.SUPERUSER_ID, [('state', '=', 'installed'), ('name', '!=', 'web')], context=request.context) installed = set(x['name'] for x in m.read(cr, 1, ids, ['name'], context=request.context)) - mods = ['', "web"] + sorted(installed) + mods = [''] + openerp.conf.server_wide_modules + sorted(installed) self._routing_map = http.routing_map(mods, False, converters=self._get_converters()) return self._routing_map diff --git a/openerp/addons/base/ir/ir_qweb.py b/openerp/addons/base/ir/ir_qweb.py index 60f5e71b341..6612d097258 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -24,18 +24,26 @@ _logger = logging.getLogger(__name__) #-------------------------------------------------------------------- # QWeb template engine #-------------------------------------------------------------------- - class QWebException(Exception): - def __init__(self, message, template=None, node=None, expression=None, inner=None): + def __init__(self, message, **kw): Exception.__init__(self, message) - self.template = template - self.node = node - self.expression = expression - self.inner = inner + self.qweb = dict(kw) class QWebTemplateNotFound(QWebException): pass +def convert_to_qweb_exception(etype=None, **kw): + if etype is None: + etype = QWebException + orig_type, original, tb = sys.exc_info() + try: + raise etype, original, tb + except etype, e: + for k, v in kw.items(): + e.qweb[k] = v + e.qweb['inner'] = original + return e + class QWebContext(dict): def __init__(self, cr, uid, data, loader=None, templates=None, context=None): self.cr = cr @@ -156,21 +164,21 @@ class QWeb(orm.AbstractModel): if qwebcontext.loader and name not in qwebcontext.templates: try: xml_doc = qwebcontext.loader(name) - except ValueError, e: - raise QWebTemplateNotFound("Loader could not find template %r" % name, template=origin_template, inner=e) + except ValueError: + raise convert_to_qweb_exception(QWebTemplateNotFound, message="Loader could not find template %r" % name, template=origin_template) self.load_document(xml_doc, qwebcontext=qwebcontext) if name in qwebcontext.templates: return qwebcontext.templates[name] - raise QWebTemplateNotFound("Template %r not found" % name, template=origin_template) + raise convert_to_qweb_exception(QWebTemplateNotFound, message="Template %r not found" % name, template=origin_template) def eval(self, expr, qwebcontext): try: return qwebcontext.safe_eval(expr) - except Exception, e: + except Exception: template = qwebcontext.get('__template__') - raise QWebException("Could not evaluate expression %r" % expr, expression=expr, template=template, inner=e) + raise convert_to_qweb_exception(message="Could not evaluate expression %r" % expr, expression=expr, template=template) def eval_object(self, expr, qwebcontext): return self.eval(expr, qwebcontext) @@ -196,9 +204,9 @@ class QWeb(orm.AbstractModel): try: return str(expr % qwebcontext) - except Exception, e: + except Exception: template = qwebcontext.get('__template__') - raise QWebException("Format error for expression %r" % expr, expression=expr, template=template, inner=e) + raise convert_to_qweb_exception(message="Format error for expression %r" % expr, expression=expr, template=template) def eval_bool(self, expr, qwebcontext): return int(bool(self.eval(expr, qwebcontext))) @@ -281,9 +289,9 @@ class QWeb(orm.AbstractModel): g_inner.append(self.render_node(current_node, qwebcontext)) except QWebException: raise - except Exception, e: + except Exception: template = qwebcontext.get('__template__') - raise QWebException("Could not render element %r -- %s" % (element.nodeName, e.message), node=element, template=template, inner=e), None, sys.exc_info()[2] + raise convert_to_qweb_exception(message="Could not render element %r" % element.nodeName, node=element, template=template) name = str(element.nodeName) inner = "".join(g_inner) trim = template_attributes.get("trim", 0) @@ -634,8 +642,8 @@ class ManyToOneConverter(osv.AbstractModel): def record_to_html(self, cr, uid, field_name, record, column, options=None, context=None): [read] = record.read([field_name]) + if not read[field_name]: return '' _, value = read[field_name] - return nl2br(value, options=options) class HTMLConverter(osv.AbstractModel): diff --git a/openerp/addons/base/ir/ir_translation_view.xml b/openerp/addons/base/ir/ir_translation_view.xml index f3f9c4d0fac..4cabab6d1b4 100644 --- a/openerp/addons/base/ir/ir_translation_view.xml +++ b/openerp/addons/base/ir/ir_translation_view.xml @@ -14,7 +14,7 @@ domain="[('comments', 'like', 'openerp-web')]"/> - + diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py index 33354063587..8ea46bcfa4d 100644 --- a/openerp/addons/base/ir/ir_ui_view.py +++ b/openerp/addons/base/ir/ir_ui_view.py @@ -24,10 +24,8 @@ import logging from lxml import etree from operator import itemgetter import os -import time import HTMLParser -from lxml import etree import openerp from openerp import tools @@ -36,7 +34,7 @@ from openerp.tools import graph, SKIPPED_ELEMENT_TYPES from openerp.tools.safe_eval import safe_eval as eval from openerp.tools.view_validation import valid_view from openerp.tools import misc -from openerp.osv.orm import browse_record, browse_record_list +from openerp.tools.translate import _ _logger = logging.getLogger(__name__) @@ -89,7 +87,7 @@ class view(osv.osv): 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."), - 'model_ids': fields.one2many('ir.model.data', 'res_id', auto_join=True), + 'model_ids': fields.one2many('ir.model.data', 'res_id', domain=[('model','=','ir.ui.view')], auto_join=True), } _defaults = { 'priority': 16, @@ -114,20 +112,19 @@ class view(osv.osv): def _check_xml(self, cr, uid, ids, context=None): if context is None: context = {} - context['check_view_ids'] = ids + context = dict(context, check_view_ids=ids) + # Sanity checks: the view should not break anything upon rendering! + # Any exception raised below will cause a transaction rollback. for view in self.browse(cr, uid, ids, context): - # Sanity check: the view should not break anything upon rendering! - try: - fvg = self.read_combined(cr, uid, view.id, None, context=context) - view_arch_utf8 = fvg['arch'] - except Exception, e: - _logger.exception(e) - return False + view_def = self.read_combined(cr, uid, view.id, None, context=context) + view_arch_utf8 = view_def['arch'] if view.type != 'qweb': + view_doc = etree.fromstring(view_arch_utf8) + # verify that all fields used are valid, etc. + self.postprocess_and_fields(cr, uid, view.model, view_doc, view.id, context=context) # 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)] + view_docs = [view_doc] if view_docs[0].tag == 'data': # A element is a wrapper for multiple root nodes view_docs = view_docs[0] @@ -142,7 +139,7 @@ class view(osv.osv): return True _constraints = [ - (_check_xml, 'Invalid XML for View Architecture!', ['arch']) + (_check_xml, 'Invalid view definition', ['arch']) ] def _auto_init(self, cr, context=None): @@ -251,8 +248,21 @@ class view(osv.osv): if not (view.groups_id and user_groups.isdisjoint(view.groups_id))] def raise_view_error(self, cr, uid, message, view_id, context=None): - view = self.browse(cr, uid, [view_id], context)[0] - 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) + view = self.browse(cr, uid, view_id, context) + not_avail = _('n/a') + message = ("%(msg)s\n\n" + + _("Error context:\nView `%(view_name)s`") + + "\n[view_id: %(viewid)s, xml_id: %(xmlid)s, " + "model: %(model)s, parent_id: %(parent)s]") % \ + { + 'view_name': view.name or not_avail, + 'viewid': view_id or not_avail, + 'xmlid': view.xml_id or not_avail, + 'model': view.model or not_avail, + 'parent': view.inherit_id.id or not_avail, + 'msg': message, + } + _logger.error(message) raise AttributeError(message) def locate_node(self, arch, spec): @@ -360,7 +370,7 @@ class view(osv.osv): elif pos == 'before': node.addprevious(child) else: - self.raise_view_error(cr, uid, "Invalid position value: '%s'" % pos, inherit_id, context=context) + self.raise_view_error(cr, uid, _("Invalid position attribute: '%s'") % pos, inherit_id, context=context) else: attrs = ''.join([ ' %s="%s"' % (attr, spec.get(attr)) @@ -368,7 +378,7 @@ class view(osv.osv): if attr != 'position' ]) tag = "<%s%s>" % (spec.tag, attrs) - self.raise_view_error(cr, uid, "Element '%s' not found in parent view " % tag, inherit_id, context=context) + self.raise_view_error(cr, uid, _("Element '%s' cannot be located in parent view") % tag, inherit_id, context=context) return source @@ -458,6 +468,9 @@ class view(osv.osv): modifiers = {} Model = self.pool.get(model) + if not Model: + self.raise_view_error(cr, user, _('Model not found: %(model)s') % dict(model=model), + view_id, context) def encode(s): if isinstance(s, unicode): @@ -474,7 +487,7 @@ class view(osv.osv): :return: True if field should be included in the result of fields_view_get """ - if Model and node.tag == 'field' and node.get('name') in Model._all_columns: + 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): @@ -523,8 +536,6 @@ class view(osv.osv): column = False if column: - relation = self.pool[column._obj] if column._obj else None - children = False views = {} for f in node: @@ -563,7 +574,7 @@ class view(osv.osv): # 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 ? + # TODO remove attrs counterpart in modifiers when invisible is true ? # translate view if 'lang' in context: @@ -638,6 +649,8 @@ class view(osv.osv): """ fields = {} Model = self.pool.get(model) + if not Model: + self.raise_view_error(cr, user, _('Model not found: %(model)s') % dict(model=model), view_id, context) if node.tag == 'diagram': if node.getchildren()[0].tag == 'node': @@ -649,7 +662,7 @@ class view(osv.osv): 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) - elif Model: + else: fields = Model.fields_get(cr, user, None, context) fields_def = self.postprocess(cr, user, model, node, view_id, False, fields, context=context) @@ -669,14 +682,9 @@ class view(osv.osv): 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) + message = _("Field `%(field_name)s` does not exist") % \ + dict(field_name=field) + self.raise_view_error(cr, user, message, view_id, context) return arch, fields #------------------------------------------------------ @@ -687,9 +695,7 @@ class view(osv.osv): if '.' not in xml_id: raise ValueError('Invalid template id: %r' % (xml_id,)) - m, n = xml_id.split('.', 1) - _, view_id = self.pool['ir.model.data'].get_object_reference(cr, uid, m, n) - + view_id = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, xml_id, raise_if_not_found=True) arch = self.read_combined(cr, uid, view_id, fields=['arch'], context=context)['arch'] arch_tree = etree.fromstring(arch) diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index a6b730096f7..9115ccae481 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -23,14 +23,12 @@ import datetime from lxml import etree import math import pytz -import re import openerp from openerp import SUPERUSER_ID from openerp import tools from openerp.osv import osv, fields from openerp.tools.translate import _ -from openerp.tools.yaml_import import is_comment class format_address(object): def fields_view_get_address(self, cr, uid, arch, context={}): @@ -218,12 +216,12 @@ class res_partner(osv.osv, format_address): _display_name = lambda self, *args, **kwargs: self._display_name_compute(*args, **kwargs) _commercial_partner_store_triggers = { - 'res.partner': (lambda self,cr,uid,ids,context=None: self.search(cr, uid, [('id','child_of',ids)]), - ['parent_id', 'is_company'], 10) + 'res.partner': (lambda self,cr,uid,ids,context=None: self.search(cr, uid, [('id','child_of',ids)], context=dict(active_test=False)), + ['parent_id', 'is_company'], 10) } _display_name_store_triggers = { - 'res.partner': (lambda self,cr,uid,ids,context=None: self.search(cr, uid, [('id','child_of',ids)]), - ['parent_id', 'is_company', 'name'], 10) + 'res.partner': (lambda self,cr,uid,ids,context=None: self.search(cr, uid, [('id','child_of',ids)], context=dict(active_test=False)), + ['parent_id', 'is_company', 'name'], 10) } _order = "display_name" @@ -605,7 +603,8 @@ class res_partner(osv.osv, format_address): """ Override search() to always show inactive children when searching via ``child_of`` operator. The ORM will always call search() with a simple domain of the form [('parent_id', 'in', [ids])]. """ # a special ``domain`` is set on the ``child_ids`` o2m to bypass this logic, as it uses similar domain expressions - if len(args) == 1 and len(args[0]) == 3 and args[0][:2] == ('parent_id','in'): + if len(args) == 1 and len(args[0]) == 3 and args[0][:2] == ('parent_id','in') \ + and args[0][2] != [False]: context = dict(context or {}, active_test=False) return super(res_partner, self)._search(cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count, access_rights_uid=access_rights_uid) @@ -614,26 +613,36 @@ class res_partner(osv.osv, format_address): if not args: args = [] if name and operator in ('=', 'ilike', '=ilike', 'like', '=like'): + + self.check_access_rights(cr, uid, 'read') + where_query = self._where_calc(cr, uid, args, context=context) + self._apply_ir_rules(cr, uid, where_query, 'read', context=context) + from_clause, where_clause, where_clause_params = where_query.get_sql() + where_str = where_clause and (" WHERE %s AND " % where_clause) or ' WHERE ' + # search on the name of the contacts and of its company search_name = name if operator in ('ilike', 'like'): search_name = '%%%s%%' % name if operator in ('=ilike', '=like'): operator = operator[1:] - query_args = {'name': search_name} - query = ('''SELECT id FROM res_partner - WHERE email ''' + operator + ''' %(name)s - OR display_name ''' + operator + ''' %(name)s - ORDER BY display_name - ''') + + query = ('SELECT id FROM res_partner ' + + where_str + '(email ' + operator + ''' %s + OR display_name ''' + operator + ''' %s) + ORDER BY display_name''') + + where_clause_params += [search_name, search_name] if limit: - query += ' limit %(limit)s' - query_args['limit'] = limit - cr.execute(query, query_args) + query += ' limit %s' + where_clause_params.append(limit) + cr.execute(query, where_clause_params) ids = map(lambda x: x[0], cr.fetchall()) - ids = self.search(cr, uid, [('id', 'in', ids)] + args, limit=limit, context=context) + if ids: return self.name_get(cr, uid, ids, context) + else: + return [] return super(res_partner,self).name_search(cr, uid, name, args, operator=operator, context=context, limit=limit) def find_or_create(self, cr, uid, email, context=None): diff --git a/openerp/addons/base/tests/test_base.py b/openerp/addons/base/tests/test_base.py index 4cc54aeaf9d..9b6ec4708ac 100644 --- a/openerp/addons/base/tests/test_base.py +++ b/openerp/addons/base/tests/test_base.py @@ -39,6 +39,20 @@ class test_base(common.TransactionCase): new_id2 = self.res_partner.find_or_create(cr, uid, self.samples[2][0]) self.assertTrue(new_id2 > new_id, 'find_or_create failed - should have created new one again') + def test_15_res_partner_name_search(self): + cr,uid = self.cr, self.uid + for name, active in [ + ('"A Raoul Grosbedon" ', False), + ('B Raoul chirurgiens-dentistes.fr', True), + ("C Raoul O'hara ", True), + ('ryu+giga-Sushi@aizubange.fukushima.jp', True), + ]: + partner_id, dummy = self.res_partner.name_create(cr, uid, name, context={'default_active': active}) + partners = self.res_partner.name_search(cr, uid, 'Raoul') + self.assertEqual(len(partners), 2, 'Incorrect search number result for name_search') + partners = self.res_partner.name_search(cr, uid, 'Raoul', limit=1) + self.assertEqual(len(partners), 1, 'Incorrect search number result for name_search with a limit') + self.assertEqual(partners[0][1], 'B Raoul chirurgiens-dentistes.fr', 'Incorrect partner returned, should be the first active') def test_20_res_partner_address_sync(self): cr, uid = self.cr, self.uid diff --git a/openerp/addons/base/tests/test_views.py b/openerp/addons/base/tests/test_views.py index 8e5247e8fba..d93734f48cb 100644 --- a/openerp/addons/base/tests/test_views.py +++ b/openerp/addons/base/tests/test_views.py @@ -1,16 +1,13 @@ # -*- encoding: utf-8 -*- from functools import partial -import unittest2 from lxml import etree as ET from lxml.builder import E from openerp.tests import common -from openerp.tools import mute_logger Field = E.field - class TestNodeLocator(common.BaseCase): """ The node locator returns None when it can not find a node, and the first @@ -143,6 +140,7 @@ class TestViewInheritance(common.TransactionCase): 'name': name, 'arch': arch or self.arch_for(name, parent=parent), 'inherit_id': parent, + 'priority': 5, # higher than default views }) self.ids[name] = view_id return view_id @@ -150,7 +148,7 @@ class TestViewInheritance(common.TransactionCase): def setUp(self): super(TestViewInheritance, self).setUp() - self.model = 'dummy' + self.model = 'ir.ui.view.custom' self.View = self.registry('ir.ui.view') self._init = self.View.pool._init self.View.pool._init = False @@ -368,21 +366,23 @@ class TestNoModel(common.TransactionCase): View = self.registry('ir.ui.view') view_id = View.create(self.cr, self.uid, { 'name': 'dummy', - 'arch': '
', - 'inherit_id': False + 'arch': '