[MERGE] Sync with trunk-website-al

bzr revid: rim@openerp.com-20140131075451-2g14hxx3h5ycf5pe
This commit is contained in:
Richard Mathot (OpenERP)
2014-01-31 08:54:51 +01:00
17 changed files with 212 additions and 254 deletions
+7 -5
View File
@@ -129,19 +129,21 @@ Administrator</field>
<field name="path">/dev/null</field>
<field name="mode">all</field>
</record>
<record id="public_partner" model="res.partner">
<field name="name">Public user</field>
<field name="active" eval="False"/>
</record>
<record id="public_user" model="res.users">
<field name="name">Public user</field>
<field name="login">public</field>
<field name="password"></field>
<!-- Avoid auto-including this demo user in any default group -->
<field name="groups_id" eval="[(5,)]"/>
<field name="groups_id" eval="[(6,0,[ref('base.group_public')])]"/>
<field name="image" type="base64" file="base/static/img/public_user-image.png"/>
<field name="partner_id" ref="public_partner"/>
<field name="active" eval="False"/>
<!-- TODO make res.partner active false too -->
</record>
<record id="base.group_public" model="res.groups">
<field name="users" eval="[(4, ref('base.public_user'))]"/>
</record>
</data>
+1 -2
View File
@@ -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
+24 -16
View File
@@ -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):
@@ -14,7 +14,7 @@
domain="[('comments', 'like', 'openerp-web')]"/>
<field name="name" operator="="/>
<field name="lang"/>
<field name="source"/>
<field name="src"/>
<field name="value"/>
</search>
</field>
+41 -35
View File
@@ -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 <data> 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)
+26 -17
View File
@@ -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):
+14
View File
@@ -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" <raoul@chirurgiens-dentistes.fr>', False),
('B Raoul chirurgiens-dentistes.fr', True),
("C Raoul O'hara <!@historicalsociety.museum>", 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
+21 -32
View File
@@ -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': '<form string=""/>',
'inherit_id': False
'arch': '<template name="foo"/>',
'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': '<form string=""/>',
'type': 'form',
'arch': '<template name="foo"/>',
'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):
+24 -32
View File
@@ -72,29 +72,6 @@ def redirect_with_hash(url, code=303):
return werkzeug.utils.redirect(url, code)
return "<html><head><script>window.location = '%s' + location.hash;</script></head></html>" % 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)
+3
View File
@@ -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':
+24 -6
View File
@@ -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
+1 -1
View File
@@ -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:
+23 -13
View File
@@ -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)
+1 -3
View File
@@ -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):
+1 -1
View File
@@ -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)
-1
View File
@@ -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
-89
View File
@@ -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: