[MERGE] forward port branch 10.0 up to 692a32e780
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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">
|
||||||
@@ -17,7 +19,8 @@
|
|||||||
<span class="o_stat_text">Context Action</span>
|
<span class="o_stat_text">Context Action</span>
|
||||||
</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.
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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']
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user