summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-08-07 16:54:18 +0200
committerChristophe Simonis <chs@odoo.com>2018-08-07 16:54:18 +0200
commit0c5cbbe0bb4209dd6924494cd0c4d07dd3892b9e (patch)
tree62472770e4559486ed8ead37d29af0642f8a482c
parent1f92477cf46af9580d6cb4b2a430254875e83ed1 (diff)
parent692a32e78098d57507c6ca2a5ac956af0f652446 (diff)
[MERGE] forward port branch 10.0 up to 692a32e7809
-rw-r--r--addons/auth_signup/models/res_partner.py12
-rw-r--r--addons/auth_signup/models/res_users.py12
-rw-r--r--addons/crm/tests/test_lead2opportunity.py2
-rw-r--r--addons/event_sale/models/sale_order.py9
-rw-r--r--addons/mail/models/mail_template.py12
-rw-r--r--addons/mail/views/mail_template_views.xml7
-rw-r--r--addons/point_of_sale/static/src/js/screens.js3
-rw-r--r--addons/report/models/report.py2
-rw-r--r--addons/sale/models/sale.py2
-rw-r--r--addons/web/controllers/main.py5
-rw-r--r--addons/website/models/ir_attachment.py6
-rw-r--r--addons/website_mail/controllers/main.py5
-rw-r--r--addons/website_mail/models/mail_message.py6
-rw-r--r--odoo/addons/base/ir/ir_actions.py12
-rw-r--r--odoo/addons/base/ir/ir_attachment.py21
-rw-r--r--odoo/addons/base/ir/ir_qweb/assetsbundle.py7
-rw-r--r--odoo/addons/base/module/module.py1
-rw-r--r--odoo/addons/base/res/res_users.py27
-rw-r--r--odoo/http.py1
-rw-r--r--odoo/models.py15
-rw-r--r--odoo/tools/misc.py21
21 files changed, 140 insertions, 48 deletions
diff --git a/addons/auth_signup/models/res_partner.py b/addons/auth_signup/models/res_partner.py
index a1d9f7d3ec5..1f79b89e992 100644
--- a/addons/auth_signup/models/res_partner.py
+++ b/addons/auth_signup/models/res_partner.py
@@ -26,9 +26,9 @@ def now(**kwargs):
class ResPartner(models.Model):
_inherit = 'res.partner'
- signup_token = fields.Char(copy=False)
- signup_type = fields.Char(string='Signup Token Type', copy=False)
- signup_expiration = fields.Datetime(copy=False)
+ signup_token = fields.Char(copy=False, groups="base.group_erp_manager")
+ signup_type = fields.Char(string='Signup Token Type', copy=False, groups="base.group_erp_manager")
+ signup_expiration = fields.Datetime(copy=False, groups="base.group_erp_manager")
signup_valid = fields.Boolean(compute='_compute_signup_valid', string='Signup Token is Valid')
signup_url = fields.Char(compute='_compute_signup_url', string='Signup URL')
@@ -36,15 +36,17 @@ class ResPartner(models.Model):
@api.depends('signup_token', 'signup_expiration')
def _compute_signup_valid(self):
dt = now()
- for partner in self:
+ for partner in self.sudo():
partner.signup_valid = bool(partner.signup_token) and \
(not partner.signup_expiration or dt <= partner.signup_expiration)
@api.multi
def _compute_signup_url(self):
""" proxy for function field towards actual implementation """
- result = self._get_signup_url_for_action()
+ result = self.sudo()._get_signup_url_for_action()
for partner in self:
+ if any(u.has_group('base.group_user') for u in partner.user_ids if u != self.env.user):
+ self.env['res.users'].check_access_rights('write')
partner.signup_url = result.get(partner.id, False)
@api.multi
diff --git a/addons/auth_signup/models/res_users.py b/addons/auth_signup/models/res_users.py
index f5c0a6f2d88..c170b7ab8a4 100644
--- a/addons/auth_signup/models/res_users.py
+++ b/addons/auth_signup/models/res_users.py
@@ -132,10 +132,20 @@ class ResUsers(models.Model):
template = self.env.ref('auth_signup.reset_password_email')
assert template._name == 'mail.template'
+ template_values = {
+ 'email_to': '${object.email|safe}',
+ 'email_cc': False,
+ 'auto_delete': True,
+ 'partner_to': False,
+ 'scheduled_date': False,
+ }
+ template.write(template_values)
+
for user in self:
if not user.email:
raise UserError(_("Cannot send email: user %s has no email address.") % user.name)
- template.with_context(lang=user.lang).send_mail(user.id, force_send=True, raise_exception=True)
+ with self.env.cr.savepoint():
+ template.with_context(lang=user.lang).send_mail(user.id, force_send=True, raise_exception=True)
_logger.info("Password reset email sent for user <%s> to <%s>", user.login, user.email)
@api.model
diff --git a/addons/crm/tests/test_lead2opportunity.py b/addons/crm/tests/test_lead2opportunity.py
index 861444b3f7c..e4847b5bf45 100644
--- a/addons/crm/tests/test_lead2opportunity.py
+++ b/addons/crm/tests/test_lead2opportunity.py
@@ -38,7 +38,7 @@ class TestLead2opportunity2win(TestCrmCases):
crm_case_3.message_post(subject='Test note', body='Détails envoyés par le client sur ​​le FAX pour la qualité')
# I convert mass lead into opportunity customer.
- mass = CrmLead2OpportunityPartnerMass.with_context({'active_model': 'crm.lead', 'active_ids': [crm_case_13.id, crm_case_2.id], 'active_id': crm_case_13.id}).create({
+ mass = CrmLead2OpportunityPartnerMass.sudo(self.crm_salemanager.id).with_context({'active_model': 'crm.lead', 'active_ids': [crm_case_13.id, crm_case_2.id], 'active_id': crm_case_13.id}).create({
'user_ids': [(6, 0, self.env.ref('base.user_root').ids)],
'team_id': self.env.ref("sales_team.team_sales_department").id
})
diff --git a/addons/event_sale/models/sale_order.py b/addons/event_sale/models/sale_order.py
index 7baca444240..2ea7d127cc8 100644
--- a/addons/event_sale/models/sale_order.py
+++ b/addons/event_sale/models/sale_order.py
@@ -8,11 +8,12 @@ class SaleOrder(models.Model):
@api.multi
def action_confirm(self):
- self.ensure_one()
res = super(SaleOrder, self).action_confirm()
- self.order_line._update_registrations(confirm=True)
- if any(self.order_line.filtered(lambda line: line.event_id)):
- return self.env['ir.actions.act_window'].with_context(default_sale_order_id=self.id).for_xml_id('event_sale', 'action_sale_order_event_registration')
+ for order in self:
+ order.order_line._update_registrations(confirm=True)
+ if any(order.order_line.filtered(lambda line: line.event_id)):
+ return self.env['ir.actions.act_window'].with_context(default_sale_order_id=order.id).for_xml_id(
+ 'event_sale', 'action_sale_order_event_registration')
return res
diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py
index 01f26f3d048..5fdf370bf65 100644
--- a/addons/mail/models/mail_template.py
+++ b/addons/mail/models/mail_template.py
@@ -262,22 +262,22 @@ class MailTemplate(models.Model):
def unlink_action(self):
for template in self:
if template.ref_ir_act_window:
- template.ref_ir_act_window.sudo().unlink()
+ template.ref_ir_act_window.unlink()
if template.ref_ir_value:
- template.ref_ir_value.sudo().unlink()
+ template.ref_ir_value.unlink()
return True
@api.multi
def create_action(self):
- ActWindowSudo = self.env['ir.actions.act_window'].sudo()
- IrValuesSudo = self.env['ir.values'].sudo()
+ ActWindow = self.env['ir.actions.act_window']
+ IrValues = self.env['ir.values']
view = self.env.ref('mail.email_compose_message_wizard_form')
for template in self:
src_obj = template.model_id.model
button_name = _('Send Mail (%s)') % template.name
- action = ActWindowSudo.create({
+ action = ActWindow.create({
'name': button_name,
'type': 'ir.actions.act_window',
'res_model': 'mail.compose.message',
@@ -288,7 +288,7 @@ class MailTemplate(models.Model):
'view_id': view.id,
'target': 'new',
})
- ir_value = IrValuesSudo.create({
+ ir_value = IrValues.create({
'name': button_name,
'model': src_obj,
'key2': 'client_action_multi',
diff --git a/addons/mail/views/mail_template_views.xml b/addons/mail/views/mail_template_views.xml
index 483c80ae394..4306f1fe8d1 100644
--- a/addons/mail/views/mail_template_views.xml
+++ b/addons/mail/views/mail_template_views.xml
@@ -9,7 +9,9 @@
<sheet>
<div class="oe_button_box" name="button_box">
<field name="ref_ir_act_window" invisible="1"/>
- <button class="oe_stat_button" name="create_action" type="object"
+ <button class="oe_stat_button"
+ groups="base.group_system"
+ name="create_action" type="object"
attrs="{'invisible':[('ref_ir_act_window','!=',False)]}" icon="fa-plus"
help="Display an option on related documents to open a composition wizard with this template">
<div class="o_form_field o_stat_info">
@@ -17,7 +19,8 @@
<span class="o_stat_text">Context Action</span>
</div>
</button>
- <button name="unlink_action" type="object"
+ <button name="unlink_action" type="object"
+ groups="base.group_system"
class="oe_stat_button" icon="fa-minus"
attrs="{'invisible':[('ref_ir_act_window','=',False)]}"
help="Remove the contextual action to use this template on related documents" widget="statinfo">
diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js
index e64e12ffd38..b37dc13ff95 100644
--- a/addons/point_of_sale/static/src/js/screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -1317,6 +1317,9 @@ var ClientListScreenWidget = ScreenWidget.extend({
reload_partners: function(){
var self = this;
return this.pos.load_new_partners().then(function(){
+ // partners may have changed in the backend
+ self.partner_cache = new DomCache();
+
self.render_list(self.pos.db.get_partners_sorted(1000));
// update the currently assigned client if it has been changed in db.
diff --git a/addons/report/models/report.py b/addons/report/models/report.py
index 0f0d3e13a0b..e7afee3e0e1 100644
--- a/addons/report/models/report.py
+++ b/addons/report/models/report.py
@@ -526,7 +526,7 @@ class Report(models.Model):
:specific_paperformat_args: a dict containing prioritized wkhtmltopdf arguments
:returns: list of string representing the wkhtmltopdf arguments
"""
- command_args = []
+ command_args = ['--disable-local-file-access']
if paperformat.format and paperformat.format != 'custom':
command_args.extend(['--page-size', paperformat.format])
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index db883fb3080..e94ba3eac75 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -1014,13 +1014,13 @@ class SaleOrderLine(models.Model):
@api.onchange('product_id', 'price_unit', 'product_uom', 'product_uom_qty', 'tax_id')
def _onchange_discount(self):
- self.discount = 0.0
if not (self.product_id and self.product_uom and
self.order_id.partner_id and self.order_id.pricelist_id and
self.order_id.pricelist_id.discount_policy == 'without_discount' and
self.env.user.has_group('sale.group_discount_per_so_line')):
return
+ self.discount = 0.0
product = self.product_id.with_context(
lang=self.order_id.partner_id.lang,
partner=self.order_id.partner_id.id,
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index ff6dac34c05..3a4ca384657 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -711,6 +711,8 @@ class Database(http.Controller):
@http.route('/web/database/restore', type='http', auth="none", methods=['POST'], csrf=False)
def restore(self, master_pwd, backup_file, name, copy=False):
try:
+ data_file = None
+ db.check_super(master_pwd)
with tempfile.NamedTemporaryFile(delete=False) as data_file:
backup_file.save(data_file)
db.restore_db(name, data_file.name, str2bool(copy))
@@ -719,7 +721,8 @@ class Database(http.Controller):
error = "Database restore error: %s" % (str(e) or repr(e))
return self._render_template(error=error)
finally:
- os.unlink(data_file.name)
+ if data_file:
+ os.unlink(data_file.name)
@http.route('/web/database/change_password', type='http', auth="none", methods=['POST'], csrf=False)
def change_password(self, master_pwd, master_pwd_new):
diff --git a/addons/website/models/ir_attachment.py b/addons/website/models/ir_attachment.py
index 215b182bfb8..eb81cd6702c 100644
--- a/addons/website/models/ir_attachment.py
+++ b/addons/website/models/ir_attachment.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import fields, models
+from odoo import api, fields, models
class Attachment(models.Model):
@@ -10,3 +10,7 @@ class Attachment(models.Model):
# related for backward compatibility with saas-6
website_url = fields.Char(string="Attachment URL", related='local_url', deprecated=True)
+
+ @api.model
+ def get_serving_groups(self):
+ return super(Attachment, self).get_serving_groups() + ['website.group_website_designer']
diff --git a/addons/website_mail/controllers/main.py b/addons/website_mail/controllers/main.py
index 4e85c240767..44ccb232d1f 100644
--- a/addons/website_mail/controllers/main.py
+++ b/addons/website_mail/controllers/main.py
@@ -40,7 +40,7 @@ def _message_post_helper(res_model='', res_id=None, message='', token='', token_
optional keywords arguments:
:param string token: access token if the object's model uses some kind of public access
using tokens (usually a uuid4) to bypass access rules
- :param string token_field: name of the field that contains the token on the object (defaults to 'token')
+ :param string token_field: name of the field that contains the token on the object (deprecated, use _mail_post_token_field)
:param string sha_in: Deprecated, use `token` and `token_field` instead
:param str sha_time: Deprecated, use `token` and `token_field` instead
:param bool nosubscribe: set False if you want the partner to be set as follower of the object when posting (default to True)
@@ -49,7 +49,7 @@ def _message_post_helper(res_model='', res_id=None, message='', token='', token_
"""
record = request.env[res_model].browse(res_id)
author_id = request.env.user.partner_id.id if request.env.user.partner_id else False
- if token and record and token == getattr(record.sudo(), token_field, None):
+ if token and record and token == getattr(record.sudo(), record._mail_post_token_field, None):
record = record.sudo()
if request.env.user == request.env.ref('base.public_user'):
author_id = record.partner_id.id if hasattr(record, 'partner_id') else author_id
@@ -57,6 +57,7 @@ def _message_post_helper(res_model='', res_id=None, message='', token='', token_
if not author_id:
raise NotFound()
kw.pop('csrf_token', None)
+ kw.pop('attachment_ids', None)
return record.with_context(mail_create_nosubscribe=nosubscribe).message_post(body=message,
message_type=kw.pop('message_type', "comment"),
subtype=kw.pop('subtype', "mt_comment"),
diff --git a/addons/website_mail/models/mail_message.py b/addons/website_mail/models/mail_message.py
index d5e12e41b0b..e60392f62bd 100644
--- a/addons/website_mail/models/mail_message.py
+++ b/addons/website_mail/models/mail_message.py
@@ -56,3 +56,9 @@ class MailMessage(models.Model):
if self.env.cr.fetchall():
raise AccessError(_('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % (self._description, operation))
return super(MailMessage, self).check_access_rule(operation=operation)
+
+
+class MailThread(models.AbstractModel):
+ _inherit = 'mail.thread'
+
+ _mail_post_token_field = 'access_token' # token field for external posts, to be overridden
diff --git a/odoo/addons/base/ir/ir_actions.py b/odoo/addons/base/ir/ir_actions.py
index 48e8baffd64..557ab5ffc7f 100644
--- a/odoo/addons/base/ir/ir_actions.py
+++ b/odoo/addons/base/ir/ir_actions.py
@@ -2,7 +2,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import datetime
-import dateutil
import logging
import os
import time
@@ -13,9 +12,20 @@ from odoo import api, fields, models, tools, _
from odoo.exceptions import MissingError, UserError, ValidationError
from odoo.report.report_sxw import report_sxw, report_rml
from odoo.tools.safe_eval import safe_eval, test_python_expr
+from odoo.tools.misc import wrap_module
_logger = logging.getLogger(__name__)
+# build dateutil helper, starting with the relevant *lazy* imports
+import dateutil
+import dateutil.parser
+import dateutil.relativedelta
+import dateutil.rrule
+import dateutil.tz
+mods = {'parser', 'relativedelta', 'rrule', 'tz'}
+attribs = {atr for m in mods for atr in getattr(dateutil, m).__all__}
+dateutil = wrap_module(dateutil, mods | attribs)
+
class IrActions(models.Model):
_name = 'ir.actions.actions'
diff --git a/odoo/addons/base/ir/ir_attachment.py b/odoo/addons/base/ir/ir_attachment.py
index 954fda6f038..c3fa524e4a9 100644
--- a/odoo/addons/base/ir/ir_attachment.py
+++ b/odoo/addons/base/ir/ir_attachment.py
@@ -10,7 +10,7 @@ import re
from collections import defaultdict
from odoo import api, fields, models, tools, SUPERUSER_ID, _
-from odoo.exceptions import AccessError
+from odoo.exceptions import AccessError, ValidationError
from odoo.tools import config, human_size, ustr, html_escape
from odoo.tools.mimetypes import guess_mimetype
@@ -262,6 +262,15 @@ class IrAttachment(models.Model):
index_content = ustr("\n".join(words))
return index_content
+ @api.model
+ def get_serving_groups(self):
+ """ An ir.attachment record may be used as a fallback in the
+ http dispatch if its type field is set to "binary" and its url
+ field is set as the request's url. Only the groups returned by
+ this method are allowed to create and write on such records.
+ """
+ return ['base.group_system']
+
name = fields.Char('Attachment Name', required=True)
datas_fname = fields.Char('File Name')
description = fields.Text('Description')
@@ -297,6 +306,16 @@ class IrAttachment(models.Model):
self._cr.commit()
return res
+ @api.one
+ @api.constrains('type', 'url')
+ def _check_serving_attachments(self):
+ # restrict writing on attachments that could be served by the
+ # ir.http's dispatch exception handling
+ if self.type == 'binary' and self.url:
+ has_group = self.env.user.has_group
+ if not any([has_group(g) for g in self.get_serving_groups()]):
+ raise ValidationError("Sorry, you are not allowed to write on this document")
+
@api.model
def check(self, mode, values=None):
"""Restricts the access to an ir.attachment, according to referred model
diff --git a/odoo/addons/base/ir/ir_qweb/assetsbundle.py b/odoo/addons/base/ir/ir_qweb/assetsbundle.py
index 28708f19ecb..c6e627c7bb8 100644
--- a/odoo/addons/base/ir/ir_qweb/assetsbundle.py
+++ b/odoo/addons/base/ir/ir_qweb/assetsbundle.py
@@ -8,7 +8,7 @@ import textwrap
import uuid
from datetime import datetime
from subprocess import Popen, PIPE
-from odoo import fields, tools
+from odoo import fields, tools, SUPERUSER_ID
from odoo.http import request
from odoo.modules.module import get_resource_path
import psycopg2
@@ -191,10 +191,11 @@ class AssetsBundle(object):
self.env.cr.execute("""
SELECT max(id)
FROM ir_attachment
- WHERE url like %s
+ WHERE create_uid = %s
+ AND url like %s
GROUP BY datas_fname
ORDER BY datas_fname
- """, [url_pattern])
+ """, [SUPERUSER_ID, url_pattern])
attachment_ids = [r[0] for r in self.env.cr.fetchall()]
return self.env['ir.attachment'].sudo().browse(attachment_ids)
diff --git a/odoo/addons/base/module/module.py b/odoo/addons/base/module/module.py
index e499eb6550b..82893800496 100644
--- a/odoo/addons/base/module/module.py
+++ b/odoo/addons/base/module/module.py
@@ -169,6 +169,7 @@ class Module(models.Model):
'doctitle_xform': False,
'output_encoding': 'unicode',
'xml_declaration': False,
+ 'file_insertion_enabled': False,
}
output = publish_string(source=module.description or '', settings_overrides=overrides, writer=MyWriter())
module.description_html = tools.html_sanitize(output)
diff --git a/odoo/addons/base/res/res_users.py b/odoo/addons/base/res/res_users.py
index 03058c4065c..8b3a9fd5549 100644
--- a/odoo/addons/base/res/res_users.py
+++ b/odoo/addons/base/res/res_users.py
@@ -282,6 +282,19 @@ class Users(models.Model):
if any(user.company_ids and user.company_id not in user.company_ids for user in self):
raise ValidationError(_('The chosen company is not in the allowed companies for this user'))
+ def _read_from_database(self, field_names, inherited_field_names=[]):
+ super(Users, self)._read_from_database(field_names, inherited_field_names)
+ canwrite = self.check_access_rights('write', raise_exception=False)
+ if not canwrite and set(USER_PRIVATE_FIELDS).intersection(field_names):
+ for record in self:
+ for f in USER_PRIVATE_FIELDS:
+ try:
+ record._cache[f]
+ record._cache[f] = '********'
+ except Exception:
+ # skip SpecialValue (e.g. for missing record or access right)
+ pass
+
@api.multi
@api.constrains('action_id')
def _check_action_id(self):
@@ -299,19 +312,7 @@ class Users(models.Model):
# safe fields only, so we read as super-user to bypass access rights
self = self.sudo()
- result = super(Users, self).read(fields=fields, load=load)
-
- canwrite = self.env['ir.model.access'].check('res.users', 'write', False)
- if not canwrite:
- def override_password(vals):
- if (vals['id'] != self._uid):
- for key in USER_PRIVATE_FIELDS:
- if key in vals:
- vals[key] = '********'
- return vals
- result = map(override_password, result)
-
- return result
+ return super(Users, self).read(fields=fields, load=load)
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
diff --git a/odoo/http.py b/odoo/http.py
index 0075b1bed34..5a8f6448956 100644
--- a/odoo/http.py
+++ b/odoo/http.py
@@ -1523,6 +1523,7 @@ def db_filter(dbs, httprequest=None):
if d == "www" and r:
d = r.partition('.')[0]
if odoo.tools.config['dbfilter']:
+ d, h = re.escape(d), re.escape(h)
r = odoo.tools.config['dbfilter'].replace('%h', h).replace('%d', d)
dbs = [i for i in dbs if re.match(r, i)]
elif odoo.tools.config['db_name']:
diff --git a/odoo/models.py b/odoo/models.py
index 108df85a318..62556d94c73 100644
--- a/odoo/models.py
+++ b/odoo/models.py
@@ -60,7 +60,7 @@ _unlink = logging.getLogger(__name__ + '.unlink')
regex_order = re.compile('^(\s*([a-z0-9:_]+|"[a-z0-9:_]+")(\s+(desc|asc))?\s*(,|$))+(?<!,)$', re.I)
regex_object_name = re.compile(r'^[a-z0-9_.]+$')
regex_pg_name = re.compile(r'^[a-z_][a-z0-9_$]*$', re.I)
-onchange_v7 = re.compile(r"^(\w+)\((.*)\)$")
+onchange_v7 = re.compile(r"^([a-zA-Z]\w+)\((.*)\)$")
AUTOINIT_RECALCULATE_STORED_FIELDS = 1000
@@ -4053,6 +4053,15 @@ class BaseModel(object):
if table not in query.tables:
query.tables.append(table)
+ if self._transient:
+ # One single implicit access rule for transient models: owner only!
+ # This is ok because we assert that TransientModels always have
+ # log_access enabled, so that 'create_uid' is always there.
+ domain = [('create_uid', '=', self._uid)]
+ tquery = self._where_calc(domain, active_test=False)
+ apply_rule(tquery.where_clause, tquery.where_clause_params, tquery.tables)
+ return
+
# apply main rules on the object
Rule = self.env['ir.rule']
where_clause, where_params, tables = Rule.domain_get(self._name, mode)
@@ -4200,10 +4209,6 @@ class BaseModel(object):
"""
self.sudo(access_rights_uid or self._uid).check_access_rights('read')
- # For transient models, restrict access to the current user, except for the super-user
- if self.is_transient() and self._log_access and self._uid != SUPERUSER_ID:
- args = expression.AND(([('create_uid', '=', self._uid)], args or []))
-
query = self._where_calc(args)
self._apply_ir_rules(query, 'read')
order_by = self._generate_order_by(order, query)
diff --git a/odoo/tools/misc.py b/odoo/tools/misc.py
index 6233f4572b1..18a92b5fd9d 100644
--- a/odoo/tools/misc.py
+++ b/odoo/tools/misc.py
@@ -19,6 +19,7 @@ import socket
import sys
import threading
import time
+import types
import werkzeug.utils
import zipfile
from cStringIO import StringIO
@@ -1217,3 +1218,23 @@ class Pickle(object):
dump = cPickle.dump
pickle = Pickle
+
+def wrap_module(module, attr_list):
+ """Helper for wrapping a package/module to expose selected attributes
+
+ :param Module module: the actual package/module to wrap, as returned by ``import <module>``
+ :param iterable attr_list: a global list of attributes to expose, usually the top-level
+ attributes and their own main attributes. No support for hiding attributes in case
+ of name collision at different levels.
+ """
+ attr_list = set(attr_list)
+ class WrappedModule(object):
+ def __getattr__(self, attrib):
+ if attrib in attr_list:
+ target = getattr(module, attrib)
+ if isinstance(target, types.ModuleType):
+ return wrap_module(target, attr_list)
+ return target
+ raise AttributeError(attrib)
+ # module and attr_list are in the closure
+ return WrappedModule()