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': '',
+ 'inherit_id': False,
+ 'type': 'qweb',
})
fields = ['name', 'arch', 'type', 'priority', 'inherit_id', 'model']
[view] = View.read(self.cr, self.uid, [view_id], fields)
self.assertEqual(view, {
'id': view_id,
'name': 'dummy',
- 'arch': '',
- 'type': 'form',
+ 'arch': '',
+ 'type': 'qweb',
'priority': 16,
'inherit_id': False,
'model': False,
})
+ text_para = E.p("", {'class': 'legalese'})
arch = E.body(
E.div(
E.h1("Title"),
@@ -390,43 +390,32 @@ class TestNoModel(common.TransactionCase):
E.p("Welcome!"),
E.div(
E.hr(),
- E.p("Copyright copyrighter", {'class': 'legalese'}),
+ text_para,
id="footer"),
{'class': "index"},)
- def test_fields_mess(self):
- """
- Try to call __view_look_dom_arch without a model provided, will need
- to be altered once it's broken up into sane components
- """
- View = self.registry('ir.ui.view')
- sarch, fields = View.postprocess_and_fields(
- self.cr, self.uid, None, self.arch, None)
-
- self.assertEqual(sarch, ET.tostring(self.arch, encoding='utf-8'))
- self.assertEqual(fields, {})
-
- def test_mess_translation(self):
+ def test_qweb_translation(self):
"""
Test if translations work correctly without a model
"""
View = self.registry('ir.ui.view')
self.registry('res.lang').load_lang(self.cr, self.uid, 'fr_FR')
+ orig_text = "Copyright copyrighter"
+ translated_text = u"Copyrighter, tous droits réservés"
+ self.text_para.text = orig_text
self.registry('ir.translation').create(self.cr, self.uid, {
- 'name': '',
+ 'name': 'website',
'type': 'view',
'lang': 'fr_FR',
- 'src': 'Copyright copyrighter',
- 'value': u"Copyrighter, tous droits réservés",
+ 'src': orig_text,
+ 'value': translated_text,
})
- sarch, fields = View.postprocess_and_fields(
- self.cr, self.uid, None, self.arch, None, {'lang': 'fr_FR'})
+ sarch = View.translate_qweb(self.cr, self.uid, None, self.arch, 'fr_FR')
+
+ self.text_para.text = translated_text
self.assertEqual(
- sarch,
- ET.tostring(self.arch, encoding='utf-8')
- .replace('Copyright copyrighter',
- 'Copyrighter, tous droits réservés'))
- self.assertEqual(fields, {})
+ ET.tostring(sarch, encoding='utf-8'),
+ ET.tostring(self.arch, encoding='utf-8'))
class test_views(common.TransactionCase):
diff --git a/openerp/http.py b/openerp/http.py
index ca3330a7baf..94e0276ddf0 100644
--- a/openerp/http.py
+++ b/openerp/http.py
@@ -72,29 +72,6 @@ def redirect_with_hash(url, code=303):
return werkzeug.utils.redirect(url, code)
return "" % url
-def ensure_db(with_registry=False, redirect='/web/database/selector'):
- db = request.params.get('db')
- # if db not provided, use the session one
- if not db:
- db = request.session.db
-
- # if no database provided and no database in session, use monodb
- if not db:
- db = db_monodb(request.httprequest)
-
- # if no db can be found til here, send to the database selector
- # the database selector will redirect to database manager if needed
- if not db:
- werkzeug.exceptions.abort(werkzeug.utils.redirect(redirect, 303))
-
- # always switch the session to the computed db
- if db != request.session.db:
- request.session.logout()
- request.session.db = db
-
- if with_registry:
- request.disable_db = False
-
class WebRequest(object):
""" Parent class for all OpenERP Web request types, mostly deals with
initialization and setup of the request object (the dispatching itself has
@@ -227,7 +204,7 @@ class WebRequest(object):
kwargs.update(self.func_arguments)
# Backward for 7.0
- if getattr(self.func, '_first_arg_is_req', False):
+ if getattr(self.func.method, '_first_arg_is_req', False):
args = (request,) + args
# Correct exception handling and concurency retry
@service_model.check
@@ -271,6 +248,7 @@ def route(route=None, **kw):
authentication modules. There request code will not have any facilities to access the database nor have any
configuration indicating the current database nor the current user.
:param methods: A sequence of http methods this route applies to. If not specified, all methods are allowed.
+ :param cors: The Access-Control-Allow-Origin cors directive value.
"""
routing = kw.copy()
assert not 'type' in routing or routing['type'] in ("http", "json")
@@ -931,7 +909,7 @@ class Root(object):
self.load_addons()
_logger.info("Generating nondb routing")
- self.nodb_routing_map = routing_map(['', "web"], True)
+ self.nodb_routing_map = routing_map([''] + openerp.conf.server_wide_modules, True)
def __call__(self, environ, start_response):
""" Handle a WSGI request
@@ -979,8 +957,13 @@ class Root(object):
return explicit_session
def setup_db(self, httprequest):
- if not httprequest.session.db:
- # allow "admin" routes to works without being logged in when in monodb.
+ db = httprequest.session.db
+ # Check if session.db is legit
+ if db and db not in db_filter([db], httprequest=httprequest):
+ httprequest.session.logout()
+ db = None
+
+ if not db:
httprequest.session.db = db_monodb(httprequest)
def setup_lang(self, httprequest):
@@ -1003,8 +986,6 @@ class Root(object):
try:
result.process()
except(Exception), e:
- # In case of auth="none" we re-activate db getter for exception handling
- request.disable_db = False
if request.db:
result = request.registry['ir.http']._handle_exception(e)
else:
@@ -1026,6 +1007,16 @@ class Root(object):
if not explicit_session and hasattr(response, 'set_cookie'):
response.set_cookie('session_id', httprequest.session.sid, max_age=90 * 24 * 60 * 60)
+ # Support for Cross-Origin Resource Sharing
+ if request.func and 'cors' in request.func.routing:
+ response.headers.set('Access-Control-Allow-Origin', request.func.routing['cors'])
+ methods = 'GET, POST'
+ if request.func_request_type == 'json':
+ methods = 'POST'
+ elif request.func.routing.get('methods'):
+ methods = ', '.join(request.func.routing['methods'])
+ response.headers.set('Access-Control-Allow-Methods', methods)
+
return response
def dispatch(self, environ, start_response):
@@ -1079,8 +1070,11 @@ class Root(object):
return request.registry['ir.http'].routing_map()
def db_list(force=False, httprequest=None):
- httprequest = httprequest or request.httprequest
dbs = openerp.netsvc.dispatch_rpc("db", "list", [force])
+ return db_filter(dbs, httprequest=httprequest)
+
+def db_filter(dbs, httprequest=None):
+ httprequest = httprequest or request.httprequest
h = httprequest.environ['HTTP_HOST'].split(':')[0]
d = h.split('.')[0]
r = openerp.tools.config['dbfilter'].replace('%h', h).replace('%d', d)
@@ -1099,8 +1093,6 @@ def db_monodb(httprequest=None):
Returns ``None`` if the magic is not magic enough.
"""
httprequest = httprequest or request.httprequest
- db = None
- redirect = None
dbs = db_list(True, httprequest)
diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py
index 48c5c5adab5..4e157c51d20 100644
--- a/openerp/modules/loading.py
+++ b/openerp/modules/loading.py
@@ -70,6 +70,9 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
cr.commit()
else:
cr.rollback()
+ # avoid keeping stale xml_id, etc. in cache
+ openerp.modules.registry.RegistryManager.clear_caches(cr.dbname)
+
def _get_files_of_kind(kind):
if kind == 'demo':
diff --git a/openerp/modules/registry.py b/openerp/modules/registry.py
index 5929924b118..7dcf02020fd 100644
--- a/openerp/modules/registry.py
+++ b/openerp/modules/registry.py
@@ -68,8 +68,8 @@ class Registry(Mapping):
# must be reloaded.
# The `base_cache_signaling sequence` indicates all caches must be
# invalidated (i.e. cleared).
- self.base_registry_signaling_sequence = 1
- self.base_cache_signaling_sequence = 1
+ self.base_registry_signaling_sequence = None
+ self.base_cache_signaling_sequence = None
# Flag indicating if at least one model cache has been cleared.
# Useful only in a multi-process context.
@@ -159,7 +159,7 @@ class Registry(Mapping):
@classmethod
def setup_multi_process_signaling(cls, cr):
if not openerp.multi_process:
- return
+ return None, None
# Inter-process signaling:
# The `base_registry_signaling` sequence indicates the whole registry
@@ -172,6 +172,16 @@ class Registry(Mapping):
cr.execute("""SELECT nextval('base_registry_signaling')""")
cr.execute("""CREATE SEQUENCE base_cache_signaling INCREMENT BY 1 START WITH 1""")
cr.execute("""SELECT nextval('base_cache_signaling')""")
+
+ cr.execute("""
+ SELECT base_registry_signaling.last_value,
+ base_cache_signaling.last_value
+ FROM base_registry_signaling, base_cache_signaling""")
+ r, c = cr.fetchone()
+ _logger.debug("Multiprocess load registry signaling: [Registry: # %s] "\
+ "[Cache: # %s]",
+ r, c)
+ return r, c
@contextmanager
def cursor(self, auto_commit=True):
@@ -229,6 +239,10 @@ class RegistryManager(object):
cls.delete(db_name)
cls.registries[db_name] = registry
try:
+ with registry.cursor() as cr:
+ seq_registry, seq_cache = Registry.setup_multi_process_signaling(cr)
+ registry.base_registry_signaling_sequence = seq_registry
+ registry.base_cache_signaling_sequence = seq_cache
# This should be a method on Registry
openerp.modules.load_modules(registry.db, force_demo, status, update_module)
except Exception:
@@ -242,7 +256,6 @@ class RegistryManager(object):
cr = registry.db.cursor()
try:
- Registry.setup_multi_process_signaling(cr)
registry.do_parent_store(cr)
cr.commit()
finally:
@@ -304,16 +317,20 @@ class RegistryManager(object):
base_cache_signaling.last_value
FROM base_registry_signaling, base_cache_signaling""")
r, c = cr.fetchone()
+ _logger.debug("Multiprocess signaling check: [Registry - old# %s new# %s] "\
+ "[Cache - old# %s new# %s]",
+ registry.base_registry_signaling_sequence, r,
+ registry.base_cache_signaling_sequence, c)
# Check if the model registry must be reloaded (e.g. after the
# database has been updated by another process).
- if registry.base_registry_signaling_sequence > 1 and registry.base_registry_signaling_sequence != r:
+ if registry.base_registry_signaling_sequence is not None and registry.base_registry_signaling_sequence != r:
changed = True
_logger.info("Reloading the model registry after database signaling.")
registry = cls.new(db_name)
# Check if the model caches must be invalidated (e.g. after a write
# occured on another process). Don't clear right after a registry
# has been reload.
- elif registry.base_cache_signaling_sequence > 1 and registry.base_cache_signaling_sequence != c:
+ elif registry.base_cache_signaling_sequence is not None and registry.base_cache_signaling_sequence != c:
changed = True
_logger.info("Invalidating all model caches after database signaling.")
registry.clear_caches()
@@ -352,6 +369,7 @@ class RegistryManager(object):
@classmethod
def signal_registry_change(cls, db_name):
if openerp.multi_process and db_name in cls.registries:
+ _logger.info("Registry changed, signaling through the database")
registry = cls.get(db_name)
cr = registry.db.cursor()
r = 1
diff --git a/openerp/osv/expression.py b/openerp/osv/expression.py
index 08d93947698..648a8447292 100644
--- a/openerp/osv/expression.py
+++ b/openerp/osv/expression.py
@@ -1018,7 +1018,7 @@ class expression(object):
right += ' 23:59:59'
push(create_substitution_leaf(leaf, (left, operator, right), working_model))
- elif field.translate:
+ elif field.translate and right:
need_wildcard = operator in ('like', 'ilike', 'not like', 'not ilike')
sql_operator = {'=like': 'like', '=ilike': 'ilike'}.get(operator, operator)
if need_wildcard:
diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py
index f298e219ebd..e595852c830 100644
--- a/openerp/osv/orm.py
+++ b/openerp/osv/orm.py
@@ -515,7 +515,7 @@ class browse_record(object):
return self._id
def __str__(self):
- return "browse_record(%s, %d)" % (self._table_name, self._id)
+ return "browse_record(%s, %s)" % (self._table_name, self._id)
def __eq__(self, other):
if not isinstance(other, browse_record):
@@ -899,11 +899,6 @@ class BaseModel(object):
for c in new.keys():
if new[c].manual:
del new[c]
- # Duplicate float fields because they have a .digits
- # cache (which must be per-registry, not server-wide).
- for c in new.keys():
- if new[c]._type == 'float':
- new[c] = copy.copy(new[c])
if hasattr(new, 'update'):
new.update(cls.__dict__.get(s, {}))
elif s=='_constraints':
@@ -939,6 +934,13 @@ class BaseModel(object):
if not getattr(cls, '_original_module', None):
cls._original_module = cls._module
obj = object.__new__(cls)
+
+ if hasattr(obj, '_columns'):
+ # float fields are registry-dependent (digit attribute). Duplicate them to avoid issues.
+ for c, f in obj._columns.items():
+ if f._type == 'float':
+ obj._columns[c] = copy.copy(f)
+
obj.__init__(pool, cr)
return obj
@@ -1541,9 +1543,16 @@ class BaseModel(object):
error_msgs = []
for constraint in self._constraints:
fun, msg, fields = constraint
- # We don't pass around the context here: validation code
- # must always yield the same results.
- if not fun(self, cr, uid, ids):
+ try:
+ # We don't pass around the context here: validation code
+ # must always yield the same results.
+ valid = fun(self, cr, uid, ids)
+ extra_error = None
+ except Exception, e:
+ _logger.debug('Exception while validating constraint', exc_info=True)
+ valid = False
+ extra_error = tools.ustr(e)
+ if not valid:
# Check presence of __call__ directly instead of using
# callable() because it will be deprecated as of Python 3.0
if hasattr(msg, '__call__'):
@@ -1555,6 +1564,8 @@ class BaseModel(object):
translated_msg = tmp_msg
else:
translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, msg)
+ if extra_error:
+ translated_msg += "\n\n%s\n%s" % (_('Error details:'), extra_error)
error_msgs.append(
_("The field(s) `%s` failed against a constraint: %s") % (', '.join(fields), translated_msg)
)
@@ -1837,7 +1848,7 @@ class BaseModel(object):
# otherwise try to find the lowest priority matching ir.ui.view
view_id = View.default_view(cr, uid, self._name, view_type, context=context)
- # context for postproscessing might be overriden
+ # context for post-processing might be overriden
ctx = context
if view_id:
# read the view with inherited views applied
@@ -1862,7 +1873,7 @@ class BaseModel(object):
raise except_orm(_('Invalid Architecture!'), _("No default view of type '%s' could be found !") % view_type)
# Apply post processing, groups and modifiers etc...
- xarch, xfields = View.postprocess_and_fields( cr, uid, self._name, etree.fromstring(result['arch']), view_id, context=ctx)
+ xarch, xfields = View.postprocess_and_fields(cr, uid, self._name, etree.fromstring(result['arch']), view_id, context=ctx)
result['arch'] = xarch
result['fields'] = xfields
@@ -1872,8 +1883,7 @@ class BaseModel(object):
def clean(x):
x = x[2]
for key in toclean:
- if key in x:
- del x[key]
+ x.pop(key, None)
return x
ir_values_obj = self.pool.get('ir.values')
resprint = ir_values_obj.get(cr, uid, 'action', 'client_print_multi', [(self._name, False)], False, context)
diff --git a/openerp/report/report_sxw.py b/openerp/report/report_sxw.py
index 03bc0ffa03f..1c31c5ec3ae 100644
--- a/openerp/report/report_sxw.py
+++ b/openerp/report/report_sxw.py
@@ -255,9 +255,7 @@ class rml_parse(object):
d = DEFAULT_DIGITS = 2
if dp:
decimal_precision_obj = self.pool['decimal.precision']
- ids = decimal_precision_obj.search(self.cr, self.uid, [('name', '=', dp)])
- if ids:
- d = decimal_precision_obj.browse(self.cr, self.uid, ids)[0].digits
+ d = decimal_precision_obj.precision_get(self.cr, self.uid, dp)
elif obj and f:
res_digits = getattr(obj._columns[f], 'digits', lambda x: ((16, DEFAULT_DIGITS)))
if isinstance(res_digits, tuple):
diff --git a/openerp/service/db.py b/openerp/service/db.py
index 1381b5b1e04..fa1bc1336fc 100644
--- a/openerp/service/db.py
+++ b/openerp/service/db.py
@@ -145,7 +145,7 @@ def exp_get_progress(id):
raise Exception, e
def exp_drop(db_name):
- if not exp_db_exist(db_name):
+ if db_name not in exp_list(True):
return False
openerp.modules.registry.RegistryManager.delete(db_name)
openerp.sql_db.close_db(db_name)
diff --git a/openerp/tests/__init__.py b/openerp/tests/__init__.py
index aa38f7831e9..a74122424de 100644
--- a/openerp/tests/__init__.py
+++ b/openerp/tests/__init__.py
@@ -19,7 +19,6 @@ import test_misc
import test_orm
import test_osv
import test_translate
-import test_uninstall
import test_view_validation
import test_qweb
import test_func
diff --git a/openerp/tests/test_uninstall.py b/openerp/tests/test_uninstall.py
deleted file mode 100644
index 6f6a623f6a9..00000000000
--- a/openerp/tests/test_uninstall.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# -*- coding: utf-8 -*-
-# This assumes an existing but uninitialized database.
-import unittest2
-
-import openerp
-from openerp import SUPERUSER_ID
-import common
-
-DB = common.DB
-ADMIN_USER_ID = common.ADMIN_USER_ID
-
-def registry(model):
- return openerp.modules.registry.RegistryManager.get(DB)[model]
-
-def cursor():
- return openerp.modules.registry.RegistryManager.get(DB).db.cursor()
-
-def model_exists(model_name):
- registry = openerp.modules.registry.RegistryManager.get(DB)
- return model_name in registry
-
-def reload_registry():
- openerp.modules.registry.RegistryManager.new(
- DB, update_module=True)
-
-def search_registry(model_name, domain):
- cr = cursor()
- model = registry(model_name)
- record_ids = model.search(cr, SUPERUSER_ID, domain, {})
- cr.close()
- return record_ids
-
-def install_module(module_name):
- ir_module_module = registry('ir.module.module')
- cr = cursor()
- module_ids = ir_module_module.search(cr, SUPERUSER_ID,
- [('name', '=', module_name)], {})
- assert len(module_ids) == 1
- ir_module_module.button_install(cr, SUPERUSER_ID, module_ids, {})
- cr.commit()
- cr.close()
- reload_registry()
-
-def uninstall_module(module_name):
- ir_module_module = registry('ir.module.module')
- cr = cursor()
- module_ids = ir_module_module.search(cr, SUPERUSER_ID,
- [('name', '=', module_name)], {})
- assert len(module_ids) == 1
- ir_module_module.button_uninstall(cr, SUPERUSER_ID, module_ids, {})
- cr.commit()
- cr.close()
- reload_registry()
-
-class test_uninstall(unittest2.TestCase):
- """
- Test the install/uninstall of a test module. The module is available in
- `openerp.tests` which should be present in the addons-path.
- """
-
- def test_01_install(self):
- """ Check a few things showing the module is installed. """
- install_module('test_uninstall')
- assert model_exists('test_uninstall.model')
-
- assert search_registry('ir.model.data',
- [('module', '=', 'test_uninstall')])
-
- assert search_registry('ir.model.fields',
- [('model', '=', 'test_uninstall.model')])
-
- def test_02_uninstall(self):
- """ Check a few things showing the module is uninstalled. """
- uninstall_module('test_uninstall')
- assert not model_exists('test_uninstall.model')
-
- assert not search_registry('ir.model.data',
- [('module', '=', 'test_uninstall')])
-
- assert not search_registry('ir.model.fields',
- [('model', '=', 'test_uninstall.model')])
-
-
-
-if __name__ == '__main__':
- unittest2.main()
-
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: