[MERGE] forward port branch 10.0 up to 692a32e780

This commit is contained in:
Christophe Simonis
2018-08-07 16:54:18 +02:00
21 changed files with 140 additions and 48 deletions
+7 -5
View File
@@ -26,9 +26,9 @@ def now(**kwargs):
class ResPartner(models.Model): class ResPartner(models.Model):
_inherit = 'res.partner' _inherit = 'res.partner'
signup_token = fields.Char(copy=False) signup_token = fields.Char(copy=False, groups="base.group_erp_manager")
signup_type = fields.Char(string='Signup Token Type', copy=False) signup_type = fields.Char(string='Signup Token Type', copy=False, groups="base.group_erp_manager")
signup_expiration = fields.Datetime(copy=False) 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_valid = fields.Boolean(compute='_compute_signup_valid', string='Signup Token is Valid')
signup_url = fields.Char(compute='_compute_signup_url', string='Signup URL') 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') @api.depends('signup_token', 'signup_expiration')
def _compute_signup_valid(self): def _compute_signup_valid(self):
dt = now() dt = now()
for partner in self: for partner in self.sudo():
partner.signup_valid = bool(partner.signup_token) and \ partner.signup_valid = bool(partner.signup_token) and \
(not partner.signup_expiration or dt <= partner.signup_expiration) (not partner.signup_expiration or dt <= partner.signup_expiration)
@api.multi @api.multi
def _compute_signup_url(self): def _compute_signup_url(self):
""" proxy for function field towards actual implementation """ """ 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: 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) partner.signup_url = result.get(partner.id, False)
@api.multi @api.multi
+11 -1
View File
@@ -132,10 +132,20 @@ class ResUsers(models.Model):
template = self.env.ref('auth_signup.reset_password_email') template = self.env.ref('auth_signup.reset_password_email')
assert template._name == 'mail.template' 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: for user in self:
if not user.email: if not user.email:
raise UserError(_("Cannot send email: user %s has no email address.") % user.name) 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) _logger.info("Password reset email sent for user <%s> to <%s>", user.login, user.email)
@api.model @api.model
+1 -1
View File
@@ -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é') 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. # 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)], 'user_ids': [(6, 0, self.env.ref('base.user_root').ids)],
'team_id': self.env.ref("sales_team.team_sales_department").id 'team_id': self.env.ref("sales_team.team_sales_department").id
}) })
+5 -4
View File
@@ -8,11 +8,12 @@ class SaleOrder(models.Model):
@api.multi @api.multi
def action_confirm(self): def action_confirm(self):
self.ensure_one()
res = super(SaleOrder, self).action_confirm() res = super(SaleOrder, self).action_confirm()
self.order_line._update_registrations(confirm=True) for order in self:
if any(self.order_line.filtered(lambda line: line.event_id)): order.order_line._update_registrations(confirm=True)
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') 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 return res
+6 -6
View File
@@ -262,22 +262,22 @@ class MailTemplate(models.Model):
def unlink_action(self): def unlink_action(self):
for template in self: for template in self:
if template.ref_ir_act_window: if template.ref_ir_act_window:
template.ref_ir_act_window.sudo().unlink() template.ref_ir_act_window.unlink()
if template.ref_ir_value: if template.ref_ir_value:
template.ref_ir_value.sudo().unlink() template.ref_ir_value.unlink()
return True return True
@api.multi @api.multi
def create_action(self): def create_action(self):
ActWindowSudo = self.env['ir.actions.act_window'].sudo() ActWindow = self.env['ir.actions.act_window']
IrValuesSudo = self.env['ir.values'].sudo() IrValues = self.env['ir.values']
view = self.env.ref('mail.email_compose_message_wizard_form') view = self.env.ref('mail.email_compose_message_wizard_form')
for template in self: for template in self:
src_obj = template.model_id.model src_obj = template.model_id.model
button_name = _('Send Mail (%s)') % template.name button_name = _('Send Mail (%s)') % template.name
action = ActWindowSudo.create({ action = ActWindow.create({
'name': button_name, 'name': button_name,
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
'res_model': 'mail.compose.message', 'res_model': 'mail.compose.message',
@@ -288,7 +288,7 @@ class MailTemplate(models.Model):
'view_id': view.id, 'view_id': view.id,
'target': 'new', 'target': 'new',
}) })
ir_value = IrValuesSudo.create({ ir_value = IrValues.create({
'name': button_name, 'name': button_name,
'model': src_obj, 'model': src_obj,
'key2': 'client_action_multi', 'key2': 'client_action_multi',
+4 -1
View File
@@ -9,7 +9,9 @@
<sheet> <sheet>
<div class="oe_button_box" name="button_box"> <div class="oe_button_box" name="button_box">
<field name="ref_ir_act_window" invisible="1"/> <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" 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"> help="Display an option on related documents to open a composition wizard with this template">
<div class="o_form_field o_stat_info"> <div class="o_form_field o_stat_info">
@@ -18,6 +20,7 @@
</div> </div>
</button> </button>
<button name="unlink_action" type="object" <button name="unlink_action" type="object"
groups="base.group_system"
class="oe_stat_button" icon="fa-minus" class="oe_stat_button" icon="fa-minus"
attrs="{'invisible':[('ref_ir_act_window','=',False)]}" attrs="{'invisible':[('ref_ir_act_window','=',False)]}"
help="Remove the contextual action to use this template on related documents" widget="statinfo"> help="Remove the contextual action to use this template on related documents" widget="statinfo">
@@ -1317,6 +1317,9 @@ var ClientListScreenWidget = ScreenWidget.extend({
reload_partners: function(){ reload_partners: function(){
var self = this; var self = this;
return this.pos.load_new_partners().then(function(){ 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)); self.render_list(self.pos.db.get_partners_sorted(1000));
// update the currently assigned client if it has been changed in db. // update the currently assigned client if it has been changed in db.
+1 -1
View File
@@ -526,7 +526,7 @@ class Report(models.Model):
:specific_paperformat_args: a dict containing prioritized wkhtmltopdf arguments :specific_paperformat_args: a dict containing prioritized wkhtmltopdf arguments
:returns: list of string representing the 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': if paperformat.format and paperformat.format != 'custom':
command_args.extend(['--page-size', paperformat.format]) command_args.extend(['--page-size', paperformat.format])
+1 -1
View File
@@ -1014,13 +1014,13 @@ class SaleOrderLine(models.Model):
@api.onchange('product_id', 'price_unit', 'product_uom', 'product_uom_qty', 'tax_id') @api.onchange('product_id', 'price_unit', 'product_uom', 'product_uom_qty', 'tax_id')
def _onchange_discount(self): def _onchange_discount(self):
self.discount = 0.0
if not (self.product_id and self.product_uom and 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.partner_id and self.order_id.pricelist_id and
self.order_id.pricelist_id.discount_policy == 'without_discount' and self.order_id.pricelist_id.discount_policy == 'without_discount' and
self.env.user.has_group('sale.group_discount_per_so_line')): self.env.user.has_group('sale.group_discount_per_so_line')):
return return
self.discount = 0.0
product = self.product_id.with_context( product = self.product_id.with_context(
lang=self.order_id.partner_id.lang, lang=self.order_id.partner_id.lang,
partner=self.order_id.partner_id.id, partner=self.order_id.partner_id.id,
+4 -1
View File
@@ -711,6 +711,8 @@ class Database(http.Controller):
@http.route('/web/database/restore', type='http', auth="none", methods=['POST'], csrf=False) @http.route('/web/database/restore', type='http', auth="none", methods=['POST'], csrf=False)
def restore(self, master_pwd, backup_file, name, copy=False): def restore(self, master_pwd, backup_file, name, copy=False):
try: try:
data_file = None
db.check_super(master_pwd)
with tempfile.NamedTemporaryFile(delete=False) as data_file: with tempfile.NamedTemporaryFile(delete=False) as data_file:
backup_file.save(data_file) backup_file.save(data_file)
db.restore_db(name, data_file.name, str2bool(copy)) 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)) error = "Database restore error: %s" % (str(e) or repr(e))
return self._render_template(error=error) return self._render_template(error=error)
finally: 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) @http.route('/web/database/change_password', type='http', auth="none", methods=['POST'], csrf=False)
def change_password(self, master_pwd, master_pwd_new): def change_password(self, master_pwd, master_pwd_new):
+5 -1
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details. # 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): class Attachment(models.Model):
@@ -10,3 +10,7 @@ class Attachment(models.Model):
# related for backward compatibility with saas-6 # related for backward compatibility with saas-6
website_url = fields.Char(string="Attachment URL", related='local_url', deprecated=True) 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']
+3 -2
View File
@@ -40,7 +40,7 @@ def _message_post_helper(res_model='', res_id=None, message='', token='', token_
optional keywords arguments: optional keywords arguments:
:param string token: access token if the object's model uses some kind of public access :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 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 string sha_in: Deprecated, use `token` and `token_field` instead
:param str sha_time: 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) :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) record = request.env[res_model].browse(res_id)
author_id = request.env.user.partner_id.id if request.env.user.partner_id else False 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() record = record.sudo()
if request.env.user == request.env.ref('base.public_user'): if request.env.user == request.env.ref('base.public_user'):
author_id = record.partner_id.id if hasattr(record, 'partner_id') else author_id 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: if not author_id:
raise NotFound() raise NotFound()
kw.pop('csrf_token', None) kw.pop('csrf_token', None)
kw.pop('attachment_ids', None)
return record.with_context(mail_create_nosubscribe=nosubscribe).message_post(body=message, return record.with_context(mail_create_nosubscribe=nosubscribe).message_post(body=message,
message_type=kw.pop('message_type', "comment"), message_type=kw.pop('message_type', "comment"),
subtype=kw.pop('subtype', "mt_comment"), subtype=kw.pop('subtype', "mt_comment"),
@@ -56,3 +56,9 @@ class MailMessage(models.Model):
if self.env.cr.fetchall(): 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)) 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) 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
+11 -1
View File
@@ -2,7 +2,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
import datetime import datetime
import dateutil
import logging import logging
import os import os
import time import time
@@ -13,9 +12,20 @@ from odoo import api, fields, models, tools, _
from odoo.exceptions import MissingError, UserError, ValidationError from odoo.exceptions import MissingError, UserError, ValidationError
from odoo.report.report_sxw import report_sxw, report_rml from odoo.report.report_sxw import report_sxw, report_rml
from odoo.tools.safe_eval import safe_eval, test_python_expr from odoo.tools.safe_eval import safe_eval, test_python_expr
from odoo.tools.misc import wrap_module
_logger = logging.getLogger(__name__) _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): class IrActions(models.Model):
_name = 'ir.actions.actions' _name = 'ir.actions.actions'
+20 -1
View File
@@ -10,7 +10,7 @@ import re
from collections import defaultdict from collections import defaultdict
from odoo import api, fields, models, tools, SUPERUSER_ID, _ 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 import config, human_size, ustr, html_escape
from odoo.tools.mimetypes import guess_mimetype from odoo.tools.mimetypes import guess_mimetype
@@ -262,6 +262,15 @@ class IrAttachment(models.Model):
index_content = ustr("\n".join(words)) index_content = ustr("\n".join(words))
return index_content 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) name = fields.Char('Attachment Name', required=True)
datas_fname = fields.Char('File Name') datas_fname = fields.Char('File Name')
description = fields.Text('Description') description = fields.Text('Description')
@@ -297,6 +306,16 @@ class IrAttachment(models.Model):
self._cr.commit() self._cr.commit()
return res 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 @api.model
def check(self, mode, values=None): def check(self, mode, values=None):
"""Restricts the access to an ir.attachment, according to referred model """Restricts the access to an ir.attachment, according to referred model
+4 -3
View File
@@ -8,7 +8,7 @@ import textwrap
import uuid import uuid
from datetime import datetime from datetime import datetime
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from odoo import fields, tools from odoo import fields, tools, SUPERUSER_ID
from odoo.http import request from odoo.http import request
from odoo.modules.module import get_resource_path from odoo.modules.module import get_resource_path
import psycopg2 import psycopg2
@@ -191,10 +191,11 @@ class AssetsBundle(object):
self.env.cr.execute(""" self.env.cr.execute("""
SELECT max(id) SELECT max(id)
FROM ir_attachment FROM ir_attachment
WHERE url like %s WHERE create_uid = %s
AND url like %s
GROUP BY datas_fname GROUP BY datas_fname
ORDER BY datas_fname ORDER BY datas_fname
""", [url_pattern]) """, [SUPERUSER_ID, url_pattern])
attachment_ids = [r[0] for r in self.env.cr.fetchall()] attachment_ids = [r[0] for r in self.env.cr.fetchall()]
return self.env['ir.attachment'].sudo().browse(attachment_ids) return self.env['ir.attachment'].sudo().browse(attachment_ids)
+1
View File
@@ -169,6 +169,7 @@ class Module(models.Model):
'doctitle_xform': False, 'doctitle_xform': False,
'output_encoding': 'unicode', 'output_encoding': 'unicode',
'xml_declaration': False, 'xml_declaration': False,
'file_insertion_enabled': False,
} }
output = publish_string(source=module.description or '', settings_overrides=overrides, writer=MyWriter()) output = publish_string(source=module.description or '', settings_overrides=overrides, writer=MyWriter())
module.description_html = tools.html_sanitize(output) module.description_html = tools.html_sanitize(output)
+14 -13
View File
@@ -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): 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')) 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.multi
@api.constrains('action_id') @api.constrains('action_id')
def _check_action_id(self): 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 # safe fields only, so we read as super-user to bypass access rights
self = self.sudo() self = self.sudo()
result = super(Users, self).read(fields=fields, load=load) return 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
@api.model @api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True): def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
+1
View File
@@ -1523,6 +1523,7 @@ def db_filter(dbs, httprequest=None):
if d == "www" and r: if d == "www" and r:
d = r.partition('.')[0] d = r.partition('.')[0]
if odoo.tools.config['dbfilter']: if odoo.tools.config['dbfilter']:
d, h = re.escape(d), re.escape(h)
r = odoo.tools.config['dbfilter'].replace('%h', h).replace('%d', d) r = odoo.tools.config['dbfilter'].replace('%h', h).replace('%d', d)
dbs = [i for i in dbs if re.match(r, i)] dbs = [i for i in dbs if re.match(r, i)]
elif odoo.tools.config['db_name']: elif odoo.tools.config['db_name']:
+10 -5
View File
@@ -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_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_object_name = re.compile(r'^[a-z0-9_.]+$')
regex_pg_name = re.compile(r'^[a-z_][a-z0-9_$]*$', re.I) 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 AUTOINIT_RECALCULATE_STORED_FIELDS = 1000
@@ -4053,6 +4053,15 @@ class BaseModel(object):
if table not in query.tables: if table not in query.tables:
query.tables.append(table) 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 # apply main rules on the object
Rule = self.env['ir.rule'] Rule = self.env['ir.rule']
where_clause, where_params, tables = Rule.domain_get(self._name, mode) 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') 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) query = self._where_calc(args)
self._apply_ir_rules(query, 'read') self._apply_ir_rules(query, 'read')
order_by = self._generate_order_by(order, query) order_by = self._generate_order_by(order, query)
+21
View File
@@ -19,6 +19,7 @@ import socket
import sys import sys
import threading import threading
import time import time
import types
import werkzeug.utils import werkzeug.utils
import zipfile import zipfile
from cStringIO import StringIO from cStringIO import StringIO
@@ -1217,3 +1218,23 @@ class Pickle(object):
dump = cPickle.dump dump = cPickle.dump
pickle = Pickle 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()