[MERGE] forward port branch 11.0 up to 27081bf6f5
This commit is contained in:
@@ -644,8 +644,8 @@ class AccountBankStatementLine(models.Model):
|
||||
|
||||
# Create The payment
|
||||
payment = self.env['account.payment']
|
||||
partner_id = self.partner_id or (aml_dict.get('move_line') and aml_dict['move_line'].partner_id) or self.env['res.partner']
|
||||
if abs(total)>0.00001:
|
||||
partner_id = self.partner_id and self.partner_id.id or False
|
||||
partner_type = False
|
||||
if partner_id and len(account_types) == 1:
|
||||
partner_type = 'customer' if account_types == receivable_account_type else 'supplier'
|
||||
@@ -660,7 +660,7 @@ class AccountBankStatementLine(models.Model):
|
||||
payment = self.env['account.payment'].create({
|
||||
'payment_method_id': payment_methods and payment_methods[0].id or False,
|
||||
'payment_type': total >0 and 'inbound' or 'outbound',
|
||||
'partner_id': self.partner_id and self.partner_id.id or False,
|
||||
'partner_id': partner_id.id,
|
||||
'partner_type': partner_type,
|
||||
'journal_id': self.statement_id.journal_id.id,
|
||||
'payment_date': self.date,
|
||||
|
||||
@@ -665,12 +665,12 @@ class AccountInvoice(models.Model):
|
||||
msg = _('Cannot find a chart of accounts for this company, You should configure it. \nPlease go to Account Configuration.')
|
||||
raise RedirectWarning(msg, action.id, _('Go to the configuration panel'))
|
||||
|
||||
if type in ('out_invoice', 'out_refund'):
|
||||
account_id = rec_account.id
|
||||
payment_term_id = p.property_payment_term_id.id
|
||||
else:
|
||||
if type in ('in_invoice', 'in_refund'):
|
||||
account_id = pay_account.id
|
||||
payment_term_id = p.property_supplier_payment_term_id.id
|
||||
else:
|
||||
account_id = rec_account.id
|
||||
payment_term_id = p.property_payment_term_id.id
|
||||
|
||||
delivery_partner_id = self.get_delivery_partner_id()
|
||||
fiscal_position = self.env['account.fiscal.position'].get_fiscal_position(self.partner_id.id, delivery_id=delivery_partner_id)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from odoo.addons.account.tests.account_test_classes import AccountingTestCase
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
import time
|
||||
|
||||
@@ -60,7 +61,8 @@ class TestAccountInvoiceRounding(AccountingTestCase):
|
||||
# to avoid a ValidationError from _check_cash_rounding because the onchange
|
||||
# are not well triggered in the tests.
|
||||
try:
|
||||
invoice_id.cash_rounding_id = cash_rounding_id
|
||||
with mute_logger('odoo.models'):
|
||||
invoice_id.cash_rounding_id = cash_rounding_id
|
||||
except ValidationError:
|
||||
pass
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from odoo import api, exceptions, fields, models, _
|
||||
from odoo.tools import pycompat
|
||||
|
||||
class SignupError(Exception):
|
||||
pass
|
||||
@@ -28,16 +29,16 @@ class ResPartner(models.Model):
|
||||
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', compute_sudo=True, 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')
|
||||
|
||||
@api.multi
|
||||
@api.depends('signup_token', 'signup_expiration')
|
||||
def _compute_signup_valid(self):
|
||||
dt = now()
|
||||
for partner in self:
|
||||
partner.signup_valid = bool(partner.signup_token) and \
|
||||
(not partner.signup_expiration or dt <= partner.signup_expiration)
|
||||
for partner, partner_sudo in pycompat.izip(self, self.sudo()):
|
||||
partner.signup_valid = bool(partner_sudo.signup_token) and \
|
||||
(not partner_sudo.signup_expiration or dt <= partner_sudo.signup_expiration)
|
||||
|
||||
@api.multi
|
||||
def _compute_signup_url(self):
|
||||
|
||||
@@ -64,3 +64,17 @@ class SaleOrderLine(models.Model):
|
||||
@api.onchange('event_ticket_id')
|
||||
def _onchange_event_ticket_id(self):
|
||||
self.price_unit = (self.event_id.company_id or self.env.user.company_id).currency_id.compute(self.event_ticket_id.price, self.order_id.currency_id)
|
||||
|
||||
@api.onchange('product_uom', 'product_uom_qty')
|
||||
def product_uom_change(self):
|
||||
if not self.event_ticket_id:
|
||||
super(SaleOrderLine, self).product_uom_change()
|
||||
|
||||
@api.multi
|
||||
@api.onchange('product_id')
|
||||
def product_id_change(self):
|
||||
super(SaleOrderLine, self).product_id_change()
|
||||
self.event_id = None
|
||||
self.event_ticket_id = None
|
||||
if self.product_id.event_ok:
|
||||
self.price_unit = 0.0
|
||||
|
||||
@@ -10,7 +10,7 @@ class GamificationBadgeUserWizard(models.TransientModel):
|
||||
|
||||
employee_id = fields.Many2one('hr.employee', string='Employee', required=True)
|
||||
user_id = fields.Many2one('res.users', string='User',
|
||||
related='employee_id.user_id', store=True, readonly=True)
|
||||
related='employee_id.user_id', store=True, readonly=True, compute_sudo=True)
|
||||
|
||||
# TODO 12.0/master remove this hack by changing the model
|
||||
@api.model
|
||||
|
||||
@@ -92,7 +92,7 @@ class HolidaysRequest(models.Model):
|
||||
payslip_status = fields.Boolean('Reported in last payslips',
|
||||
help='Green this button when the leave has been taken into account in the payslip.')
|
||||
report_note = fields.Text('HR Comments')
|
||||
user_id = fields.Many2one('res.users', string='User', related='employee_id.user_id', related_sudo=True, store=True, default=lambda self: self.env.uid, readonly=True)
|
||||
user_id = fields.Many2one('res.users', string='User', related='employee_id.user_id', related_sudo=True, compute_sudo=True, store=True, default=lambda self: self.env.uid, readonly=True)
|
||||
date_from = fields.Datetime('Start Date', readonly=True, index=True, copy=False, required=True,
|
||||
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, track_visibility='onchange')
|
||||
date_to = fields.Datetime('End Date', readonly=True, copy=False, required=True,
|
||||
|
||||
@@ -77,6 +77,27 @@ class AccountFrFec(models.TransientModel):
|
||||
listrow = list(row)
|
||||
return listrow
|
||||
|
||||
def _get_company_legal_data(self, company):
|
||||
"""
|
||||
Dom-Tom are excluded from the EU's fiscal territory
|
||||
Those regions do not have SIREN
|
||||
sources:
|
||||
https://www.service-public.fr/professionnels-entreprises/vosdroits/F23570
|
||||
http://www.douane.gouv.fr/articles/a11024-tva-dans-les-dom
|
||||
"""
|
||||
dom_tom_group = self.env.ref('l10n_fr.dom-tom')
|
||||
is_dom_tom = company.country_id.code in dom_tom_group.country_ids.mapped('code')
|
||||
if not is_dom_tom and not company.vat:
|
||||
raise Warning(
|
||||
_("Missing VAT number for company %s") % company.name)
|
||||
if not is_dom_tom and company.vat[0:2] != 'FR':
|
||||
raise Warning(
|
||||
_("FEC is for French companies only !"))
|
||||
|
||||
return {
|
||||
'siren': company.vat[4:13] if not is_dom_tom else '',
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def generate_fec(self):
|
||||
self.ensure_one()
|
||||
@@ -88,6 +109,9 @@ class AccountFrFec(models.TransientModel):
|
||||
# 2) CSV files are easier to read/use for a regular accountant.
|
||||
# So it will be easier for the accountant to check the file before
|
||||
# sending it to the fiscal administration
|
||||
company = self.env.user.company_id
|
||||
company_legal_data = self._get_company_legal_data(company)
|
||||
|
||||
header = [
|
||||
u'JournalCode', # 0
|
||||
u'JournalLib', # 1
|
||||
@@ -109,18 +133,7 @@ class AccountFrFec(models.TransientModel):
|
||||
u'Idevise', # 17
|
||||
]
|
||||
|
||||
company = self.env.user.company_id
|
||||
if not company.vat:
|
||||
raise Warning(
|
||||
_("Missing VAT number for company %s") % company.name)
|
||||
if company.vat[0:2] != 'FR':
|
||||
raise Warning(
|
||||
_("FEC is for French companies only !"))
|
||||
|
||||
fecfile = io.BytesIO()
|
||||
w = pycompat.csv_writer(fecfile, delimiter='|')
|
||||
w.writerow(header)
|
||||
|
||||
rows_to_write = [header]
|
||||
# INITIAL BALANCE
|
||||
unaffected_earnings_xml_ref = self.env.ref('account.data_unaffected_earnings')
|
||||
unaffected_earnings_line = True # used to make sure that we add the unaffected earning initial balance only once
|
||||
@@ -196,7 +209,8 @@ class AccountFrFec(models.TransientModel):
|
||||
else:
|
||||
listrow[11] = '0,00'
|
||||
listrow[12] = str(-listrow_amount).replace('.', ',')
|
||||
w.writerow(listrow)
|
||||
rows_to_write.append(listrow)
|
||||
|
||||
#if the unaffected earnings account wasn't in the selection yet: add it manually
|
||||
if (not unaffected_earnings_line
|
||||
and unaffected_earnings_results
|
||||
@@ -207,7 +221,7 @@ class AccountFrFec(models.TransientModel):
|
||||
if unaffected_earnings_account:
|
||||
unaffected_earnings_results[4] = unaffected_earnings_account.code
|
||||
unaffected_earnings_results[5] = unaffected_earnings_account.name
|
||||
w.writerow(unaffected_earnings_results)
|
||||
rows_to_write.append(unaffected_earnings_results)
|
||||
|
||||
# INITIAL BALANCE - receivable/payable
|
||||
sql_query = '''
|
||||
@@ -265,7 +279,7 @@ class AccountFrFec(models.TransientModel):
|
||||
for row in self._cr.fetchall():
|
||||
listrow = list(row)
|
||||
account_id = listrow.pop()
|
||||
w.writerow(listrow)
|
||||
rows_to_write.append(listrow)
|
||||
|
||||
# LINES
|
||||
sql_query = '''
|
||||
@@ -330,20 +344,19 @@ class AccountFrFec(models.TransientModel):
|
||||
sql_query, (self.date_from, self.date_to, company.id))
|
||||
|
||||
for row in self._cr.fetchall():
|
||||
w.writerow(list(row))
|
||||
rows_to_write.append(list(row))
|
||||
|
||||
siren = company.vat[4:13]
|
||||
fecvalue = self._csv_write_rows(rows_to_write)
|
||||
end_date = self.date_to.replace('-', '')
|
||||
suffix = ''
|
||||
if self.export_type == "nonofficial":
|
||||
suffix = '-NONOFFICIAL'
|
||||
fecvalue = fecfile.getvalue()
|
||||
|
||||
self.write({
|
||||
'fec_data': base64.encodestring(fecvalue),
|
||||
# Filename = <siren>FECYYYYMMDD where YYYMMDD is the closing date
|
||||
'filename': '%sFEC%s%s.csv' % (siren, end_date, suffix),
|
||||
'filename': '%sFEC%s%s.csv' % (company_legal_data['siren'], end_date, suffix),
|
||||
})
|
||||
fecfile.close()
|
||||
|
||||
action = {
|
||||
'name': 'FEC',
|
||||
@@ -352,3 +365,29 @@ class AccountFrFec(models.TransientModel):
|
||||
'target': 'self',
|
||||
}
|
||||
return action
|
||||
|
||||
def _csv_write_rows(self, rows, lineterminator=u'\r\n'):
|
||||
"""
|
||||
Write FEC rows into a file
|
||||
It seems that Bercy's bureaucracy is not too happy about the
|
||||
empty new line at the End Of File.
|
||||
|
||||
@param {list(list)} rows: the list of rows. Each row is a list of strings
|
||||
@param {unicode string} [optional] lineterminator: effective line terminator
|
||||
Has nothing to do with the csv writer parameter
|
||||
The last line written won't be terminated with it
|
||||
|
||||
@return the value of the file
|
||||
"""
|
||||
fecfile = io.BytesIO()
|
||||
writer = pycompat.csv_writer(fecfile, delimiter='|', lineterminator='')
|
||||
|
||||
rows_length = len(rows)
|
||||
for i, row in enumerate(rows):
|
||||
if not i == rows_length - 1:
|
||||
row.append(lineterminator)
|
||||
writer.writerow(row)
|
||||
|
||||
fecvalue = fecfile.getvalue()
|
||||
fecfile.close()
|
||||
return fecvalue
|
||||
|
||||
@@ -84,8 +84,8 @@ class MailActivity(models.Model):
|
||||
activity_type_id = fields.Many2one(
|
||||
'mail.activity.type', 'Activity',
|
||||
domain="['|', ('res_model_id', '=', False), ('res_model_id', '=', res_model_id)]", ondelete='restrict')
|
||||
activity_category = fields.Selection(related='activity_type_id.category')
|
||||
icon = fields.Char('Icon', related='activity_type_id.icon')
|
||||
activity_category = fields.Selection(related='activity_type_id.category', readonly=True)
|
||||
icon = fields.Char('Icon', related='activity_type_id.icon', readonly=True)
|
||||
summary = fields.Char('Summary')
|
||||
note = fields.Html('Note')
|
||||
feedback = fields.Html('Feedback')
|
||||
@@ -103,8 +103,8 @@ class MailActivity(models.Model):
|
||||
('today', 'Today'),
|
||||
('planned', 'Planned')], 'State',
|
||||
compute='_compute_state')
|
||||
recommended_activity_type_id = fields.Many2one('mail.activity.type', string="Recommended Activity Type")
|
||||
previous_activity_type_id = fields.Many2one('mail.activity.type', string='Previous Activity Type')
|
||||
recommended_activity_type_id = fields.Many2one('mail.activity.type', string="Recommended Activity Type", readonly=True)
|
||||
previous_activity_type_id = fields.Many2one('mail.activity.type', string='Previous Activity Type', readonly=True)
|
||||
has_recommended_activities = fields.Boolean(
|
||||
'Next activities available',
|
||||
compute='_compute_has_recommended_activities',
|
||||
|
||||
@@ -1477,6 +1477,8 @@ td {
|
||||
body {
|
||||
margin: 0;
|
||||
color: white !important;
|
||||
/* avoid black background if backgrounds are printed */
|
||||
background: initial;
|
||||
}
|
||||
.oe_leftbar,
|
||||
.oe_loading,
|
||||
|
||||
@@ -35,7 +35,7 @@ class PortalWizard(models.TransientModel):
|
||||
contact_ids = set()
|
||||
user_changes = []
|
||||
for partner in self.env['res.partner'].sudo().browse(partner_ids):
|
||||
contact_partners = partner.child_ids or [partner]
|
||||
contact_partners = partner.child_ids | partner
|
||||
for contact in contact_partners:
|
||||
# make sure that each contact appears at most once in the list
|
||||
if contact.id not in contact_ids:
|
||||
|
||||
@@ -241,7 +241,11 @@ class Pricelist(models.Model):
|
||||
break
|
||||
# Final price conversion into pricelist currency
|
||||
if suitable_rule and suitable_rule.compute_price != 'fixed' and suitable_rule.base != 'pricelist':
|
||||
price = product.currency_id.compute(price, self.currency_id, round=False)
|
||||
if suitable_rule.base == 'standard_price':
|
||||
# The cost of the product is always in the company currency
|
||||
price = product.cost_currency_id.compute(price, self.currency_id, round=False)
|
||||
else:
|
||||
price = product.currency_id.compute(price, self.currency_id, round=False)
|
||||
|
||||
results[product.id] = (price, suitable_rule and suitable_rule.id or False)
|
||||
|
||||
|
||||
@@ -62,6 +62,8 @@ class ProductTemplate(models.Model):
|
||||
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', 'Currency', compute='_compute_currency_id')
|
||||
cost_currency_id = fields.Many2one(
|
||||
'res.currency', 'Cost Currency', compute='_compute_cost_currency_id')
|
||||
|
||||
# price fields
|
||||
price = fields.Float(
|
||||
@@ -165,6 +167,10 @@ class ProductTemplate(models.Model):
|
||||
for template in self:
|
||||
template.currency_id = template.company_id.sudo().currency_id.id or main_company.currency_id.id
|
||||
|
||||
def _compute_cost_currency_id(self):
|
||||
for template in self:
|
||||
template.cost_currency_id = self.env.user.company_id.currency_id.id
|
||||
|
||||
@api.multi
|
||||
def _compute_template_price(self):
|
||||
prices = {}
|
||||
|
||||
@@ -51,13 +51,14 @@
|
||||
<field name="list_price" string="Sales Price" widget='monetary' options="{'currency_field': 'currency_id', 'field_digits': True}"/>
|
||||
<label for="standard_price" groups="base.group_user" attrs="{'invisible': [('product_variant_count', '>', 1), ('is_product_variant', '=', False)]}"/>
|
||||
<div name="standard_price_uom" groups="base.group_user" attrs="{'invisible': [('product_variant_count', '>', 1), ('is_product_variant', '=', False)]}">
|
||||
<field name="standard_price" widget='monetary' options="{'currency_field': 'currency_id'}"/>
|
||||
<field name="standard_price" widget='monetary' options="{'currency_field': 'cost_currency_id'}"/>
|
||||
</div>
|
||||
<field name="company_id" groups="base.group_multi_company"
|
||||
options="{'no_create': True}"/>
|
||||
<field name="uom_id" groups="uom.group_uom" options="{'no_create': True}"/>
|
||||
<field name="uom_po_id" groups="uom.group_uom" options="{'no_create': True}"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="cost_currency_id" invisible="1"/>
|
||||
<field name="product_variant_id" invisible="1"/>
|
||||
</group>
|
||||
<group name="group_lots_and_weight" attrs="{'invisible':[('type', 'not in', ['product', 'consu'])]}">
|
||||
|
||||
@@ -14,14 +14,20 @@ class ResPartner(models.Model):
|
||||
sale_warn_msg = fields.Text('Message for Sales Order')
|
||||
|
||||
def _compute_sale_order_count(self):
|
||||
sale_data = self.env['sale.order'].read_group(domain=[('partner_id', 'child_of', self.ids)],
|
||||
fields=['partner_id'], groupby=['partner_id'])
|
||||
mapped_data = dict([(m['partner_id'][0], m['partner_id_count']) for m in sale_data])
|
||||
for partner in self:
|
||||
# let's obtain the partner id and all its child ids
|
||||
partner_ids = [partner.id] + partner.child_ids.ids
|
||||
# then we can sum for all the partner's child
|
||||
partner.sale_order_count = sum(mapped_data.get(child, 0) for child in partner_ids)
|
||||
# retrieve all children partners and prefetch 'parent_id' on them
|
||||
all_partners = self.search([('id', 'child_of', self.ids)])
|
||||
all_partners.read(['parent_id'])
|
||||
|
||||
sale_order_groups = self.env['sale.order'].read_group(
|
||||
domain=[('partner_id', 'in', all_partners.ids)],
|
||||
fields=['partner_id'], groupby=['partner_id']
|
||||
)
|
||||
for group in sale_order_groups:
|
||||
partner = self.browse(group['partner_id'][0])
|
||||
while partner:
|
||||
if partner in self:
|
||||
partner.sale_order_count += group['partner_id_count']
|
||||
partner = partner.parent_id
|
||||
|
||||
def can_edit_vat(self):
|
||||
can_edit_vat = super(ResPartner, self).can_edit_vat()
|
||||
|
||||
@@ -1151,7 +1151,8 @@ class StockMove(models.Model):
|
||||
moves_todo.mapped('move_dest_ids')._action_assign()
|
||||
|
||||
# We don't want to create back order for scrap moves
|
||||
if all(move_todo.scrapped for move_todo in moves_todo):
|
||||
# Replace by a kwarg in master
|
||||
if self.env.context.get('is_scrap'):
|
||||
return moves_todo
|
||||
|
||||
if picking:
|
||||
|
||||
@@ -703,7 +703,7 @@ class Picking(models.Model):
|
||||
# If no lots when needed, raise error
|
||||
picking_type = self.picking_type_id
|
||||
precision_digits = self.env['decimal.precision'].precision_get('Product Unit of Measure')
|
||||
no_quantities_done = all(float_is_zero(move_line.qty_done, precision_digits=precision_digits) for move_line in self.move_line_ids)
|
||||
no_quantities_done = all(float_is_zero(move_line.qty_done, precision_digits=precision_digits) for move_line in self.move_line_ids.filtered(lambda m: m.state not in ('done', 'cancel')))
|
||||
no_reserved_quantities = all(float_is_zero(move_line.product_qty, precision_rounding=move_line.product_uom_id.rounding) for move_line in self.move_line_ids)
|
||||
if no_reserved_quantities and no_quantities_done:
|
||||
raise UserError(_('You cannot validate a transfer if no quantites are reserved nor done. To force the transfer, switch in edit more and encode the done quantities.'))
|
||||
|
||||
@@ -105,7 +105,8 @@ class StockScrap(models.Model):
|
||||
def do_scrap(self):
|
||||
for scrap in self:
|
||||
move = self.env['stock.move'].create(scrap._prepare_move_values())
|
||||
move._action_done()
|
||||
# master: replace context by cancel_backorder
|
||||
move.with_context(is_scrap=True)._action_done()
|
||||
scrap.write({'move_id': move.id, 'state': 'done'})
|
||||
return True
|
||||
|
||||
|
||||
@@ -3383,6 +3383,15 @@ class StockMove(TransactionCase):
|
||||
})
|
||||
picking.action_confirm()
|
||||
picking.action_assign()
|
||||
|
||||
scrap = self.env['stock.scrap'].create({
|
||||
'picking_id': picking.id,
|
||||
'product_id': self.product1.id,
|
||||
'product_uom_id': self.uom_unit.id,
|
||||
'scrap_qty': 5.0,
|
||||
})
|
||||
scrap.do_scrap()
|
||||
|
||||
# No products are reserved on the move of 10, click on `button_validate`.
|
||||
with self.assertRaises(UserError):
|
||||
picking.button_validate()
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<field name="standard_price" position="replace">
|
||||
<field name="standard_price"
|
||||
attrs="{'readonly':[('valuation','=','real_time')]}"
|
||||
widget='monetary' options="{'currency_field': 'currency_id', 'field_digits': True}"
|
||||
widget='monetary' options="{'currency_field': 'cost_currency_id', 'field_digits': True}"
|
||||
class="pull-left"/>
|
||||
<div name="update_cost_price" class="col-md-2">
|
||||
<button string="Update Cost" type="action"
|
||||
|
||||
@@ -77,7 +77,8 @@
|
||||
<field name="qty_at_date"/>
|
||||
<field name="uom_id" groups="uom.group_uom"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="stock_value" sum="Stock Valuation" widget="monetary"/>
|
||||
<field name="cost_currency_id" invisible="1"/>
|
||||
<field name="stock_value" sum="Stock Valuation" widget="monetary" options="{'currency_field': 'cost_currency_id'}"/>
|
||||
<field name="cost_method" invisible="1"/>
|
||||
<button name="action_valuation_at_date_details" type="object" icon="fa-info-circle" attrs="{'invisible': [('cost_method', '!=', 'fifo')]}" />
|
||||
</tree>
|
||||
|
||||
@@ -194,7 +194,7 @@ var FieldMany2One = AbstractField.extend({
|
||||
_bindAutoComplete: function () {
|
||||
var self = this;
|
||||
// avoid ignoring autocomplete="off" by obfuscating placeholder, see #30439
|
||||
if (this.$input.attr('placeholder')) {
|
||||
if ($.browser.chrome && this.$input.attr('placeholder')) {
|
||||
this.$input.attr('placeholder', function (index, val) {
|
||||
return val.split('').join('\ufeff');
|
||||
});
|
||||
|
||||
@@ -623,14 +623,14 @@ var PivotModel = AbstractModel.extend({
|
||||
});
|
||||
|
||||
var index = 0;
|
||||
var rowGroupBys = !_.isEmpty(this.data.groupedBy) ? this.data.groupedBy : this.initialRowGroupBys;
|
||||
var rowGroupBys = !_.isEmpty(this.data.groupedBy) ? this.data.groupedBy : this.initialRowGroupBys.slice();
|
||||
this.data.groupedBy = rowGroupBys;
|
||||
var colGroupBys = this.data.colGroupBys;
|
||||
var datapt, row, col, attrs, cell_value;
|
||||
var main_row_header, main_col_header;
|
||||
var groupBys;
|
||||
var m;
|
||||
|
||||
|
||||
for (var i = 0; i < rowGroupBys.length + 1; i++) {
|
||||
for (var j = 0; j < colGroupBys.length + 1; j++) {
|
||||
for (var k = 0; k < data[index].length; k++) {
|
||||
@@ -757,8 +757,8 @@ var PivotModel = AbstractModel.extend({
|
||||
*/
|
||||
_updateMainGroupBys: function (old, main) {
|
||||
var new_groupby_length = this._getHeaderDepth(main.root) - 1;
|
||||
var new_groupby_list = old.root.groupbys.slice(0, new_groupby_length);
|
||||
main.root.groupbys = new_groupby_list;
|
||||
main.root.groupbys = old.root.groupbys;
|
||||
main.root.groupbys.splice(new_groupby_length);
|
||||
},
|
||||
/**
|
||||
* @param {Object} old_tree
|
||||
|
||||
@@ -994,6 +994,48 @@ QUnit.module('Views', {
|
||||
pivot.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('Reload, group by columns, reload', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var pivot = createView({
|
||||
View: PivotView,
|
||||
model: "partner",
|
||||
data: this.data,
|
||||
arch: '<pivot/>',
|
||||
});
|
||||
|
||||
// Set a column groupby
|
||||
pivot.$('thead .o_pivot_header_cell_closed').click();
|
||||
pivot.$('.o_field_selection li[data-field=customer] a').click();
|
||||
|
||||
// Set a domain
|
||||
pivot.update({domain: [['product_id', '=', 41]]});
|
||||
|
||||
var expectedContext = {pivot_column_groupby: ['customer'],
|
||||
pivot_measures: ['__count'],
|
||||
pivot_row_groupby: []};
|
||||
|
||||
// Check that column groupbys were not lost
|
||||
assert.deepEqual(pivot.getContext(), expectedContext,
|
||||
'Column groupby not lost after first reload');
|
||||
|
||||
// Set a column groupby
|
||||
pivot.$('thead .o_pivot_header_cell_closed').click();
|
||||
pivot.$('.o_field_selection li[data-field=product_id] a').click();
|
||||
|
||||
// Set a domain
|
||||
pivot.update({domain: [['product_id', '=', 37]]});
|
||||
|
||||
var expectedContext = {pivot_column_groupby: ['customer', 'product_id'],
|
||||
pivot_measures: ['__count'],
|
||||
pivot_row_groupby: []};
|
||||
|
||||
assert.deepEqual(pivot.getContext(), expectedContext,
|
||||
'Column groupby not lost after second reload');
|
||||
|
||||
pivot.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('correctly uses pivot_ keys from the context', function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
|
||||
@@ -189,5 +189,6 @@ class IrUiView(models.Model):
|
||||
``bundles=True`` returns also the asset bundles
|
||||
"""
|
||||
user_groups = set(self.env.user.groups_id)
|
||||
views = self.with_context(active_test=False)._views_get(key, bundles=bundles)
|
||||
View = self.with_context(active_test=False, lang=None)
|
||||
views = View._views_get(key, bundles=bundles)
|
||||
return views.filtered(lambda v: not v.groups_id or len(user_groups.intersection(v.groups_id)))
|
||||
|
||||
@@ -84,6 +84,16 @@ var SnippetEditor = Widget.extend({
|
||||
});
|
||||
}
|
||||
|
||||
this.$target.on('transitionstart.snippet_editor, animationstart.snippet_editor', function () {
|
||||
self._targetIsAnimated = true;
|
||||
});
|
||||
this.$target.on('transitionend.snippet_editor, animationend.snippet_editor', function () {
|
||||
self._targetIsAnimated = false;
|
||||
if (self.$el.is('.oe_active')) {
|
||||
self.cover();
|
||||
}
|
||||
});
|
||||
|
||||
return $.when.apply($, defs);
|
||||
},
|
||||
/**
|
||||
@@ -92,6 +102,7 @@ var SnippetEditor = Widget.extend({
|
||||
destroy: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.$target.removeData('snippet-editor');
|
||||
this.$target.off('.snippet_editor');
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -123,6 +134,11 @@ var SnippetEditor = Widget.extend({
|
||||
* Makes the editor overlay cover the associated snippet.
|
||||
*/
|
||||
cover: function () {
|
||||
if (this._targetIsAnimated) {
|
||||
// Do not cover a target being animated, it will be covered once the
|
||||
// animation is completed.
|
||||
return;
|
||||
}
|
||||
var offset = this.$target.offset();
|
||||
var manipulatorOffset = this.$el.parent().offset();
|
||||
offset.top -= manipulatorOffset.top;
|
||||
|
||||
@@ -289,13 +289,11 @@ var SnippetOption = Widget.extend({
|
||||
.addClass('active');
|
||||
|
||||
_processSelectClassElements(this.$el);
|
||||
_.each(this.$el.find('.dropdown-menu'), function (group) {
|
||||
_processSelectClassElements($(group).children());
|
||||
});
|
||||
|
||||
function _processSelectClassElements($elements) {
|
||||
function _processSelectClassElements($el) {
|
||||
var maxNbClasses = -1;
|
||||
$elements.filter('[data-select-class]')
|
||||
$el.find('[data-select-class]')
|
||||
.addBack('[data-select-class]')
|
||||
.removeClass('active')
|
||||
.filter(function () {
|
||||
var className = $(this).data('selectClass');
|
||||
|
||||
@@ -4,3 +4,19 @@
|
||||
from . import controllers
|
||||
from . import models
|
||||
from . import wizard
|
||||
|
||||
import odoo
|
||||
from odoo import api, SUPERUSER_ID
|
||||
from functools import partial
|
||||
|
||||
|
||||
def uninstall_hook(cr, registry):
|
||||
def rem_website_id_null(dbname):
|
||||
db_registry = odoo.modules.registry.Registry.new(dbname)
|
||||
with api.Environment.manage(), db_registry.cursor() as cr:
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
env['ir.model.fields'].search([
|
||||
('name', '=', 'website_id'),
|
||||
('model', '=', 'res.config.settings'),
|
||||
]).unlink()
|
||||
cr.after('commit', partial(rem_website_id_null, cr.dbname))
|
||||
|
||||
@@ -34,4 +34,5 @@
|
||||
],
|
||||
'qweb': ['static/src/xml/website.backend.xml'],
|
||||
'application': True,
|
||||
'uninstall_hook': 'uninstall_hook',
|
||||
}
|
||||
|
||||
@@ -11,4 +11,3 @@ from . import res_company
|
||||
from . import res_partner
|
||||
from . import res_config_settings
|
||||
from . import ir_model_fields
|
||||
from . import ir_model
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details
|
||||
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
# This is a nasty hack, targeted for V11 only
|
||||
class IrModel(models.Model):
|
||||
_inherit = 'ir.model'
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
self.env.cr.execute("DELETE FROM ir_model_fields WHERE name='website_id'")
|
||||
self.env.cr.execute("DELETE FROM res_config_settings WHERE website_id IS NOT NULL")
|
||||
return super(IrModel, self).unlink()
|
||||
@@ -150,5 +150,25 @@ tour.register('rte_translator', {
|
||||
}, {
|
||||
content: "check bis: placeholder translation",
|
||||
trigger: 'input[placeholder="test french placeholder"]',
|
||||
}, {
|
||||
content: "Open customize menu",
|
||||
trigger: "#customize-menu > .dropdown-toggle",
|
||||
}, {
|
||||
content: "Open HTML editor",
|
||||
trigger: "[data-action='ace']",
|
||||
}, {
|
||||
content: "Check that the editor is not showing translated content (1)",
|
||||
trigger: '.ace_text-layer .ace_line:contains("an HTML")',
|
||||
run: function (actions) {
|
||||
var lineEscapedText = $(this.$anchor.text()).text();
|
||||
if (lineEscapedText !== "<b></b> is an HTML tag & is empty") {
|
||||
console.error('The HTML editor should display the correct untranslated content');
|
||||
$('body').addClass('rte_translator_error');
|
||||
}
|
||||
},
|
||||
}, {
|
||||
content: "Check that the editor is not showing translated content (2)",
|
||||
trigger: 'body:not(.rte_translator_error)',
|
||||
run: function () {},
|
||||
}]);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import product
|
||||
from . import product_pricelist
|
||||
from . import sale_order
|
||||
from . import website
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import _, api, models
|
||||
|
||||
class PricelistItem(models.Model):
|
||||
_inherit = "product.pricelist.item"
|
||||
|
||||
@api.onchange('applied_on', 'product_id', 'product_tmpl_id', 'min_quantity')
|
||||
def _onchange_event_sale_warning(self):
|
||||
if self.min_quantity > 0:
|
||||
msg = ''
|
||||
if self.applied_on == '3_global' or self.applied_on == '2_product_category':
|
||||
msg = _("A pricelist item with a positive min. quantity will not be applied to the event tickets products.")
|
||||
elif ((self.applied_on == '1_product' and self.product_tmpl_id.event_ok) or
|
||||
(self.applied_on == '0_product_variant' and self.product_id.event_ok)):
|
||||
msg = _("A pricelist item with a positive min. quantity cannot be applied to this event tickets product.")
|
||||
if msg:
|
||||
return {'warning':
|
||||
{
|
||||
'title': _("Warning"),
|
||||
'message': msg
|
||||
}
|
||||
}
|
||||
@@ -117,7 +117,10 @@ class SaleQuoteOption(models.Model):
|
||||
product = self.product_id
|
||||
self.price_unit = product.list_price
|
||||
self.website_description = product.product_tmpl_id.quote_description
|
||||
self.name = product.name
|
||||
name = product.name
|
||||
if self.product_id.description_sale:
|
||||
name += '\n' + self.product_id.description_sale
|
||||
self.name = name
|
||||
self.uom_id = product.uom_id
|
||||
domain = {'uom_id': [('category_id', '=', self.product_id.uom_id.category_id.id)]}
|
||||
return {'domain': domain}
|
||||
|
||||
@@ -14,3 +14,4 @@ Damián Soriano ds@adhoc.com.ar https://github.com/damiansoriano
|
||||
Juan José Scarafía jjs@adhoc.com.ar https://github.com/jjscarafia
|
||||
Nicolás Mac Rouillon nmr@adhoc.com.ar https://github.com/nicomacr
|
||||
Katherine Zaoral kz@adhoc.com.ar https://github.com/zaoral
|
||||
Valentino Defelice vd@adhoc.com.ar https://github.com/ValentinoDefelice
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
Turkey, 2017-12-05
|
||||
|
||||
Projet Yazilim Ve Danismanlik AS agrees to the terms of the Odoo Corporate Contributor License
|
||||
Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Can Tecim can.tecim@gmail.com https://github.com/cantecim
|
||||
|
||||
List of contributors:
|
||||
|
||||
Can Tecim can.tecim@gmail.com https://github.com/cantecim
|
||||
Can Tecim dev@projetgrup.com https://github.com/devprojetgrup
|
||||
Selahattin Ünlü selahattin.unlu@yandex.com https://github.com/selahattinunlu
|
||||
Abdurrahman Işık isikabdurrahman@yahoo.com https://github.com/abdurrahman
|
||||
@@ -21,6 +21,8 @@ Julien Jezequel-Breard julien.jezequel-breard@savoirfairelinux.com https://githu
|
||||
Quentin Lavallée-Bourdeau quentin.lavallee@savoirfairelinux.com https://github.com/qtiplb
|
||||
Rim Ben Dhaou rim.bendhaou@savoirfairelinux.com https://github.com/rimbendhaou
|
||||
Yasmine El Mrini yasmine.elmrini@savoirfairelinux.com https://github.com/yasmineelmrini
|
||||
Luis Garcia Ruiz luis.garcia@savoirfairelinux.com https://github.com/eilst
|
||||
Dimitar Apostolov dimitar.apostolov@savoirfairelinux.com https://github.com/dapostolov-sfl
|
||||
Jérome Boisvert-Chouinard jerome.boisvertchouinard@savoirfairelinux.com https://github.com/jbchouinard (up to 2017-10-17)
|
||||
Julie Moussu julie.mousse@savoirfairelinux.com https://github.com/JulieSFL (up to 2017-10-17)
|
||||
Pierre Lamarche pierre.lamarche@savoirfairelinux.com https://github.com/plamarche (up to 2017-10-17)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
Turkey, 2019-02-18
|
||||
|
||||
I hereby agree to the terms of the Odoo Individual Contributor License
|
||||
Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Can Tecim can.tecim@gmail.com https://github.com/cantecim
|
||||
@@ -0,0 +1,11 @@
|
||||
Bangladesh, 16 February, 2019
|
||||
|
||||
I hereby agree to the terms of the Odoo Individual Contributor License
|
||||
Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Abu Zafar azmikbal@gmail.com https://github.com/MjAbuz
|
||||
@@ -181,6 +181,9 @@ class IrModel(models.Model):
|
||||
# prevent screwing up fields that depend on these models' fields
|
||||
model.field_id._prepare_update()
|
||||
|
||||
# delete fields whose comodel is being removed
|
||||
self.env['ir.model.fields'].search([('relation', 'in', self.mapped('model'))]).unlink()
|
||||
|
||||
self._drop_table()
|
||||
res = super(IrModel, self).unlink()
|
||||
|
||||
|
||||
@@ -96,6 +96,8 @@ class IrRule(models.Model):
|
||||
group_domains.append(dom)
|
||||
|
||||
# combine global domains and group domains
|
||||
if not group_domains:
|
||||
return expression.AND(global_domains)
|
||||
return expression.AND(global_domains + [expression.OR(group_domains)])
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -31,6 +31,8 @@ try:
|
||||
except ImportError:
|
||||
astor = None
|
||||
|
||||
from odoo.tools.parse_version import parse_version
|
||||
|
||||
unsafe_eval = eval
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -176,7 +178,7 @@ class QWebException(Exception):
|
||||
return str(self)
|
||||
|
||||
# Avoid DeprecationWarning while still remaining compatible with werkzeug pre-0.9
|
||||
escape = (lambda text: _escape(text, quote=True)) if getattr(werkzeug, '__version__', '0.0') < '0.9.0' else _escape
|
||||
escape = (lambda text: _escape(text, quote=True)) if parse_version(getattr(werkzeug, '__version__', '0.0')) < parse_version('0.9.0') else _escape
|
||||
|
||||
def foreach_iterator(base_ctx, enum, name):
|
||||
ctx = base_ctx.copy()
|
||||
@@ -1457,7 +1459,7 @@ class QWeb(object):
|
||||
)
|
||||
|
||||
if call_options:
|
||||
# update this dict with the content of `t-call-options`
|
||||
# update this dict with the content of `t-call-options`
|
||||
content.extend([
|
||||
# options_.update(template options)
|
||||
ast.Expr(ast.Call(
|
||||
@@ -1468,7 +1470,56 @@ class QWeb(object):
|
||||
),
|
||||
args=[self._compile_expr(call_options)],
|
||||
keywords=[], starargs=None, kwargs=None
|
||||
))
|
||||
)),
|
||||
|
||||
# if options.get('lang') != options_.get('lang'):
|
||||
# self = self.with_context(lang=options.get('lang'))
|
||||
ast.If(
|
||||
test=ast.Compare(
|
||||
left=ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='options', ctx=ast.Load()),
|
||||
attr='get',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[ast.Str("lang")], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
),
|
||||
ops=[ast.NotEq()],
|
||||
comparators=[ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id=name_options, ctx=ast.Load()),
|
||||
attr='get',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[ast.Str("lang")], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
)]
|
||||
),
|
||||
body=[
|
||||
ast.Assign(
|
||||
targets=[ast.Name(id='self', ctx=ast.Store())],
|
||||
value=ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='self', ctx=ast.Load()),
|
||||
attr='with_context',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[],
|
||||
keywords=[ast.keyword('lang', ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id=name_options, ctx=ast.Load()),
|
||||
attr='get',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[ast.Str("lang")], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
))],
|
||||
starargs=None, kwargs=None
|
||||
)
|
||||
)],
|
||||
orelse=[],
|
||||
)
|
||||
])
|
||||
|
||||
if nsmap:
|
||||
|
||||
@@ -422,20 +422,19 @@ class Users(models.Model):
|
||||
@tools.ormcache('self._uid')
|
||||
def context_get(self):
|
||||
user = self.env.user
|
||||
result = {}
|
||||
for k in self._fields:
|
||||
if k.startswith('context_'):
|
||||
context_key = k[8:]
|
||||
elif k in ['lang', 'tz']:
|
||||
context_key = k
|
||||
else:
|
||||
context_key = False
|
||||
if context_key:
|
||||
res = getattr(user, k) or False
|
||||
if isinstance(res, models.BaseModel):
|
||||
res = res.id
|
||||
result[context_key] = res or False
|
||||
return result
|
||||
# determine field names to read
|
||||
name_to_key = {
|
||||
name: name[8:] if name.startswith('context_') else name
|
||||
for name in self._fields
|
||||
if name.startswith('context_') or name in ('lang', 'tz')
|
||||
}
|
||||
# use read() to not read other fields: this must work while modifying
|
||||
# the schema of models res.users or res.partner
|
||||
values = user.read(list(name_to_key), load=False)[0]
|
||||
return {
|
||||
key: values[name]
|
||||
for name, key in name_to_key.items()
|
||||
}
|
||||
|
||||
@api.model
|
||||
@api.returns('ir.actions.act_window', lambda record: record.id)
|
||||
|
||||
@@ -640,6 +640,36 @@ class TestExpression(TransactionCase):
|
||||
'&', ('foo', '=', 2), ('bar', '=', 2)]
|
||||
self.assertEqual(expression.OR([d1, d2]), expected)
|
||||
|
||||
def test_proper_combine_unit_leaves(self):
|
||||
# test that unit leaves (TRUE_LEAF, FALSE_LEAF) are properly handled in specific cases
|
||||
false = expression.FALSE_DOMAIN
|
||||
true = expression.TRUE_DOMAIN
|
||||
normal = [('foo', '=', 'bar')]
|
||||
# OR with single FALSE_LEAF
|
||||
expr = expression.OR([false])
|
||||
self.assertEqual(expr, false)
|
||||
# OR with multiple FALSE_LEAF
|
||||
expr = expression.OR([false, false])
|
||||
self.assertEqual(expr, false)
|
||||
# OR with FALSE_LEAF and a normal leaf
|
||||
expr = expression.OR([false, normal])
|
||||
self.assertEqual(expr, normal)
|
||||
# OR with AND of single TRUE_LEAF and normal leaf
|
||||
expr = expression.OR([expression.AND([true]), normal])
|
||||
self.assertEqual(expr, true)
|
||||
# AND with single TRUE_LEAF
|
||||
expr = expression.AND([true])
|
||||
self.assertEqual(expr, true)
|
||||
# AND with multiple TRUE_LEAF
|
||||
expr = expression.AND([true, true])
|
||||
self.assertEqual(expr, true)
|
||||
# AND with TRUE_LEAF and normal leaves
|
||||
expr = expression.AND([true, normal])
|
||||
self.assertEqual(expr, normal)
|
||||
# AND with OR with single FALSE_LEAF and normal leaf
|
||||
expr = expression.AND([expression.OR([false]), normal])
|
||||
self.assertEqual(expr, false)
|
||||
|
||||
|
||||
class TestAutoJoin(TransactionCase):
|
||||
|
||||
|
||||
@@ -489,6 +489,47 @@ class TestQWebNS(TransactionCase):
|
||||
with self.assertRaises(QWebException, msg=error_msg):
|
||||
view1.render()
|
||||
|
||||
def test_render_t_call_propagates_t_lang(self):
|
||||
current_lang = 'en_US'
|
||||
other_lang = 'fr_FR'
|
||||
|
||||
self.env['res.lang'].load_lang(lang=other_lang)
|
||||
|
||||
self.env['res.lang'].search([('code', '=', other_lang)], limit=1).write({
|
||||
'active': True,
|
||||
'decimal_point': '*',
|
||||
'thousands_sep': '/'
|
||||
})
|
||||
|
||||
view1 = self.env['ir.ui.view'].create({
|
||||
'name': "callee",
|
||||
'type': 'qweb',
|
||||
'arch': u"""
|
||||
<t t-name="base.callee">
|
||||
<t t-esc="9000000.00" t-options="{'widget': 'float', 'precision': 2}" />
|
||||
</t>
|
||||
"""
|
||||
})
|
||||
self.env['ir.model.data'].create({
|
||||
'name': 'callee',
|
||||
'model': 'ir.ui.view',
|
||||
'module': 'base',
|
||||
'res_id': view1.id,
|
||||
})
|
||||
|
||||
view2 = self.env['ir.ui.view'].create({
|
||||
'name': "calling",
|
||||
'type': 'qweb',
|
||||
'arch': u"""
|
||||
<t t-name="base.calling">
|
||||
<t t-call="base.callee" t-lang="'%s'" />
|
||||
</t>
|
||||
""" % other_lang
|
||||
})
|
||||
|
||||
rendered = view2.with_context(lang=current_lang).render().strip()
|
||||
self.assertEqual(rendered, b'9/000/000*00')
|
||||
|
||||
|
||||
from copy import deepcopy
|
||||
class FileSystemLoader(object):
|
||||
|
||||
@@ -866,7 +866,7 @@ class TestViews(ViewCase):
|
||||
# implemeted elsewhere...
|
||||
modifiers_tests()
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view')
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view', 'odoo.models')
|
||||
def test_invalid_field(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
self.View.create({
|
||||
@@ -880,7 +880,7 @@ class TestViews(ViewCase):
|
||||
""",
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view')
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view', 'odoo.models')
|
||||
def test_invalid_subfield(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
self.View.create({
|
||||
@@ -899,7 +899,7 @@ class TestViews(ViewCase):
|
||||
""",
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view')
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view', 'odoo.models')
|
||||
def test_context_in_view(self):
|
||||
arch = """
|
||||
<form string="View">
|
||||
@@ -919,7 +919,7 @@ class TestViews(ViewCase):
|
||||
'arch': arch % '',
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view')
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view', 'odoo.models')
|
||||
def test_context_in_subview(self):
|
||||
arch = """
|
||||
<form string="View">
|
||||
@@ -951,7 +951,7 @@ class TestViews(ViewCase):
|
||||
'arch': arch % ('<field name="model"/>', ''),
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view')
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view', 'odoo.models')
|
||||
def test_context_in_subview_with_parent(self):
|
||||
arch = """
|
||||
<form string="View">
|
||||
@@ -982,7 +982,7 @@ class TestViews(ViewCase):
|
||||
'arch': arch % ('', '<field name="model"/>'),
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view')
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view', 'odoo.models')
|
||||
def test_context_in_subsubview_with_parent(self):
|
||||
arch = """
|
||||
<form string="View">
|
||||
@@ -1024,7 +1024,7 @@ class TestViews(ViewCase):
|
||||
'arch': arch % ('', '', '<field name="model"/>'),
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view')
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view', 'odoo.models')
|
||||
def test_domain_in_view(self):
|
||||
arch = """
|
||||
<form string="View">
|
||||
@@ -1044,7 +1044,7 @@ class TestViews(ViewCase):
|
||||
'arch': arch % '',
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view')
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view', 'odoo.models')
|
||||
def test_domain_in_subview(self):
|
||||
arch = """
|
||||
<form string="View">
|
||||
@@ -1075,7 +1075,7 @@ class TestViews(ViewCase):
|
||||
'arch': arch % ('<field name="model"/>', ''),
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view')
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view', 'odoo.models')
|
||||
def test_domain_in_subview_with_parent(self):
|
||||
arch = """
|
||||
<form string="View">
|
||||
@@ -1106,7 +1106,7 @@ class TestViews(ViewCase):
|
||||
'arch': arch % ('', '<field name="model"/>'),
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view')
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view', 'odoo.models')
|
||||
def test_domain_on_field_in_view(self):
|
||||
field = self.env['ir.ui.view']._fields['inherit_id']
|
||||
self.patch(field, 'domain', "[('model', '=', model)]")
|
||||
@@ -1129,7 +1129,7 @@ class TestViews(ViewCase):
|
||||
'arch': arch % '',
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view')
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view', 'odoo.models')
|
||||
def test_domain_on_field_in_subview(self):
|
||||
field = self.env['ir.ui.view']._fields['inherit_id']
|
||||
self.patch(field, 'domain', "[('model', '=', model)]")
|
||||
@@ -1163,7 +1163,7 @@ class TestViews(ViewCase):
|
||||
'arch': arch % ('<field name="model"/>', ''),
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view')
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view', 'odoo.models')
|
||||
def test_domain_on_field_in_subview_with_parent(self):
|
||||
field = self.env['ir.ui.view']._fields['inherit_id']
|
||||
self.patch(field, 'domain', "[('model', '=', parent.model)]")
|
||||
@@ -1197,7 +1197,7 @@ class TestViews(ViewCase):
|
||||
'arch': arch % ('', '<field name="model"/>'),
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view')
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view', 'odoo.models')
|
||||
def test_domain_on_field_in_noneditable_subview(self):
|
||||
field = self.env['ir.ui.view']._fields['inherit_id']
|
||||
self.patch(field, 'domain', "[('model', '=', model)]")
|
||||
@@ -1225,7 +1225,7 @@ class TestViews(ViewCase):
|
||||
'arch': arch % ' editable="bottom"',
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view')
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view', 'odoo.models')
|
||||
def test_domain_on_readonly_field_in_view(self):
|
||||
field = self.env['ir.ui.view']._fields['inherit_id']
|
||||
self.patch(field, 'domain', "[('model', '=', model)]")
|
||||
@@ -1255,7 +1255,7 @@ class TestViews(ViewCase):
|
||||
'arch': arch,
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view')
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view', 'odoo.models')
|
||||
def test_domain_on_readonly_field_in_subview(self):
|
||||
field = self.env['ir.ui.view']._fields['inherit_id']
|
||||
self.patch(field, 'domain', "[('model', '=', model)]")
|
||||
@@ -1283,7 +1283,7 @@ class TestViews(ViewCase):
|
||||
'arch': arch % '',
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view')
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view', 'odoo.models')
|
||||
def test_attrs_field(self):
|
||||
arch = """
|
||||
<form string="View">
|
||||
@@ -1304,7 +1304,7 @@ class TestViews(ViewCase):
|
||||
'arch': arch % '',
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view')
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view', 'odoo.models')
|
||||
def test_attrs_subfield(self):
|
||||
arch = """
|
||||
<form string="View">
|
||||
@@ -1336,7 +1336,7 @@ class TestViews(ViewCase):
|
||||
'arch': arch % ('<field name="model"/>', ''),
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view')
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view', 'odoo.models')
|
||||
def test_attrs_subfield_with_parent(self):
|
||||
arch = """
|
||||
<form string="View">
|
||||
|
||||
@@ -157,6 +157,7 @@ class Field(MetaField('DummyField', (object,), {})):
|
||||
|
||||
:param compute_sudo: whether the field should be recomputed as superuser
|
||||
to bypass access rights (boolean, by default ``False``)
|
||||
Note that this has no effects on non-stored computed fields
|
||||
|
||||
The methods given for ``compute``, ``inverse`` and ``search`` are model
|
||||
methods. Their signature is shown in the following example::
|
||||
|
||||
@@ -1041,6 +1041,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
|
||||
except ValidationError as e:
|
||||
raise
|
||||
except Exception as e:
|
||||
_logger.exception('Exception while validating constraint')
|
||||
raise ValidationError("%s\n\n%s" % (_("Error while validating constraint"), tools.ustr(e)))
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -259,7 +259,7 @@ def combine(operator, unit, zero, domains):
|
||||
result += normalize_domain(domain)
|
||||
count += 1
|
||||
result = [operator] * (count - 1) + result
|
||||
return result
|
||||
return result or unit
|
||||
|
||||
|
||||
def AND(domains):
|
||||
|
||||
Reference in New Issue
Block a user