[MERGE] forward port of branch saas-1 up to revid 4899 chs@openerp.com-20130821094758-1ae0d1ml5obufzxv
bzr revid: odo@openerp.com-20130820160346-qcdy4kavgm5cb9t6 bzr revid: chs@openerp.com-20130821103139-zqqbrt8dq6iixrny
This commit is contained in:
commit
208b697239
|
@ -31,5 +31,15 @@ Changelog
|
||||||
``openerp.exceptions.RedirectWarning``.
|
``openerp.exceptions.RedirectWarning``.
|
||||||
- Give a pair of new methods to ``res.config.settings`` and a helper to make
|
- Give a pair of new methods to ``res.config.settings`` and a helper to make
|
||||||
them easier to use: ``get_config_warning()``.
|
them easier to use: ``get_config_warning()``.
|
||||||
- Path to webkit report files (field ``report_file``) must be writen with the
|
- Path to webkit report files (field ``report_file``) must be written the
|
||||||
Unix way (with ``/`` and not ``\``)
|
Unix way (with ``/`` and not ``\``)
|
||||||
|
|
||||||
|
|
||||||
|
`7.0`
|
||||||
|
-----
|
||||||
|
|
||||||
|
- Modules may now include an ``i18n_extra`` directory that will be treated like the
|
||||||
|
default ``i18n`` directory. This is typically useful for manual translation files
|
||||||
|
that are not managed by Launchpad's translation system. An example is l10n modules
|
||||||
|
that depend on ``l10n_multilang``.
|
||||||
|
|
||||||
|
|
|
@ -195,7 +195,8 @@ class ir_model(osv.osv):
|
||||||
ctx = dict(context,
|
ctx = dict(context,
|
||||||
field_name=vals['name'],
|
field_name=vals['name'],
|
||||||
field_state='manual',
|
field_state='manual',
|
||||||
select=vals.get('select_level', '0'))
|
select=vals.get('select_level', '0'),
|
||||||
|
update_custom_fields=True)
|
||||||
self.pool[vals['model']]._auto_init(cr, ctx)
|
self.pool[vals['model']]._auto_init(cr, ctx)
|
||||||
openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
|
openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
|
||||||
return res
|
return res
|
||||||
|
|
|
@ -445,6 +445,13 @@ class ir_translation(osv.osv):
|
||||||
tools.trans_load(cr, base_trans_file, lang, verbose=False, module_name=module_name, context=context)
|
tools.trans_load(cr, base_trans_file, lang, verbose=False, module_name=module_name, context=context)
|
||||||
context['overwrite'] = True # make sure the requested translation will override the base terms later
|
context['overwrite'] = True # make sure the requested translation will override the base terms later
|
||||||
|
|
||||||
|
# i18n_extra folder is for additional translations handle manually (eg: for l10n_be)
|
||||||
|
base_trans_extra_file = openerp.modules.get_module_resource(module_name, 'i18n_extra', base_lang_code + '.po')
|
||||||
|
if base_trans_extra_file:
|
||||||
|
_logger.info('module %s: loading extra base translation file %s for language %s', module_name, base_lang_code, lang)
|
||||||
|
tools.trans_load(cr, base_trans_extra_file, lang, verbose=False, module_name=module_name, context=context)
|
||||||
|
context['overwrite'] = True # make sure the requested translation will override the base terms later
|
||||||
|
|
||||||
# Step 2: then load the main translation file, possibly overriding the terms coming from the base language
|
# Step 2: then load the main translation file, possibly overriding the terms coming from the base language
|
||||||
trans_file = openerp.modules.get_module_resource(module_name, 'i18n', lang_code + '.po')
|
trans_file = openerp.modules.get_module_resource(module_name, 'i18n', lang_code + '.po')
|
||||||
if trans_file:
|
if trans_file:
|
||||||
|
@ -452,6 +459,11 @@ class ir_translation(osv.osv):
|
||||||
tools.trans_load(cr, trans_file, lang, verbose=False, module_name=module_name, context=context)
|
tools.trans_load(cr, trans_file, lang, verbose=False, module_name=module_name, context=context)
|
||||||
elif lang_code != 'en_US':
|
elif lang_code != 'en_US':
|
||||||
_logger.warning('module %s: no translation for language %s', module_name, lang_code)
|
_logger.warning('module %s: no translation for language %s', module_name, lang_code)
|
||||||
|
|
||||||
|
trans_extra_file = openerp.modules.get_module_resource(module_name, 'i18n_extra', lang_code + '.po')
|
||||||
|
if trans_extra_file:
|
||||||
|
_logger.info('module %s: loading extra translation file (%s) for language %s', module_name, lang_code, lang)
|
||||||
|
tools.trans_load(cr, trans_extra_file, lang, verbose=False, module_name=module_name, context=context)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -187,7 +187,7 @@ class view(osv.osv):
|
||||||
if self.pool._init:
|
if self.pool._init:
|
||||||
# Module init currently in progress, only consider views from modules whose code was already loaded
|
# Module init currently in progress, only consider views from modules whose code was already loaded
|
||||||
query = """SELECT v.id FROM ir_ui_view v LEFT JOIN ir_model_data md ON (md.model = 'ir.ui.view' AND md.res_id = v.id)
|
query = """SELECT v.id FROM ir_ui_view v LEFT JOIN ir_model_data md ON (md.model = 'ir.ui.view' AND md.res_id = v.id)
|
||||||
WHERE v.inherit_id=%s AND v.model=%s AND md.module in %s
|
WHERE v.inherit_id=%s AND v.model=%s AND (md.module IS NULL or md.module in %s)
|
||||||
ORDER BY priority"""
|
ORDER BY priority"""
|
||||||
query_params = (view_id, model, tuple(self.pool._init_modules))
|
query_params = (view_id, model, tuple(self.pool._init_modules))
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -30,11 +30,13 @@ from openerp.tools.translate import _
|
||||||
CURRENCY_DISPLAY_PATTERN = re.compile(r'(\w+)\s*(?:\((.*)\))?')
|
CURRENCY_DISPLAY_PATTERN = re.compile(r'(\w+)\s*(?:\((.*)\))?')
|
||||||
|
|
||||||
class res_currency(osv.osv):
|
class res_currency(osv.osv):
|
||||||
|
|
||||||
def _current_rate(self, cr, uid, ids, name, arg, context=None):
|
def _current_rate(self, cr, uid, ids, name, arg, context=None):
|
||||||
return self._get_current_rate(cr, uid, ids, name, arg, context=context)
|
return self._get_current_rate(cr, uid, ids, context=context)
|
||||||
|
|
||||||
def _get_current_rate(self, cr, uid, ids, name, arg, context=None):
|
def _current_rate_silent(self, cr, uid, ids, name, arg, context=None):
|
||||||
|
return self._get_current_rate(cr, uid, ids, raise_on_no_rate=False, context=context)
|
||||||
|
|
||||||
|
def _get_current_rate(self, cr, uid, ids, raise_on_no_rate=True, context=None):
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
res = {}
|
res = {}
|
||||||
|
@ -52,9 +54,12 @@ class res_currency(osv.osv):
|
||||||
if cr.rowcount:
|
if cr.rowcount:
|
||||||
id, rate = cr.fetchall()[0]
|
id, rate = cr.fetchall()[0]
|
||||||
res[id] = rate
|
res[id] = rate
|
||||||
|
elif not raise_on_no_rate:
|
||||||
|
res[id] = 0
|
||||||
else:
|
else:
|
||||||
raise osv.except_osv(_('Error!'),_("No currency rate associated for currency %d for the given period" % (id)))
|
raise osv.except_osv(_('Error!'),_("No currency rate associated for currency %d for the given period" % (id)))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
_name = "res.currency"
|
_name = "res.currency"
|
||||||
_description = "Currency"
|
_description = "Currency"
|
||||||
_columns = {
|
_columns = {
|
||||||
|
@ -63,6 +68,10 @@ class res_currency(osv.osv):
|
||||||
'symbol': fields.char('Symbol', size=4, help="Currency sign, to be used when printing amounts."),
|
'symbol': fields.char('Symbol', size=4, help="Currency sign, to be used when printing amounts."),
|
||||||
'rate': fields.function(_current_rate, string='Current Rate', digits=(12,6),
|
'rate': fields.function(_current_rate, string='Current Rate', digits=(12,6),
|
||||||
help='The rate of the currency to the currency of rate 1.'),
|
help='The rate of the currency to the currency of rate 1.'),
|
||||||
|
|
||||||
|
# Do not use for computation ! Same as rate field with silent failing
|
||||||
|
'rate_silent': fields.function(_current_rate_silent, string='Current Rate', digits=(12,6),
|
||||||
|
help='The rate of the currency to the currency of rate 1 (0 if no rate defined).'),
|
||||||
'rate_ids': fields.one2many('res.currency.rate', 'currency_id', 'Rates'),
|
'rate_ids': fields.one2many('res.currency.rate', 'currency_id', 'Rates'),
|
||||||
'accuracy': fields.integer('Computational Accuracy'),
|
'accuracy': fields.integer('Computational Accuracy'),
|
||||||
'rounding': fields.float('Rounding Factor', digits=(12,6)),
|
'rounding': fields.float('Rounding Factor', digits=(12,6)),
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<field name="company_id" groups="base.group_multi_company"/>
|
<field name="company_id" groups="base.group_multi_company"/>
|
||||||
<field name="rate_ids" invisible="1"/>
|
<field name="rate_ids" invisible="1"/>
|
||||||
<field name="date"/>
|
<field name="date"/>
|
||||||
<field name="rate"/>
|
<field name="rate_silent"/>
|
||||||
<field name="rounding"/>
|
<field name="rounding"/>
|
||||||
<field name="accuracy"/>
|
<field name="accuracy"/>
|
||||||
<field name="position"/>
|
<field name="position"/>
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
<form string="Currency" version="7.0">
|
<form string="Currency" version="7.0">
|
||||||
<group col="4">
|
<group col="4">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="rate"/>
|
<field name="rate_silent"/>
|
||||||
<field name="company_id" groups="base.group_multi_company"/>
|
<field name="company_id" groups="base.group_multi_company"/>
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
|
|
|
@ -208,6 +208,8 @@ class res_partner(osv.osv, format_address):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _display_name_compute(self, cr, uid, ids, name, args, context=None):
|
def _display_name_compute(self, cr, uid, ids, name, args, context=None):
|
||||||
|
context = dict(context or {})
|
||||||
|
context.pop('show_address', None)
|
||||||
return dict(self.name_get(cr, uid, ids, context=context))
|
return dict(self.name_get(cr, uid, ids, context=context))
|
||||||
|
|
||||||
# indirections to avoid passing a copy of the overridable method when declaring the function field
|
# indirections to avoid passing a copy of the overridable method when declaring the function field
|
||||||
|
|
|
@ -398,7 +398,9 @@ def is_leaf(element, internal=False):
|
||||||
INTERNAL_OPS += ('inselect',)
|
INTERNAL_OPS += ('inselect',)
|
||||||
return (isinstance(element, tuple) or isinstance(element, list)) \
|
return (isinstance(element, tuple) or isinstance(element, list)) \
|
||||||
and len(element) == 3 \
|
and len(element) == 3 \
|
||||||
and element[1] in INTERNAL_OPS
|
and element[1] in INTERNAL_OPS \
|
||||||
|
and ((isinstance(element[0], basestring) and element[0])
|
||||||
|
or element in (TRUE_LEAF, FALSE_LEAF))
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
|
@ -383,16 +383,18 @@ class browse_record(object):
|
||||||
raise KeyError(error_msg)
|
raise KeyError(error_msg)
|
||||||
|
|
||||||
# if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
|
# if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
|
||||||
if col._prefetch:
|
if col._prefetch and not col.groups:
|
||||||
# gen the list of "local" (ie not inherited) fields which are classic or many2one
|
# gen the list of "local" (ie not inherited) fields which are classic or many2one
|
||||||
fields_to_fetch = filter(lambda x: x[1]._classic_write and x[1]._prefetch, self._table._columns.items())
|
field_filter = lambda x: x[1]._classic_write and x[1]._prefetch and not x[1].groups
|
||||||
|
fields_to_fetch = filter(field_filter, self._table._columns.items())
|
||||||
# gen the list of inherited fields
|
# gen the list of inherited fields
|
||||||
inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
|
inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
|
||||||
# complete the field list with the inherited fields which are classic or many2one
|
# complete the field list with the inherited fields which are classic or many2one
|
||||||
fields_to_fetch += filter(lambda x: x[1]._classic_write and x[1]._prefetch, inherits)
|
fields_to_fetch += filter(field_filter, inherits)
|
||||||
# otherwise we fetch only that field
|
# otherwise we fetch only that field
|
||||||
else:
|
else:
|
||||||
fields_to_fetch = [(name, col)]
|
fields_to_fetch = [(name, col)]
|
||||||
|
|
||||||
ids = filter(lambda id: name not in self._data[id], self._data.keys())
|
ids = filter(lambda id: name not in self._data[id], self._data.keys())
|
||||||
# read the results
|
# read the results
|
||||||
field_names = map(lambda x: x[0], fields_to_fetch)
|
field_names = map(lambda x: x[0], fields_to_fetch)
|
||||||
|
|
|
@ -109,7 +109,7 @@ class _date_format(str, _format):
|
||||||
if self.val:
|
if self.val:
|
||||||
if getattr(self,'name', None):
|
if getattr(self,'name', None):
|
||||||
date = datetime.strptime(self.name[:get_date_length()], DEFAULT_SERVER_DATE_FORMAT)
|
date = datetime.strptime(self.name[:get_date_length()], DEFAULT_SERVER_DATE_FORMAT)
|
||||||
return date.strftime(str(self.lang_obj.date_format))
|
return date.strftime(self.lang_obj.date_format.encode('utf-8'))
|
||||||
return self.val
|
return self.val
|
||||||
|
|
||||||
class _dttime_format(str, _format):
|
class _dttime_format(str, _format):
|
||||||
|
@ -120,8 +120,8 @@ class _dttime_format(str, _format):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.val and getattr(self,'name', None):
|
if self.val and getattr(self,'name', None):
|
||||||
return datetime.strptime(self.name, DEFAULT_SERVER_DATETIME_FORMAT)\
|
return datetime.strptime(self.name, DEFAULT_SERVER_DATETIME_FORMAT)\
|
||||||
.strftime("%s %s"%(str(self.lang_obj.date_format),
|
.strftime("%s %s"%((self.lang_obj.date_format).encode('utf-8'),
|
||||||
str(self.lang_obj.time_format)))
|
(self.lang_obj.time_format).encode('utf-8')))
|
||||||
return self.val
|
return self.val
|
||||||
|
|
||||||
|
|
||||||
|
@ -313,7 +313,7 @@ class rml_parse(object):
|
||||||
date = datetime_field.context_timestamp(self.cr, self.uid,
|
date = datetime_field.context_timestamp(self.cr, self.uid,
|
||||||
timestamp=date,
|
timestamp=date,
|
||||||
context=self.localcontext)
|
context=self.localcontext)
|
||||||
return date.strftime(date_format)
|
return date.strftime(date_format.encode('utf-8'))
|
||||||
|
|
||||||
res = self.lang_dict['lang_obj'].format('%.' + str(digits) + 'f', value, grouping=grouping, monetary=monetary)
|
res = self.lang_dict['lang_obj'].format('%.' + str(digits) + 'f', value, grouping=grouping, monetary=monetary)
|
||||||
if currency_obj:
|
if currency_obj:
|
||||||
|
|
|
@ -9,6 +9,7 @@ import common
|
||||||
# test group that demo user should not have
|
# test group that demo user should not have
|
||||||
GROUP_TECHNICAL_FEATURES = 'base.group_no_one'
|
GROUP_TECHNICAL_FEATURES = 'base.group_no_one'
|
||||||
|
|
||||||
|
|
||||||
class TestACL(common.TransactionCase):
|
class TestACL(common.TransactionCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -22,25 +23,25 @@ class TestACL(common.TransactionCase):
|
||||||
|
|
||||||
def test_field_visibility_restriction(self):
|
def test_field_visibility_restriction(self):
|
||||||
"""Check that model-level ``groups`` parameter effectively restricts access to that
|
"""Check that model-level ``groups`` parameter effectively restricts access to that
|
||||||
field for users who do not belong to one of the explicitly allowed groups"""
|
field for users who do not belong to one of the explicitly allowed groups"""
|
||||||
# Verify the test environment first
|
# Verify the test environment first
|
||||||
original_fields = self.res_currency.fields_get(self.cr, self.demo_uid, [])
|
original_fields = self.res_currency.fields_get(self.cr, self.demo_uid, [])
|
||||||
form_view = self.res_currency.fields_view_get(self.cr, self.demo_uid, False, 'form')
|
form_view = self.res_currency.fields_view_get(self.cr, self.demo_uid, False, 'form')
|
||||||
view_arch = etree.fromstring(form_view.get('arch'))
|
view_arch = etree.fromstring(form_view.get('arch'))
|
||||||
has_tech_feat = self.res_users.has_group(self.cr, self.demo_uid, GROUP_TECHNICAL_FEATURES)
|
has_tech_feat = self.res_users.has_group(self.cr, self.demo_uid, GROUP_TECHNICAL_FEATURES)
|
||||||
self.assertFalse(has_tech_feat, "`demo` user should not belong to the restricted group before the test")
|
self.assertFalse(has_tech_feat, "`demo` user should not belong to the restricted group before the test")
|
||||||
self.assertTrue('rate' in original_fields, "'rate' field must be properly visible before the test")
|
self.assertTrue('accuracy' in original_fields, "'accuracy' field must be properly visible before the test")
|
||||||
self.assertNotEquals(view_arch.xpath("//field[@name='rate']"), [],
|
self.assertNotEquals(view_arch.xpath("//field[@name='accuracy']"), [],
|
||||||
"Field 'rate' must be found in view definition before the test")
|
"Field 'accuracy' must be found in view definition before the test")
|
||||||
|
|
||||||
# Restrict access to the field and check it's gone
|
# Restrict access to the field and check it's gone
|
||||||
self.res_currency._columns['rate'].groups = GROUP_TECHNICAL_FEATURES
|
self.res_currency._columns['accuracy'].groups = GROUP_TECHNICAL_FEATURES
|
||||||
fields = self.res_currency.fields_get(self.cr, self.demo_uid, [])
|
fields = self.res_currency.fields_get(self.cr, self.demo_uid, [])
|
||||||
form_view = self.res_currency.fields_view_get(self.cr, self.demo_uid, False, 'form')
|
form_view = self.res_currency.fields_view_get(self.cr, self.demo_uid, False, 'form')
|
||||||
view_arch = etree.fromstring(form_view.get('arch'))
|
view_arch = etree.fromstring(form_view.get('arch'))
|
||||||
self.assertFalse('rate' in fields, "'rate' field should be gone")
|
self.assertFalse('accuracy' in fields, "'accuracy' field should be gone")
|
||||||
self.assertEquals(view_arch.xpath("//field[@name='rate']"), [],
|
self.assertEquals(view_arch.xpath("//field[@name='accuracy']"), [],
|
||||||
"Field 'rate' must not be found in view definition")
|
"Field 'accuracy' must not be found in view definition")
|
||||||
|
|
||||||
# Make demo user a member of the restricted group and check that the field is back
|
# Make demo user a member of the restricted group and check that the field is back
|
||||||
self.tech_group.write({'users': [(4, self.demo_uid)]})
|
self.tech_group.write({'users': [(4, self.demo_uid)]})
|
||||||
|
@ -50,13 +51,13 @@ class TestACL(common.TransactionCase):
|
||||||
view_arch = etree.fromstring(form_view.get('arch'))
|
view_arch = etree.fromstring(form_view.get('arch'))
|
||||||
#import pprint; pprint.pprint(fields); pprint.pprint(form_view)
|
#import pprint; pprint.pprint(fields); pprint.pprint(form_view)
|
||||||
self.assertTrue(has_tech_feat, "`demo` user should now belong to the restricted group")
|
self.assertTrue(has_tech_feat, "`demo` user should now belong to the restricted group")
|
||||||
self.assertTrue('rate' in fields, "'rate' field must be properly visible again")
|
self.assertTrue('accuracy' in fields, "'accuracy' field must be properly visible again")
|
||||||
self.assertNotEquals(view_arch.xpath("//field[@name='rate']"), [],
|
self.assertNotEquals(view_arch.xpath("//field[@name='accuracy']"), [],
|
||||||
"Field 'rate' must be found in view definition again")
|
"Field 'accuracy' must be found in view definition again")
|
||||||
|
|
||||||
#cleanup
|
#cleanup
|
||||||
self.tech_group.write({'users': [(3, self.demo_uid)]})
|
self.tech_group.write({'users': [(3, self.demo_uid)]})
|
||||||
self.res_currency._columns['rate'].groups = False
|
self.res_currency._columns['accuracy'].groups = False
|
||||||
|
|
||||||
@mute_logger('openerp.osv.orm')
|
@mute_logger('openerp.osv.orm')
|
||||||
def test_field_crud_restriction(self):
|
def test_field_crud_restriction(self):
|
||||||
|
@ -65,7 +66,7 @@ class TestACL(common.TransactionCase):
|
||||||
has_tech_feat = self.res_users.has_group(self.cr, self.demo_uid, GROUP_TECHNICAL_FEATURES)
|
has_tech_feat = self.res_users.has_group(self.cr, self.demo_uid, GROUP_TECHNICAL_FEATURES)
|
||||||
self.assertFalse(has_tech_feat, "`demo` user should not belong to the restricted group")
|
self.assertFalse(has_tech_feat, "`demo` user should not belong to the restricted group")
|
||||||
self.assert_(self.res_partner.read(self.cr, self.demo_uid, [1], ['bank_ids']))
|
self.assert_(self.res_partner.read(self.cr, self.demo_uid, [1], ['bank_ids']))
|
||||||
self.assert_(self.res_partner.write(self.cr, self.demo_uid, [1], {'bank_ids': []}))
|
self.assert_(self.res_partner.write(self.cr, self.demo_uid, [1], {'bank_ids': []}))
|
||||||
|
|
||||||
# Now restrict access to the field and check it's forbidden
|
# Now restrict access to the field and check it's forbidden
|
||||||
self.res_partner._columns['bank_ids'].groups = GROUP_TECHNICAL_FEATURES
|
self.res_partner._columns['bank_ids'].groups = GROUP_TECHNICAL_FEATURES
|
||||||
|
@ -79,12 +80,30 @@ class TestACL(common.TransactionCase):
|
||||||
has_tech_feat = self.res_users.has_group(self.cr, self.demo_uid, GROUP_TECHNICAL_FEATURES)
|
has_tech_feat = self.res_users.has_group(self.cr, self.demo_uid, GROUP_TECHNICAL_FEATURES)
|
||||||
self.assertTrue(has_tech_feat, "`demo` user should now belong to the restricted group")
|
self.assertTrue(has_tech_feat, "`demo` user should now belong to the restricted group")
|
||||||
self.assert_(self.res_partner.read(self.cr, self.demo_uid, [1], ['bank_ids']))
|
self.assert_(self.res_partner.read(self.cr, self.demo_uid, [1], ['bank_ids']))
|
||||||
self.assert_(self.res_partner.write(self.cr, self.demo_uid, [1], {'bank_ids': []}))
|
self.assert_(self.res_partner.write(self.cr, self.demo_uid, [1], {'bank_ids': []}))
|
||||||
|
|
||||||
#cleanup
|
#cleanup
|
||||||
self.tech_group.write({'users': [(3, self.demo_uid)]})
|
self.tech_group.write({'users': [(3, self.demo_uid)]})
|
||||||
self.res_partner._columns['bank_ids'].groups = False
|
self.res_partner._columns['bank_ids'].groups = False
|
||||||
|
|
||||||
|
def test_fields_browse_restriction(self):
|
||||||
|
"""Test access to records having restricted fields"""
|
||||||
|
self.res_partner._columns['email'].groups = GROUP_TECHNICAL_FEATURES
|
||||||
|
try:
|
||||||
|
P = self.res_partner
|
||||||
|
pid = P.search(self.cr, self.demo_uid, [], limit=1)[0]
|
||||||
|
part = P.browse(self.cr, self.demo_uid, pid)
|
||||||
|
# accessing fields must no raise exceptions...
|
||||||
|
part.name
|
||||||
|
# ... except they are restricted
|
||||||
|
with self.assertRaises(openerp.osv.orm.except_orm) as cm:
|
||||||
|
part.email
|
||||||
|
|
||||||
|
self.assertEqual(cm.exception.args[0], 'Access Denied')
|
||||||
|
finally:
|
||||||
|
self.res_partner._columns['email'].groups = False
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest2.main()
|
unittest2.main()
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
# OpenERP, Open Source Business Applications
|
# OpenERP, Open Source Business Applications
|
||||||
# Copyright (C) 2012 OpenERP S.A. (<http://openerp.com>).
|
# Copyright (C) 2012-2013 OpenERP S.A. (<http://openerp.com>).
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -44,29 +44,53 @@ tags_to_kill = ["script", "head", "meta", "title", "link", "style", "frame", "if
|
||||||
tags_to_remove = ['html', 'body', 'font']
|
tags_to_remove = ['html', 'body', 'font']
|
||||||
|
|
||||||
|
|
||||||
def html_sanitize(src):
|
def html_sanitize(src, silent=True):
|
||||||
if not src:
|
if not src:
|
||||||
return src
|
return src
|
||||||
src = ustr(src, errors='replace')
|
src = ustr(src, errors='replace')
|
||||||
|
|
||||||
|
logger = _logger.getChild('html_sanitize')
|
||||||
|
|
||||||
# html encode email tags
|
# html encode email tags
|
||||||
part = re.compile(r"(<(([^a<>]|a[^<>\s])[^<>]*)@[^<>]+>)", re.IGNORECASE | re.DOTALL)
|
part = re.compile(r"(<(([^a<>]|a[^<>\s])[^<>]*)@[^<>]+>)", re.IGNORECASE | re.DOTALL)
|
||||||
src = part.sub(lambda m: cgi.escape(m.group(1)), src)
|
src = part.sub(lambda m: cgi.escape(m.group(1)), src)
|
||||||
|
|
||||||
# some corner cases make the parser crash (such as <SCRIPT/XSS SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT> in test_mail)
|
kwargs = {
|
||||||
|
'page_structure': True,
|
||||||
|
'style': False, # do not remove style attributes
|
||||||
|
'forms': True, # remove form tags
|
||||||
|
}
|
||||||
|
if etree.LXML_VERSION >= (2, 3, 1):
|
||||||
|
# kill_tags attribute has been added in version 2.3.1
|
||||||
|
kwargs.update({
|
||||||
|
'kill_tags': tags_to_kill,
|
||||||
|
'remove_tags': tags_to_remove,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
kwargs['remove_tags'] = tags_to_kill + tags_to_remove
|
||||||
|
|
||||||
|
if etree.LXML_VERSION >= (3, 1, 0):
|
||||||
|
kwargs.update({
|
||||||
|
'safe_attrs_only': True,
|
||||||
|
'safe_attrs': clean.defs.safe_attrs | set(['style']),
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
# lxml < 3.1.0 does not allow to specify safe_attrs. We keep all attributes in order to keep "style"
|
||||||
|
kwargs['safe_attrs_only'] = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cleaner = clean.Cleaner(page_structure=True, style=False, safe_attrs_only=False, forms=False, kill_tags=tags_to_kill, remove_tags=tags_to_remove)
|
# some corner cases make the parser crash (such as <SCRIPT/XSS SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT> in test_mail)
|
||||||
|
cleaner = clean.Cleaner(**kwargs)
|
||||||
cleaned = cleaner.clean_html(src)
|
cleaned = cleaner.clean_html(src)
|
||||||
except TypeError, e:
|
except etree.ParserError:
|
||||||
# lxml.clean version < 2.3.1 does not have a kill_tags attribute
|
if not silent:
|
||||||
# to remove in 2014
|
raise
|
||||||
cleaner = clean.Cleaner(page_structure=True, style=False, safe_attrs_only=False, forms=False, remove_tags=tags_to_kill + tags_to_remove)
|
logger.warning('ParserError obtained when sanitizing %r', src, exc_info=True)
|
||||||
cleaned = cleaner.clean_html(src)
|
|
||||||
except etree.ParserError, e:
|
|
||||||
_logger.warning('html_sanitize: ParserError "%s" obtained when sanitizing "%s"' % (e, src))
|
|
||||||
cleaned = '<p>ParserError when sanitizing</p>'
|
cleaned = '<p>ParserError when sanitizing</p>'
|
||||||
except Exception, e:
|
except Exception:
|
||||||
_logger.warning('html_sanitize: unknown error "%s" obtained when sanitizing "%s"' % (e, src))
|
if not silent:
|
||||||
|
raise
|
||||||
|
logger.warning('unknown error obtained when sanitizing %r', src, exc_info=True)
|
||||||
cleaned = '<p>Unknown error when sanitizing</p>'
|
cleaned = '<p>Unknown error when sanitizing</p>'
|
||||||
return cleaned
|
return cleaned
|
||||||
|
|
||||||
|
|
|
@ -421,6 +421,7 @@ def get_iso_codes(lang):
|
||||||
|
|
||||||
ALL_LANGUAGES = {
|
ALL_LANGUAGES = {
|
||||||
'ab_RU': u'Abkhazian / аҧсуа',
|
'ab_RU': u'Abkhazian / аҧсуа',
|
||||||
|
'am_ET': u'Amharic / አምሃርኛ',
|
||||||
'ar_SY': u'Arabic / الْعَرَبيّة',
|
'ar_SY': u'Arabic / الْعَرَبيّة',
|
||||||
'bg_BG': u'Bulgarian / български език',
|
'bg_BG': u'Bulgarian / български език',
|
||||||
'bs_BS': u'Bosnian / bosanski jezik',
|
'bs_BS': u'Bosnian / bosanski jezik',
|
||||||
|
|
Loading…
Reference in New Issue