[FIX] P3: list -> iterable builtins (#16811)

In Python 3:

* various builtins and dict methods were changed to return
  view/iterable objects rather than lists
* and the separate Python 2 view/iterable builtins and methods were
  removed altogether

This is problematic when using these items as list (which the happens
repeatedly in Odoo), but more viciously when iterating *multiple times*
over them (which also happens, which I've messed up multiple times while
writing this, and which is a pain to debug even when you've just created
the issue).

Convert all code using these to semantics-matching cross-version
helper functions to get the LCD behaviour between P2 and P3, and
forbid the builtins via lint.

issue #8530
This commit is contained in:
xmo-odoo
2017-05-10 09:39:55 +02:00
committed by GitHub
parent 10eee18a04
commit fffaf735f5
237 changed files with 1160 additions and 953 deletions
+2 -2
View File
@@ -383,7 +383,7 @@ class AccountJournal(models.Model):
account_code_prefix = company.bank_account_code_prefix or ''
else:
account_code_prefix = company.cash_account_code_prefix or company.bank_account_code_prefix or ''
for num in pycompat.range(1, 100):
for num in range(1, 100):
new_code = str(account_code_prefix.ljust(code_digits - 1, '0')) + str(num)
rec = self.env['account.account'].search([('code', '=', new_code), ('company_id', '=', company.id)], limit=1)
if not rec:
@@ -412,7 +412,7 @@ class AccountJournal(models.Model):
if not vals.get('code'):
journal_code_base = (vals['type'] == 'cash' and 'CSH' or 'BNK')
journals = self.env['account.journal'].search([('code', 'like', journal_code_base + '%'), ('company_id', '=', company_id)])
for num in pycompat.range(1, 100):
for num in range(1, 100):
# journal_code has a maximal size of 5, hence we can enforce the boundary num < 100
journal_code = journal_code_base + str(num)
if journal_code not in journals.mapped('code'):
@@ -808,7 +808,7 @@ class AccountBankStatementLine(models.Model):
whose value is the same as described in process_reconciliation except that ids are used instead of recordsets.
"""
AccountMoveLine = self.env['account.move.line']
for st_line, datum in zip(self, data):
for st_line, datum in pycompat.izip(self, data):
payment_aml_rec = AccountMoveLine.browse(datum.get('payment_aml_ids', []))
for aml_dict in datum.get('counterpart_aml_dicts', []):
aml_dict['move_line'] = AccountMoveLine.browse(aml_dict['counterpart_aml_id'])
@@ -879,7 +879,7 @@ class AccountBankStatementLine(models.Model):
for aml_dict in (counterpart_aml_dicts + new_aml_dicts):
if aml_dict.get('tax_ids') and isinstance(aml_dict['tax_ids'][0], pycompat.integer_types):
# Transform the value in the format required for One2many and Many2many fields
aml_dict['tax_ids'] = map(lambda id: (4, id, None), aml_dict['tax_ids'])
aml_dict['tax_ids'] = [(4, id, None) for id in aml_dict['tax_ids']]
# Fully reconciled moves are just linked to the bank statement
total = self.amount
+12 -12
View File
@@ -71,7 +71,7 @@ class AccountInvoice(models.Model):
inv_types = inv_type if isinstance(inv_type, list) else [inv_type]
company_id = self._context.get('company_id', self.env.user.company_id.id)
domain = [
('type', 'in', filter(None, map(TYPE2JOURNAL.get, inv_types))),
('type', 'in', [TYPE2JOURNAL[ty] for ty in inv_types if ty in TYPE2JOURNAL]),
('company_id', '=', company_id),
]
return self.env['account.journal'].search(domain, limit=1)
@@ -192,11 +192,11 @@ class AccountInvoice(models.Model):
@api.one
@api.depends('move_id.line_ids.amount_residual')
def _compute_payments(self):
payment_lines = []
payment_lines = set()
for line in self.move_id.line_ids:
payment_lines.extend(filter(None, [rp.credit_move_id.id for rp in line.matched_credit_ids]))
payment_lines.extend(filter(None, [rp.debit_move_id.id for rp in line.matched_debit_ids]))
self.payment_move_line_ids = self.env['account.move.line'].browse(list(set(payment_lines)))
payment_lines.update(line.mapped('matched_credit_ids.credit_move_id.id'))
payment_lines.update(line.mapped('matched_debit_ids.debit_move_id.id'))
self.payment_move_line_ids = self.env['account.move.line'].browse(list(payment_lines))
name = fields.Char(string='Reference/Description', index=True,
readonly=True, states={'draft': [('readonly', False)]}, copy=False, help='The name that will be used on account move lines')
@@ -335,7 +335,7 @@ class AccountInvoice(models.Model):
'_onchange_partner_id': ['account_id', 'payment_term_id', 'fiscal_position_id', 'partner_bank_id'],
'_onchange_journal_id': ['currency_id'],
}
for onchange_method, changed_fields in onchanges.items():
for onchange_method, changed_fields in pycompat.items(onchanges):
if any(f not in vals for f in changed_fields):
invoice = self.new(vals)
getattr(invoice, onchange_method)()
@@ -456,7 +456,7 @@ class AccountInvoice(models.Model):
tax_grouped = invoice.get_taxes_values()
# Create new tax lines
for tax in tax_grouped.values():
for tax in pycompat.values(tax_grouped):
account_invoice_tax.create(tax)
# dummy write on self to trigger recomputations
@@ -475,7 +475,7 @@ class AccountInvoice(models.Model):
def _onchange_invoice_line_ids(self):
taxes_grouped = self.get_taxes_values()
tax_lines = self.tax_line_ids.browse([])
for tax in taxes_grouped.values():
for tax in pycompat.values(taxes_grouped):
tax_lines += tax_lines.new(tax)
self.tax_line_ids = tax_lines
return
@@ -820,7 +820,7 @@ class AccountInvoice(models.Model):
else:
line2[tmp] = l
line = []
for key, val in line2.items():
for key, val in pycompat.items(line2):
line.append((0, 0, val))
return line
@@ -1017,7 +1017,7 @@ class AccountInvoice(models.Model):
result = []
for line in lines:
values = {}
for name, field in line._fields.iteritems():
for name, field in pycompat.items(line._fields):
if name in MAGIC_COLUMNS:
continue
elif field.type == 'many2one':
@@ -1155,8 +1155,8 @@ class AccountInvoice(models.Model):
for line in self.tax_line_ids:
res.setdefault(line.tax_id.tax_group_id, 0.0)
res[line.tax_id.tax_group_id] += line.amount
res = sorted(res.items(), key=lambda l: l[0].sequence)
res = map(lambda l: (l[0].name, l[1]), res)
res = sorted(pycompat.items(res), key=lambda l: l[0].sequence)
res = [(l[0].name, l[1]) for l in res]
return res
@@ -166,7 +166,7 @@ class account_journal(models.Model):
""", (tuple(self.ids),))
number_to_reconcile = self.env.cr.fetchone()[0]
# optimization to read sum of balance from account_move_line
account_ids = tuple(filter(None, [self.default_debit_account_id.id, self.default_credit_account_id.id]))
account_ids = tuple(ac for ac in [self.default_debit_account_id.id, self.default_credit_account_id.id] if ac)
if account_ids:
amount_field = 'balance' if not self.currency_id else 'amount_currency'
query = """SELECT sum(%s) FROM account_move_line WHERE account_id in %%s;""" % (amount_field,)
+1 -1
View File
@@ -1696,7 +1696,7 @@ class AccountPartialReconcile(models.Model):
total_amount_currency += aml.company_id.currency_id.with_context(date=aml.date).compute(aml.balance, partial_rec.currency_id)
for x in aml.matched_debit_ids | aml.matched_credit_ids:
partial_rec_set[x] = None
partial_rec_ids = [x.id for x in partial_rec_set.keys()]
partial_rec_ids = [x.id for x in partial_rec_set]
aml_ids = aml_set.ids
#then, if the total debit and credit are equal, or the total amount in currency is 0, the reconciliation is full
digits_rounding_precision = aml_set[0].company_id.currency_id.rounding
+4 -1
View File
@@ -5,6 +5,9 @@ from odoo import api, fields, models, _
from odoo import SUPERUSER_ID
import logging
from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
def migrate_set_tags_and_taxes_updatable(cr, registry, module):
@@ -322,7 +325,7 @@ class AccountChartTemplate(models.Model):
# writing account values after creation of accounts
company.transfer_account_id = account_template_ref[transfer_account_id.id]
for key, value in generated_tax_res['account_dict'].items():
for key, value in pycompat.items(generated_tax_res['account_dict']):
if value['refund_account_id'] or value['account_id'] or value['cash_basis_account']:
AccountTaxObj.browse(key).write({
'refund_account_id': account_ref.get(value['refund_account_id'], False),
+4 -4
View File
@@ -4,7 +4,7 @@ from operator import itemgetter
import time
from odoo import api, fields, models, _
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, pycompat
from odoo.exceptions import ValidationError
from odoo.addons.base.res.res_partner import WARNING_MESSAGE, WARNING_HELP
@@ -72,7 +72,7 @@ class AccountFiscalPosition(models.Model):
ref_dict = {}
for line in self.account_ids:
ref_dict[line.account_src_id] = line.account_dest_id
for key, acc in accounts.items():
for key, acc in list(pycompat.items(accounts)):
if acc in ref_dict:
accounts[key] = ref_dict[acc]
return accounts
@@ -245,7 +245,7 @@ class ResPartner(models.Model):
res = self._cr.fetchall()
if not res:
return [('id', '=', '0')]
return [('id', 'in', map(itemgetter(0), res))]
return [('id', 'in', [r[0] for r in res])]
@api.model
def _credit_search(self, operator, operand):
@@ -292,7 +292,7 @@ class ResPartner(models.Model):
""" % where_clause
self.env.cr.execute(query, where_clause_params)
price_totals = self.env.cr.dictfetchall()
for partner, child_ids in all_partners_and_children.items():
for partner, child_ids in pycompat.items(all_partners_and_children):
partner.total_invoiced = sum(price['total'] for price in price_totals if price['partner_id'] in child_ids)
@api.multi
+1 -1
View File
@@ -44,7 +44,7 @@ class ReportTrialBalance(models.AbstractModel):
currency = account.currency_id and account.currency_id or account.company_id.currency_id
res['code'] = account.code
res['name'] = account.name
if account.id in account_result.keys():
if account.id in account_result:
res['debit'] = account_result[account.id].get('debit')
res['credit'] = account_result[account.id].get('credit')
res['balance'] = account_result[account.id].get('balance')
@@ -27,7 +27,7 @@ class ReportGeneralLedger(models.AbstractModel):
"""
cr = self.env.cr
MoveLine = self.env['account.move.line']
move_lines = dict(map(lambda x: (x, []), accounts.ids))
move_lines = {x: [] for x in accounts.ids}
# Prepare initial sql query and Get the initial move lines
if init_balance:
+1 -1
View File
@@ -24,7 +24,7 @@ class ReportJournal(models.AbstractModel):
query += 'am.name'
query += ', "account_move_line".move_id, acc.code'
self.env.cr.execute(query, tuple(params))
ids = map(lambda x: x[0], self.env.cr.fetchall())
ids = (x[0] for x in self.env.cr.fetchall())
return self.env['account.move.line'].browse(ids)
def _sum_debit(self, data, journal_id):
@@ -8,7 +8,7 @@ class ReportOverdue(models.AbstractModel):
_name = 'report.account.report_overdue'
def _get_account_move_lines(self, partner_ids):
res = dict(map(lambda x:(x,[]), partner_ids))
res = {x: [] for x in partner_ids}
self.env.cr.execute("SELECT m.name AS move_id, l.date, l.name, l.ref, l.date_maturity, l.partner_id, l.blocked, l.amount_currency, l.currency_id, "
"CASE WHEN at.type = 'receivable' "
"THEN SUM(l.debit) "
@@ -2,6 +2,7 @@
import time
from odoo import api, models
from odoo.tools import pycompat
class ReportFinancial(models.AbstractModel):
@@ -18,7 +19,7 @@ class ReportFinancial(models.AbstractModel):
res = {}
for account in accounts:
res[account.id] = dict((fn, 0.0) for fn in mapping.keys())
res[account.id] = dict.fromkeys(mapping, 0.0)
if accounts:
tables, where_clause, where_params = self.env['account.move.line']._query_get()
tables = tables.replace('"', '') if tables else "account_move_line"
@@ -26,7 +27,7 @@ class ReportFinancial(models.AbstractModel):
if where_clause.strip():
wheres.append(where_clause.strip())
filters = " AND ".join(wheres)
request = "SELECT account_id as id, " + ', '.join(mapping.values()) + \
request = "SELECT account_id as id, " + ', '.join(pycompat.values(mapping)) + \
" FROM " + tables + \
" WHERE account_id IN %s " \
+ filters + \
@@ -53,26 +54,26 @@ class ReportFinancial(models.AbstractModel):
if report.type == 'accounts':
# it's the sum of the linked accounts
res[report.id]['account'] = self._compute_account_balance(report.account_ids)
for value in res[report.id]['account'].values():
for value in pycompat.values(res[report.id]['account']):
for field in fields:
res[report.id][field] += value.get(field)
elif report.type == 'account_type':
# it's the sum the leaf accounts with such an account type
accounts = self.env['account.account'].search([('user_type_id', 'in', report.account_type_ids.ids)])
res[report.id]['account'] = self._compute_account_balance(accounts)
for value in res[report.id]['account'].values():
for value in pycompat.values(res[report.id]['account']):
for field in fields:
res[report.id][field] += value.get(field)
elif report.type == 'account_report' and report.account_report_id:
# it's the amount of the linked report
res2 = self._compute_report_balance(report.account_report_id)
for key, value in res2.items():
for key, value in pycompat.items(res2):
for field in fields:
res[report.id][field] += value[field]
elif report.type == 'sum':
# it's the sum of the children of this account.report
res2 = self._compute_report_balance(report.children_ids)
for key, value in res2.items():
for key, value in pycompat.items(res2):
for field in fields:
res[report.id][field] += value[field]
return res
@@ -84,11 +85,11 @@ class ReportFinancial(models.AbstractModel):
res = self.with_context(data.get('used_context'))._compute_report_balance(child_reports)
if data['enable_filter']:
comparison_res = self.with_context(data.get('comparison_context'))._compute_report_balance(child_reports)
for report_id, value in comparison_res.items():
for report_id, value in pycompat.items(comparison_res):
res[report_id]['comp_bal'] = value['balance']
report_acc = res[report_id].get('account')
if report_acc:
for account_id, val in comparison_res[report_id].get('account').items():
for account_id, val in pycompat.items(comparison_res[report_id].get('account')):
report_acc[account_id]['comp_bal'] = val['balance']
for report in child_reports:
@@ -113,7 +114,7 @@ class ReportFinancial(models.AbstractModel):
if res[report.id].get('account'):
sub_lines = []
for account_id, value in res[report.id]['account'].items():
for account_id, value in pycompat.items(res[report.id]['account']):
#if there are accounts to display, we add them to the lines with a level equals to their level in
#the COA + 1 (to avoid having them with a too low level that would conflicts with the level of data
#financial reports for Assets, liabilities...)
+6 -3
View File
@@ -2,6 +2,9 @@ from odoo.addons.account.tests.account_test_classes import AccountingTestCase
import time
import unittest
from odoo.tools import pycompat
class TestReconciliation(AccountingTestCase):
"""Tests for reconciliation (account.tax)
@@ -336,8 +339,8 @@ class TestReconciliation(AccountingTestCase):
self.assertTrue(exchange_loss_line, 'There should be one move line of 0.01 EUR in credit')
# The journal items of the reconciliation should have their debit and credit total equal
# Besides, the total debit and total credit should be 60.61 EUR (2.00 USD)
self.assertEquals(sum([res['debit'] for res in result.values()]), 60.61)
self.assertEquals(sum([res['credit'] for res in result.values()]), 60.61)
self.assertEquals(sum(res['debit'] for res in pycompat.values(result)), 60.61)
self.assertEquals(sum(res['credit'] for res in pycompat.items(result)), 60.61)
counterpart_exchange_loss_line = None
for line in exchange_loss_line.move_id.line_id:
if line.account_id.id == self.account_fx_expense_id:
@@ -473,4 +476,4 @@ class TestReconciliation(AccountingTestCase):
self.assertEquals(round(aml.debit, 2), line['debit'])
self.assertEquals(round(aml.credit, 2), line['credit'])
self.assertEquals(round(aml.amount_currency, 2), line['amount_currency'])
self.assertEquals(aml.currency_id.id, line['currency_id'])
self.assertEquals(aml.currency_id.id, line['currency_id'])
+3 -3
View File
@@ -7,7 +7,7 @@ from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF, pycompat
from odoo.tools import float_compare, float_is_zero
@@ -374,7 +374,7 @@ class AccountAssetAsset(models.Model):
vals = self.onchange_category_id_values(self.category_id.id)
# We cannot use 'write' on an object that doesn't exist yet
if vals:
for k, v in vals['value'].iteritems():
for k, v in pycompat.items(vals['value']):
setattr(self, k, v)
def onchange_category_id_values(self, category_id):
@@ -583,7 +583,7 @@ class AccountAssetDepreciationLine(models.Model):
message = ''
if message_description:
message = '<span>%s</span>' % message_description
for name, values in tracked_values.iteritems():
for name, values in pycompat.items(tracked_values):
message += '<div> &nbsp; &nbsp; &bull; <b>%s</b>: ' % name
message += '%s</div>' % values
return message
@@ -22,6 +22,6 @@ class AssetDepreciationConfirmationWizard(models.TransientModel):
'view_mode': 'tree,form',
'res_model': 'account.move',
'view_id': False,
'domain': "[('id','in',[" + ','.join(map(str, created_move_ids)) + "])]",
'domain': "[('id','in',[" + ','.join(str(id) for id in created_move_ids) + "])]",
'type': 'ir.actions.act_window',
}
@@ -31,8 +31,8 @@ class ReportAssertAccount(models.AbstractModel):
:rtype: [(key, value)]
"""
if cols is None:
cols = item.keys()
return [(col, item.get(col)) for col in cols if col in item.keys()]
cols = list(item)
return [(col, item.get(col)) for col in cols if col in item]
localdict = {
'cr': self.env.cr,
@@ -5,6 +5,7 @@
from odoo import fields, models, api, _
import odoo.addons.decimal_precision as dp
from odoo.exceptions import UserError
from odoo.tools import pycompat
class AccountVoucher(models.Model):
@@ -357,7 +358,7 @@ class AccountVoucherLine(models.Model):
self.company_id.id,
self.voucher_id.currency_id.id,
self.voucher_id.voucher_type)
for fname, fvalue in onchange_res['value'].iteritems():
for fname, fvalue in pycompat.items(onchange_res['value']):
setattr(self, fname, fvalue)
def _get_account(self, product, fpos, type):
+1 -1
View File
@@ -76,7 +76,7 @@ class IrModelFieldsAnonymization(models.Model):
@api.multi
def write(self, vals):
# check field state: all should be clear before we can modify a field:
if not len(vals.keys()) == 1 and vals.get('state') == 'clear':
if not len(vals) == 1 and vals.get('state') == 'clear':
self._check_write()
if vals.get('field_name') and vals.get('model_name'):
vals['model_id'], vals['field_id'] = self._get_model_and_field_ids(vals)
@@ -270,7 +270,7 @@ class IrModelFieldsAnonymizeWizard(models.TransientModel):
data = pickle.loads(base64.decodestring(self.file_import))
fixes = self.env['ir.model.fields.anonymization.migration.fix'].search_read([
('target_version', '=', '.'.join(map(str, version_info[:2])))
('target_version', '=', '.'.join(str(v) for v in version_info[:2]))
], ['model_name', 'field_name', 'query', 'query_type', 'sequence'])
fixes = group(fixes, ('model_name', 'field_name'))
+3 -2
View File
@@ -7,6 +7,7 @@ from odoo import http, _
from odoo.addons.auth_signup.models.res_users import SignupError
from odoo.addons.web.controllers.main import ensure_db, Home
from odoo.http import request
from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -86,7 +87,7 @@ class AuthSignupHome(Home):
try:
# retrieve the user info (name, login or email) corresponding to a signup token
token_infos = request.env['res.partner'].sudo().signup_retrieve_info(qcontext.get('token'))
for k, v in token_infos.items():
for k, v in pycompat.items(token_infos):
qcontext.setdefault(k, v)
except:
qcontext['error'] = _("Invalid signup token")
@@ -96,7 +97,7 @@ class AuthSignupHome(Home):
def do_signup(self, qcontext):
""" Shared helper that creates a res.partner out of a token """
values = { key: qcontext.get(key) for key in ('login', 'name', 'password') }
assert values.values(), "The form was not properly filled in."
assert values, "The form was not properly filled in."
assert values.get('password') == qcontext.get('confirm_password'), "Passwords do not match; please retype them."
supported_langs = [lang['code'] for lang in request.env['res.lang'].sudo().search_read([], ['code'])]
if request.lang in supported_langs:
+1 -1
View File
@@ -17,7 +17,7 @@ class SignupError(Exception):
def random_token():
# the token has an entropy of about 120 bits (6 bits/char * 20 chars)
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
return ''.join(random.SystemRandom().choice(chars) for _ in pycompat.range(20))
return ''.join(random.SystemRandom().choice(chars) for _ in range(20))
def now(**kwargs):
dt = datetime.now() + timedelta(**kwargs)
@@ -5,7 +5,7 @@ import re
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import pycompat
STREET_FIELDS = ('street_name', 'street_number', 'street_number2')
@@ -135,7 +135,7 @@ class Partner(models.Model):
vals[field_name] = street_raw
# assign the values to the fields
# /!\ Note that a write(vals) would cause a recursion since it would bypass the cache
for k, v in vals.items():
for k, v in pycompat.items(vals):
partner[k] = v
@@ -12,6 +12,7 @@ from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, SUPERUSER_ID
from odoo.modules.registry import Registry
from odoo.tools import pycompat
from odoo.tools.safe_eval import safe_eval
_logger = logging.getLogger(__name__)
@@ -263,7 +264,7 @@ class BaseAutomation(models.Model):
if res:
if 'value' in res:
res['value'].pop('id', None)
self.update({key: val for key, val in res['value'].iteritems() if key in self._fields})
self.update({key: val for key, val in pycompat.items(res['value']) if key in self._fields})
if 'domain' in res:
result.setdefault('domain', {}).update(res['domain'])
if 'warning' in res:
@@ -8,6 +8,7 @@ import uuid
from odoo import api, fields, models, tools, _
from odoo.exceptions import UserError, ValidationError
from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -193,7 +194,7 @@ class BaseGengoTranslations(models.TransientModel):
term_ids.write(vals)
jobs = response.get('jobs', [])
if jobs:
for t_id, job in jobs.items():
for t_id, job in pycompat.items(jobs):
self._update_terms_job(job)
return
@@ -31,10 +31,10 @@ def geo_query_address(street=None, zip=None, city=None, state=None, country=None
# put country qualifier in front, otherwise GMap gives wrong results,
# e.g. 'Congo, Democratic Republic of the' => 'Democratic Republic of the Congo'
country = '{1} {0}'.format(*country.split(',', 1))
return tools.ustr(', '.join(filter(None, [street,
("%s %s" % (zip or '', city or '')).strip(),
state,
country])))
return tools.ustr(', '.join(
field for field in [street, ("%s %s" % (zip or '', city or '')).strip(), state, country]
if field
))
class ResPartner(models.Model):
+9 -9
View File
@@ -15,7 +15,7 @@ from odoo import api, fields, models
from odoo.tools.translate import _
from odoo.tools.mimetypes import guess_mimetype
from odoo.tools.misc import ustr
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, pycompat
try:
from cStringIO import StringIO
@@ -49,7 +49,7 @@ FILE_TYPE_DICT = {
}
EXTENSIONS = {
'.' + ext: handler
for mime, (ext, handler, req) in FILE_TYPE_DICT.iteritems()
for mime, (ext, handler, req) in pycompat.items(FILE_TYPE_DICT)
}
@@ -121,7 +121,7 @@ class Import(models.TransientModel):
}]
model_fields = Model.fields_get()
blacklist = models.MAGIC_COLUMNS + [Model.CONCURRENCY_CHECK_FIELD]
for name, field in model_fields.iteritems():
for name, field in pycompat.items(model_fields):
if name in blacklist:
continue
# an empty string means the field is deprecated, @deprecated must
@@ -134,7 +134,7 @@ class Import(models.TransientModel):
continue
# states = {state: [(attr, value), (attr2, value2)], state2:...}
if not any(attr == 'readonly' and value is False
for attr, value in itertools.chain.from_iterable(states.itervalues())):
for attr, value in itertools.chain.from_iterable(pycompat.values(states))):
continue
field_value = {
'id': name,
@@ -208,7 +208,7 @@ class Import(models.TransientModel):
def _read_xls_book(self, book):
sheet = book.sheet_by_index(0)
# emulate Sheet.get_rows for pre-0.9.4
for row in itertools.imap(sheet.row, range(sheet.nrows)):
for row in pycompat.imap(sheet.row, range(sheet.nrows)):
values = []
for cell in row:
if cell.ctype is xlrd.XL_CELL_NUMBER:
@@ -543,13 +543,13 @@ class Import(models.TransientModel):
else:
mapper = operator.itemgetter(*indices)
# Get only list of actually imported fields
import_fields = filter(None, fields)
import_fields = [f for f in fields if f]
rows_to_import = self._read_file(options)
if options.get('headers'):
rows_to_import = itertools.islice(rows_to_import, 1, None)
data = [
list(row) for row in itertools.imap(mapper, rows_to_import)
list(row) for row in pycompat.imap(mapper, rows_to_import)
# don't try inserting completely empty rows (e.g. from
# filtering out o2m fields)
if any(row)
@@ -566,7 +566,7 @@ class Import(models.TransientModel):
value = value[1:-1]
negative = True
float_regex = re.compile(r'([-]?[0-9.,]+)')
split_value = filter(None, float_regex.split(value))
split_value = [g for g in float_regex.split(value) if g]
if len(split_value) > 2:
# This is probably not a float
return False
@@ -603,7 +603,7 @@ class Import(models.TransientModel):
def _parse_import_data(self, data, import_fields, options):
# Get fields of type date/datetime
all_fields = self.env[self.res_model].fields_get()
for name, field in all_fields.iteritems():
for name, field in pycompat.items(all_fields):
if field['type'] in ('date', 'datetime') and name in import_fields:
# Parse date
index = import_fields.index(name)
+3 -1
View File
@@ -19,6 +19,8 @@ from odf import opendocument
from odf.table import Table, TableRow, TableCell
from odf.text import P
from odoo.tools import pycompat
class ODSReader(object):
@@ -95,4 +97,4 @@ class ODSReader(object):
return self.SHEETS[name]
def getFirstSheet(self):
return next(iter(self.SHEETS.itervalues()))
return next(iter(pycompat.values(self.SHEETS)))
+4 -5
View File
@@ -9,7 +9,6 @@ from odoo.tests.common import TransactionCase, can_import
from odoo.modules.module import get_module_resource
from odoo.tools import mute_logger
ID_FIELD = {
'id': 'id',
'name': 'id',
@@ -260,7 +259,7 @@ class TestPreview(TransactionCase):
['qux', '5', '6'],
])
# Ensure we only have the response fields we expect
self.assertItemsEqual(result.keys(), ['matches', 'headers', 'fields', 'preview', 'headers_type', 'options', 'advanced_mode', 'debug'])
self.assertItemsEqual(list(result), ['matches', 'headers', 'fields', 'preview', 'headers_type', 'options', 'advanced_mode', 'debug'])
@unittest.skipUnless(can_import('xlrd'), "XLRD module not available")
def test_xls_success(self):
@@ -290,7 +289,7 @@ class TestPreview(TransactionCase):
['qux', '5', '6'],
])
# Ensure we only have the response fields we expect
self.assertItemsEqual(result.keys(), ['matches', 'headers', 'fields', 'preview', 'headers_type', 'options', 'advanced_mode', 'debug'])
self.assertItemsEqual(list(result), ['matches', 'headers', 'fields', 'preview', 'headers_type', 'options', 'advanced_mode', 'debug'])
@unittest.skipUnless(can_import('xlrd.xlsx'), "XLRD/XLSX not available")
def test_xlsx_success(self):
@@ -320,7 +319,7 @@ class TestPreview(TransactionCase):
['qux', '5', '6'],
])
# Ensure we only have the response fields we expect
self.assertItemsEqual(result.keys(), ['matches', 'headers', 'fields', 'preview', 'headers_type', 'options','advanced_mode', 'debug'])
self.assertItemsEqual(list(result), ['matches', 'headers', 'fields', 'preview', 'headers_type', 'options','advanced_mode', 'debug'])
@unittest.skipUnless(can_import('odf'), "ODFPY not available")
def test_ods_success(self):
@@ -350,7 +349,7 @@ class TestPreview(TransactionCase):
['aux', '5', '6'],
])
# Ensure we only have the response fields we expect
self.assertItemsEqual(result.keys(), ['matches', 'headers', 'fields', 'preview', 'headers_type', 'options', 'advanced_mode', 'debug'])
self.assertItemsEqual(list(result), ['matches', 'headers', 'fields', 'preview', 'headers_type', 'options', 'advanced_mode', 'debug'])
class test_convert_import_data(TransactionCase):
@@ -8,7 +8,7 @@ from os.path import join as opj
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.modules import load_information_from_description_file
from odoo.tools import convert_file, exception_to_unicode
from odoo.tools import convert_file, exception_to_unicode, pycompat
from odoo.tools.osutil import tempdir
_logger = logging.getLogger(__name__)
@@ -113,6 +113,6 @@ class IrModule(models.Model):
_logger.exception('Error while importing module')
errors[mod_name] = exception_to_unicode(e)
r = ["Successfully imported module '%s'" % mod for mod in success]
for mod, error in errors.items():
for mod, error in pycompat.items(errors):
r.append("Error while importing module '%s': %r" % (mod, error))
return '\n'.join(r), module_names
@@ -23,6 +23,6 @@ class IrUiView(models.Model):
GROUP BY coalesce(v.inherit_id, v.id)
""", [model])
ids = map(itemgetter(0), self._cr.fetchall())
ids = (row[0] for row in self._cr.fetchall())
views = self.with_context(load_all_views=True).browse(ids)
return views._check_xml() and result
+1 -1
View File
@@ -154,7 +154,7 @@ class ResPartner(models.Model):
match = self.__check_vat_ch_re2.match(vat)
if match:
# For new TVA numbers, do a mod11 check
num = filter(lambda s: s.isdigit(), match.group(1)) # get the digits only
num = [s for s in match.group(1) if s.isdigit()] # get the digits only
factor = (5, 4, 3, 2, 7, 6, 5, 4)
csum = sum([int(num[i]) * factor[i] for i in range(8)])
check = (11 - (csum % 11)) % 11
+12 -12
View File
@@ -34,7 +34,7 @@ def calendar_id2real_id(calendar_id=None, with_date=False):
:return: real event id
"""
if calendar_id and isinstance(calendar_id, (basestring)):
res = filter(None, calendar_id.split('-'))
res = [bit for bit in calendar_id.split('-') if bit]
if len(res) == 2:
real_id = res[0]
if with_date:
@@ -127,7 +127,7 @@ class Attendee(models.Model):
def create(self, values):
if not values.get("email") and values.get("common_name"):
common_nameval = values.get("common_name").split(':')
email = filter(lambda x: x.__contains__('@'), common_nameval) # TODO JEM : should be refactored
email = [x for x in common_nameval if '@' in x] # TODO JEM : should be refactored
values['email'] = email and email[0] or ''
values['common_name'] = values.get("common_name")
return super(Attendee, self).create(values)
@@ -354,7 +354,7 @@ class AlarmManager(models.AbstractModel):
all_meetings = self.get_next_potential_limit_alarm('email', seconds=cron_interval)
for meeting in self.env['calendar.event'].browse(all_meetings.keys()):
for meeting in self.env['calendar.event'].browse(all_meetings):
max_delta = all_meetings[meeting.id]['max_duration']
if meeting.recurrency:
@@ -468,7 +468,7 @@ class Alarm(models.Model):
name = fields.Char('Name', required=True)
type = fields.Selection([('notification', 'Notification'), ('email', 'Email')], 'Type', required=True, default='email')
duration = fields.Integer('Remind Before', required=True, default=1)
interval = fields.Selection(list(_interval_selection.iteritems()), 'Unit', required=True, default='hours')
interval = fields.Selection(list(pycompat.items(_interval_selection)), 'Unit', required=True, default='hours')
duration_minutes = fields.Integer('Duration in minutes', compute='_compute_duration_minutes', store=True, help="Duration in minutes")
@api.onchange('duration', 'interval')
@@ -541,7 +541,7 @@ class Meeting(models.Model):
""" Get recurrent start and stop dates based on Rule string"""
start_dates = self._get_recurrent_date_by_event(date_field='start')
stop_dates = self._get_recurrent_date_by_event(date_field='stop')
return zip(start_dates, stop_dates)
return list(pycompat.izip(start_dates, stop_dates))
@api.multi
def _get_recurrent_date_by_event(self, date_field='start'):
@@ -550,7 +550,7 @@ class Meeting(models.Model):
date_field: the field containing the reference date information for recurrency computation
"""
self.ensure_one()
if date_field in self._fields.keys() and self._fields[date_field].type in ('date', 'datetime'):
if date_field in self._fields and self._fields[date_field].type in ('date', 'datetime'):
reference_date = self[date_field]
else:
reference_date = self.start
@@ -1157,7 +1157,7 @@ class Meeting(models.Model):
data['final_date'] = rule._until and rule._until.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
#repeat weekly
if rule._byweekday:
for i in pycompat.range(0, 7):
for i in range(0, 7):
if i in rule._byweekday:
data[day_list[i]] = True
data['rrule_type'] = 'weekly'
@@ -1286,7 +1286,7 @@ class Meeting(models.Model):
@api.multi
def _get_message_unread(self):
id_map = {x: calendar_id2real_id(x) for x in self.ids}
real = self.browse(set(id_map.values()))
real = self.browse(set(pycompat.values(id_map)))
super(Meeting, real)._get_message_unread()
for event in self:
if event.id == id_map[event.id]:
@@ -1298,7 +1298,7 @@ class Meeting(models.Model):
@api.multi
def _get_message_needaction(self):
id_map = {x: calendar_id2real_id(x) for x in self.ids}
real = self.browse(set(id_map.values()))
real = self.browse(set(pycompat.values(id_map)))
super(Meeting, real)._get_message_needaction()
for event in self:
if event.id == id_map[event.id]:
@@ -1339,7 +1339,7 @@ class Meeting(models.Model):
@api.multi
def get_metadata(self):
real = self.browse(set({x: calendar_id2real_id(x) for x in self.ids}.values()))
real = self.browse({calendar_id2real_id(x) for x in self.ids})
return super(Meeting, real).get_metadata()
@api.model
@@ -1460,7 +1460,7 @@ class Meeting(models.Model):
if fields and (f not in fields):
fields2.append(f)
select = map(lambda x: (x, calendar_id2real_id(x)), self.ids)
select = [(x, calendar_id2real_id(x)) for x in self.ids]
real_events = self.browse([real_id for calendar_id, real_id in select])
real_data = super(Meeting, real_events).read(fields=fields2, load=load)
real_data = dict((d['id'], d) for d in real_data)
@@ -1493,7 +1493,7 @@ class Meeting(models.Model):
if user_id == self.env.user.id or partner_id in r.get("partner_ids", []):
continue
if r['privacy'] == 'private':
for f in r.keys():
for f in r:
recurrent_fields = self._get_recurrent_fields()
public_fields = list(set(recurrent_fields + ['id', 'allday', 'start', 'stop', 'display_start', 'display_stop', 'duration', 'user_id', 'state', 'interval', 'count', 'recurrent_id_date', 'rrule']))
if f not in public_fields:
+2 -2
View File
@@ -19,13 +19,13 @@ class Message(models.Model):
if isinstance(args[index][2], basestring):
args[index] = (args[index][0], args[index][1], get_real_ids(args[index][2]))
elif isinstance(args[index][2], list):
args[index] = (args[index][0], args[index][1], map(lambda x: get_real_ids(x), args[index][2]))
args[index] = (args[index][0], args[index][1], [get_real_ids(x) for x in args[index][2]])
return super(Message, self).search(args, offset=offset, limit=limit, order=order, count=count)
@api.model
def _find_allowed_model_wise(self, doc_model, doc_dict):
if doc_model == 'calendar.event':
order = self._context.get('order', self._order)
for virtual_id in self.env[doc_model].browse(doc_dict.keys()).get_recurrent_ids([], order=order):
for virtual_id in self.env[doc_model].browse(doc_dict).get_recurrent_ids([], order=order):
doc_dict.setdefault(virtual_id, doc_dict[get_real_ids(virtual_id)])
return super(Message, self)._find_allowed_model_wise(doc_model, doc_dict)
+1 -4
View File
@@ -433,9 +433,6 @@ class Lead(models.Model):
res = _get_first_not_null(attr, opportunities)
return res.id if res else False
def _concat_all(attr, opportunities):
return '\n\n'.join(filter(None, (opp[attr] for opp in opportunities)))
# process the fields' values
data = {}
for field_name in fields:
@@ -447,7 +444,7 @@ class Lead(models.Model):
elif field.type == 'many2one':
data[field_name] = _get_first_not_null_id(field_name, self) # take the first not null
elif field.type == 'text':
data[field_name] = _concat_all(field_name, self) # contact field of all opportunities
data[field_name] = '\n\n'.join(it for it in self.mapped(field_name) if it)
else:
data[field_name] = _get_first_not_null(field_name, self)
+3 -4
View File
@@ -17,8 +17,7 @@ from .validate_email import validate_email
from odoo import api, fields, models
from odoo import SUPERUSER_ID, _
from odoo.exceptions import ValidationError, UserError
from odoo.tools import mute_logger
from odoo.tools import mute_logger, pycompat
_logger = logging.getLogger('base.partner.merge')
@@ -269,7 +268,7 @@ class MergePartnerAutomatic(models.TransientModel):
return item
# get all fields that are not computed or x2many
values = dict()
for column, field in model_fields.iteritems():
for column, field in pycompat.items(model_fields):
if field.type not in ('many2many', 'one2many') and field.compute is None:
for item in itertools.chain(src_partners, [dst_partner]):
if item[column]:
@@ -407,7 +406,7 @@ class MergePartnerAutomatic(models.TransientModel):
"""
return any(
self.env[model].search_count([(field, 'in', aggr_ids)])
for model, field in models.iteritems()
for model, field in pycompat.items(models)
)
@api.model
+2 -1
View File
@@ -5,6 +5,7 @@ import pytz
from odoo import _, api, fields, models
from odoo.addons.mail.models.mail_template import format_tz
from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.tools import pycompat
from odoo.tools.translate import html_translate
from dateutil.relativedelta import relativedelta
@@ -393,7 +394,7 @@ class EventRegistration(models.Model):
'partner_id': partner_id.id,
'event_id': event_id and event_id.id or False,
}
data.update({key: registration[key] for key in registration.keys() if key in self._fields})
data.update({key: value for key, value in pycompat.items(registration) if key in self._fields})
return data
@api.one
+4 -4
View File
@@ -98,10 +98,10 @@ class EventMailScheduler(models.Model):
def execute(self):
if self.interval_type == 'after_sub':
# update registration lines
lines = []
reg_ids = [mail_reg.registration_id for mail_reg in self.mail_registration_ids]
for registration in filter(lambda item: item not in reg_ids, self.event_id.registration_ids):
lines.append((0, 0, {'registration_id': registration.id}))
lines = [
(0, 0, {'registration_id': registration.id})
for registration in (self.event_id.registration_ids - self.mapped('mail_registration_ids.registration_id'))
]
if lines:
self.write({'mail_registration_ids': lines})
# execute scheduler on registrations
+3 -1
View File
@@ -6,6 +6,8 @@ from odoo.exceptions import UserError
from dateutil.relativedelta import relativedelta
from odoo.tools import pycompat
class FleetVehicleCost(models.Model):
_name = 'fleet.vehicle.cost'
@@ -262,7 +264,7 @@ class FleetVehicleLogContract(models.Model):
res[contract.vehicle_id.id] = 1
Vehicle = self.env['fleet.vehicle']
for vehicle, value in res.items():
for vehicle, value in pycompat.items(res):
Vehicle.browse(vehicle).message_post(body=_('%s contract(s) need(s) to be renewed and/or closed!') % value)
return contracts.write({'state': 'toclose'})
+4 -4
View File
@@ -260,7 +260,7 @@ class Goal(models.Model):
for goal in self:
goals_by_definition.setdefault(goal.definition_id, []).append(goal)
for definition, goals in goals_by_definition.items():
for definition, goals in pycompat.items(goals_by_definition):
goals_to_write = {}
if definition.computation_mode == 'manually':
for goal in goals:
@@ -305,9 +305,9 @@ class Goal(models.Model):
subqueries.setdefault((start_date, end_date), {}).update({goal.id:safe_eval(definition.batch_user_expression, {'user': goal.user_id})})
# the global query should be split by time periods (especially for recurrent goals)
for (start_date, end_date), query_goals in subqueries.items():
for (start_date, end_date), query_goals in pycompat.items(subqueries):
subquery_domain = list(general_domain)
subquery_domain.append((field_name, 'in', list(set(query_goals.values()))))
subquery_domain.append((field_name, 'in', list(set(pycompat.values(query_goals)))))
if start_date:
subquery_domain.append((field_date_name, '>=', start_date))
if end_date:
@@ -351,7 +351,7 @@ class Goal(models.Model):
goals_to_write.update(goal._get_write_values(new_value))
for goal, values in goals_to_write.iteritems():
for goal, values in pycompat.items(goals_to_write):
if not values:
continue
goal.write(values)
+1 -1
View File
@@ -20,7 +20,7 @@ class Meeting(models.Model):
@api.multi
def write(self, values):
sync_fields = set(self.get_fields_need_update_google())
if (set(values.keys()) and sync_fields) and 'oe_update_date' not in values.keys() and 'NewMeeting' not in self._context:
if (set(values) and sync_fields) and 'oe_update_date' not in values and 'NewMeeting' not in self._context:
values['oe_update_date'] = fields.Datetime.now()
return super(Meeting, self).write(values)
@@ -10,7 +10,7 @@ import pytz
import urllib2
from odoo import api, fields, models, tools, _
from odoo.tools import exception_to_unicode
from odoo.tools import exception_to_unicode, pycompat
_logger = logging.getLogger(__name__)
@@ -23,15 +23,15 @@ class Meta(type):
""" This Meta class allow to define class as a structure, and so instancied variable
in __init__ to avoid to have side effect alike 'static' variable """
def __new__(typ, name, parents, attrs):
methods = dict((k, v) for k, v in attrs.iteritems()
if callable(v))
attrs = dict((k, v) for k, v in attrs.iteritems()
if not callable(v))
methods = {k: v for k, v in pycompat.items(attrs)
if callable(v)}
attrs = {k: v for k, v in pycompat.items(attrs)
if not callable(v)}
def init(self, **kw):
for key, val in attrs.iteritems():
for key, val in pycompat.items(attrs):
setattr(self, key, val)
for key, val in kw.iteritems():
for key, val in pycompat.items(kw):
assert key in attrs
setattr(self, key, val)
@@ -40,9 +40,7 @@ class Meta(type):
return type.__new__(typ, name, parents, methods)
class Struct(object):
__metaclass__ = Meta
Struct = Meta('Struct', (object,), {})
class OdooEvent(Struct):
event = False
@@ -161,7 +159,7 @@ class SyncOperation(object):
def __init__(self, src, info, **kw):
self.src = src
self.info = info
for key, val in kw.items():
for key, val in pycompat.items(kw):
setattr(self, key, val)
def __str__(self):
@@ -689,7 +687,7 @@ class GoogleCalendar(models.AbstractModel):
my_google_attendees = CalendarAttendee.with_context(context_novirtual).search([
('partner_id', '=', my_partner_id),
('google_internal_event_id', 'in', all_event_from_google.keys())
('google_internal_event_id', 'in', pycompat.keys(all_event_from_google))
])
my_google_att_ids = my_google_attendees.ids
@@ -753,7 +751,7 @@ class GoogleCalendar(models.AbstractModel):
ev_to_sync.OE.status = event.active
ev_to_sync.OE.synchro = att.oe_synchro_date
for event in all_event_from_google.values():
for event in pycompat.values(all_event_from_google):
event_id = event.get('id')
base_event_id = event_id.rsplit('_', 1)[0]
@@ -788,7 +786,7 @@ class GoogleCalendar(models.AbstractModel):
# DO ACTION #
######################
for base_event in event_to_synchronize:
event_to_synchronize[base_event] = sorted(event_to_synchronize[base_event].iteritems(), key=operator.itemgetter(0))
event_to_synchronize[base_event] = sorted(pycompat.items(event_to_synchronize[base_event]), key=operator.itemgetter(0))
for current_event in event_to_synchronize[base_event]:
self.env.cr.commit()
event = current_event[1] # event is an Sync Event !
+2 -2
View File
@@ -7,7 +7,7 @@ from odoo import api, fields, models
from odoo import tools, _
from odoo.exceptions import ValidationError
from odoo.modules.module import get_module_resource
from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -250,7 +250,7 @@ class Employee(models.Model):
if auto_follow_fields is None:
auto_follow_fields = ['user_id']
user_field_lst = []
for name, field in self._fields.items():
for name, field in pycompat.items(self._fields):
if name in auto_follow_fields and name in updated_fields and field.comodel_name == 'res.users':
user_field_lst.append(name)
return user_field_lst
+3 -3
View File
@@ -5,7 +5,7 @@ import re
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import email_split, float_is_zero
from odoo.tools import email_split, float_is_zero, pycompat
import odoo.addons.decimal_precision as dp
@@ -237,12 +237,12 @@ class HrExpense(models.Model):
})
#convert eml into an osv-valid format
lines = map(lambda x: (0, 0, expense._prepare_move_line(x)), move_lines)
lines = [(0, 0, expense._prepare_move_line(x)) for x in move_lines]
move.with_context(dont_create_taxes=True).write({'line_ids': lines})
expense.sheet_id.write({'account_move_id': move.id})
if expense.payment_mode == 'company_account':
expense.sheet_id.paid_expense_sheets()
for move in move_group_by_sheet.values():
for move in pycompat.values(move_group_by_sheet):
move.post()
return True
+1 -1
View File
@@ -142,7 +142,7 @@ class HolidaysType(models.Model):
if not count and not order and self._context.get('employee_id'):
leaves = self.browse(leave_ids)
sort_key = lambda l: (not l.limit, l.virtual_remaining_leaves)
return map(int, leaves.sorted(key=sort_key, reverse=True))
return leaves.sorted(key=sort_key, reverse=True).ids
return leave_ids
+6 -4
View File
@@ -11,6 +11,7 @@ import babel
from odoo import api, fields, models, tools, _
from odoo.addons import decimal_precision as dp
from odoo.exceptions import UserError, ValidationError
from odoo.tools import pycompat
class HrPayslip(models.Model):
@@ -203,7 +204,8 @@ class HrPayslip(models.Model):
'contract_id': contract.id,
}
res += [attendances] + leaves.values()
res.append(attendances)
res.extend(pycompat.values(leaves))
return res
@api.model
@@ -367,7 +369,7 @@ class HrPayslip(models.Model):
#blacklist this rule and its children
blacklist += [id for id, seq in rule._recursive_search_of_rules()]
return [value for code, value in result_dict.items()]
return [value for code, value in pycompat.items(result_dict)]
# YTI TODO To rename. This method is not really an onchange, as it is not in any view
# employee_id and contract_id could be browse records
@@ -378,9 +380,9 @@ class HrPayslip(models.Model):
'value': {
'line_ids': [],
#delete old input lines
'input_line_ids': map(lambda x: (2, x,), self.input_line_ids.ids),
'input_line_ids': [(2, x,) for x in self.input_line_ids.ids],
#delete old worked days lines
'worked_days_line_ids': map(lambda x: (2, x,), self.worked_days_line_ids.ids),
'worked_days_line_ids': [(2, x,) for x in self.worked_days_line_ids.ids],
#'details_by_salary_head':[], TODO put me back
'name': '',
'contract_id': False,
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
from odoo.tools import pycompat
class PayslipDetailsReport(models.AbstractModel):
@@ -37,9 +38,9 @@ class PayslipDetailsReport(models.AbstractModel):
result.setdefault(x[2], {})
result[x[2]].setdefault(x[1], [])
result[x[2]][x[1]].append(x[0])
for payslip_id, lines_dict in result.iteritems():
for payslip_id, lines_dict in pycompat.items(result):
res.setdefault(payslip_id, [])
for rule_categ_id, line_ids in lines_dict.iteritems():
for rule_categ_id, line_ids in pycompat.items(lines_dict):
rule_categories = RuleCateg.browse(rule_categ_id)
lines = PayslipLine.browse(line_ids)
level = 0
@@ -69,9 +70,9 @@ class PayslipDetailsReport(models.AbstractModel):
result.setdefault(line.slip_id.id, {})
result[line.slip_id.id].setdefault(line.register_id, line)
result[line.slip_id.id][line.register_id] |= line
for payslip_id, lines_dict in result.iteritems():
for payslip_id, lines_dict in pycompat.items(result):
res.setdefault(payslip_id, [])
for register, lines in lines_dict.iteritems():
for register, lines in pycompat.items(lines_dict):
res[payslip_id].append({
'register_name': register.name,
'total': sum(lines.mapped('total')),
@@ -4,6 +4,7 @@
from datetime import datetime
from odoo import api, fields, models, tools, SUPERUSER_ID
from odoo.tools import pycompat
from odoo.tools.translate import _
from odoo.exceptions import UserError
@@ -248,7 +249,7 @@ class Applicant(models.Model):
self = self.with_context(default_department_id=vals.get('department_id'))
if vals.get('job_id') or self._context.get('default_job_id'):
job_id = vals.get('job_id') or self._context.get('default_job_id')
for key, value in self._onchange_job_id_internal(job_id)['value'].iteritems():
for key, value in pycompat.items(self._onchange_job_id_internal(job_id)['value']):
if key not in vals:
vals[key] = value
if vals.get('user_id'):
+1 -1
View File
@@ -80,7 +80,7 @@ class Blackbox(Thread):
def _wrap_low_level_message_around(self, high_level_message):
bcc = self._lrc(high_level_message)
high_level_message_bytes = map(ord, high_level_message)
high_level_message_bytes = (ord(b) for b in high_level_message)
low_level_message = bytearray()
low_level_message.append(0x02)
+2 -2
View File
@@ -168,7 +168,7 @@ class StyleStack:
def to_escpos(self):
""" converts the current style to an escpos command string """
cmd = ''
ordered_cmds = sorted(self.cmds.keys(), key=lambda x: self.cmds[x]['_order'])
ordered_cmds = sorted(self.cmds, key=lambda x: self.cmds[x]['_order'])
for style in ordered_cmds:
cmd += self.cmds[style][self.get(style)]
return cmd
@@ -788,7 +788,7 @@ class Escpos:
if encoding in remaining:
del remaining[encoding]
if len(remaining) >= 1:
encoding = remaining.items()[0][0]
(encoding, _) = remaining.popitem()
else:
encoding = 'cp437'
encoded = '\xb1' # could not encode, output error character
@@ -6,6 +6,7 @@ import re
from datetime import datetime, timedelta
from odoo import api, fields, models, modules, tools
from odoo.tools import pycompat
class ImLivechatChannel(models.Model):
@@ -95,7 +96,7 @@ class ImLivechatChannel(models.Model):
for record in self:
dt = fields.Datetime.to_string(datetime.utcnow() - timedelta(days=7))
repartition = record.channel_ids.rating_get_grades([('create_date', '>=', dt)])
total = sum(repartition.values())
total = sum(pycompat.values(repartition))
if total > 0:
happy = repartition['great']
record.rating_percentage_satisfaction = ((happy*100) / total) if happy > 0 else 0
+2 -1
View File
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.tools import pycompat
class ChannelPartner(models.Model):
@@ -77,7 +78,7 @@ class MailChannel(models.Model):
last_msg = self.env['mail.message'].search([("channel_ids", "in", [channel.id])], limit=1)
if last_msg:
channel_infos_dict[channel.id]['last_message_date'] = last_msg.date
return channel_infos_dict.values()
return list(pycompat.values(channel_infos_dict))
@api.model
def channel_fetch_slot(self):
@@ -4,6 +4,7 @@
from datetime import date
from odoo import api, models
from odoo.tools import pycompat
class EmployeesYearlySalaryReport(models.AbstractModel):
@@ -97,7 +98,7 @@ class EmployeesYearlySalaryReport(models.AbstractModel):
def salary_list(self, salaries):
cat_salary_all = []
for category_name, amount in salaries.items():
for category_name, amount in pycompat.items(salaries):
cat_salary = []
total = 0.0
cat_salary.append(category_name)
+14 -14
View File
@@ -2,7 +2,7 @@ from collections import OrderedDict
import csv
import xlrd
from odoo.tools import pycompat
def _e(s):
if type(s) is unicode:
@@ -32,33 +32,33 @@ class LuxTaxGenerator:
self.suffix = self.sheet_info.cell_value(4, 2)
def iter_tax_codes(self):
keys = map(lambda c: c.value, self.sheet_tax_codes.row(0))
keys = [c.value for c in self.sheet_tax_codes.row(0)]
yield keys
for i in range(1, self.sheet_tax_codes.nrows):
row = map(lambda c: c.value, self.sheet_tax_codes.row(i))
d = OrderedDict(zip(keys, row))
row = (c.value for c in self.sheet_tax_codes.row(i))
d = OrderedDict(pycompat.izip(keys, row))
d['sign'] = int(d['sign'])
d['sequence'] = int(d['sequence'])
yield d
def iter_taxes(self):
keys = map(lambda c: c.value, self.sheet_taxes.row(0))
keys = [c.value for c in self.sheet_taxes.row(0)]
yield keys
for i in range(1, self.sheet_taxes.nrows):
row = map(lambda c: c.value, self.sheet_taxes.row(i))
yield OrderedDict(zip(keys, row))
row = (c.value for c in self.sheet_taxes.row(i))
yield OrderedDict(pycompat.izip(keys, row))
def iter_fiscal_pos_map(self):
keys = map(lambda c: c.value, self.sheet_fiscal_pos_map.row(0))
keys = [c.value for c in self.sheet_fiscal_pos_map.row(0)]
yield keys
for i in range(1, self.sheet_fiscal_pos_map.nrows):
row = map(lambda c: c.value, self.sheet_fiscal_pos_map.row(i))
yield OrderedDict(zip(keys, row))
row = (c.value for c in self.sheet_fiscal_pos_map.row(i))
yield OrderedDict(pycompat.izip(keys, row))
def tax_codes_to_csv(self):
writer = csv.writer(open('account.tax.code.template-%s.csv' %
self.suffix, 'wb'))
tax_codes_iterator = self.iter_tax_codes()
tax_codes_iterator = self.iter_tax_codes
keys = next(tax_codes_iterator)
writer.writerow(keys)
@@ -69,7 +69,7 @@ class LuxTaxGenerator:
if tax_code in tax_codes:
raise RuntimeError('duplicate tax code %s' % tax_code)
tax_codes[tax_code] = row['id']
writer.writerow(map(_e, row.values()))
writer.writerow(pycompat.imap(_e, pycompat.values(row)))
# read taxes and add leaf tax codes
new_tax_codes = {} # id: parent_code
@@ -168,7 +168,7 @@ class LuxTaxGenerator:
cur_seq = seq + 1000
else:
cur_seq = seq
writer.writerow(map(_e, row.values()[3:]) + [cur_seq])
writer.writerow(list(pycompat.imap(_e, list(pycompat.values(row))[3:])) + [cur_seq])
def fiscal_pos_map_to_csv(self):
writer = csv.writer(open('account.fiscal.'
@@ -178,7 +178,7 @@ class LuxTaxGenerator:
keys = next(fiscal_pos_map_iterator)
writer.writerow(keys)
for row in fiscal_pos_map_iterator:
writer.writerow(map(_e, row.values()))
writer.writerow(pycompat.imap(_e, pycompat.values(row)))
if __name__ == '__main__':
+2 -2
View File
@@ -13,7 +13,7 @@ from urlparse import urlparse
from werkzeug import url_encode, unescape
from odoo import models, fields, api, _
from odoo.tools import ustr
from odoo.tools import ustr, pycompat
URL_REGEX = r'(\bhref=[\'"](?!mailto:)([^\'"]+)[\'"])'
@@ -165,7 +165,7 @@ class link_tracker(models.Model):
create_vals['url'] = VALIDATE_URL(vals['url'])
search_domain = []
for fname, value in create_vals.iteritems():
for fname, value in pycompat.items(create_vals):
search_domain.append((fname, '=', value))
result = self.search(search_domain, limit=1)
+6 -4
View File
@@ -10,6 +10,8 @@ from odoo import api, fields, models, _
from odoo.exceptions import AccessError, ValidationError
import odoo.addons.decimal_precision as dp
from odoo.tools import pycompat
class LunchOrder(models.Model):
"""
@@ -24,10 +26,10 @@ class LunchOrder(models.Model):
prev_order = self.env['lunch.order.line'].search([('user_id', '=', self.env.uid), ('product_id.active', '!=', False)], limit=20, order='id desc')
# If we return return prev_order.ids, we will have duplicates (identical orders).
# Therefore, this following part removes duplicates based on product_id and note.
return {
return list(pycompat.values({
(order.product_id, order.note): order.id
for order in prev_order
}.values()
}))
user_id = fields.Many2one('res.users', 'User', readonly=True,
states={'new': [('readonly', False)]},
@@ -86,10 +88,10 @@ class LunchOrder(models.Model):
prev_order = self.env['lunch.order.line'].search([('user_id', '=', self.env.uid), ('product_id.active', '!=', False)], limit=20, order='date desc, id desc')
# If we use prev_order.ids, we will have duplicates (identical orders).
# Therefore, this following part removes duplicates based on product_id and note.
self.previous_order_ids = {
self.previous_order_ids = list(pycompat.values({
(order.product_id, order.note): order.id
for order in prev_order
}.values()
}))
if self.previous_order_ids:
lunch_data = []
+6 -5
View File
@@ -1,5 +1,7 @@
#!/usr/bin/env python
"""html2text: Turn HTML into equivalent Markdown-structured text."""
from odoo.tools import pycompat
__version__ = "2.36"
__author__ = "Aaron Swartz (me@aaronsw.com)"
__copyright__ = "(C) 2004-2008 Aaron Swartz. GNU GPL 3."
@@ -8,7 +10,6 @@ __contributors__ = ["Martin 'Joey' Schulze", "Ricardo Reyes", "Kevin Jay North"]
# TODO:
# Support decoded entities with unifiable.
if not hasattr(__builtins__, 'True'): True, False = 1, 0
import re, sys, urllib, htmlentitydefs, codecs
import sgmllib
import urlparse
@@ -52,7 +53,7 @@ unifiable = {'rsquo':"'", 'lsquo':"'", 'rdquo':'"', 'ldquo':'"',
unifiable_n = {}
for k in unifiable.keys():
for k in unifiable:
unifiable_n[name2cp(k)] = unifiable[k]
def charref(name):
@@ -61,13 +62,13 @@ def charref(name):
else:
c = int(name)
if not UNICODE_SNOB and c in unifiable_n.keys():
if not UNICODE_SNOB and c in unifiable_n:
return unifiable_n[c]
else:
return unichr(c)
def entityref(c):
if not UNICODE_SNOB and c in unifiable.keys():
if not UNICODE_SNOB and c in unifiable:
return unifiable[c]
else:
try: name2cp(c)
@@ -400,7 +401,7 @@ class _html2text(sgmllib.SGMLParser):
self.a = newa
if self.abbr_list and force == "end":
for abbr, definition in self.abbr_list.items():
for abbr, definition in pycompat.items(self.abbr_list):
self.out(" *[" + abbr + "]: " + definition + "\n")
self.p_p = 0
+3 -3
View File
@@ -9,7 +9,7 @@ import uuid
from odoo import _, api, fields, models, modules, tools
from odoo.exceptions import UserError
from odoo.osv import expression
from odoo.tools import ormcache
from odoo.tools import ormcache, pycompat
from odoo.tools.safe_eval import safe_eval
@@ -614,12 +614,12 @@ class Channel(models.Model):
GROUP BY mail_channel_id
""", (tuple(self.ids),))
channels_preview = dict((r['message_id'], r) for r in self._cr.dictfetchall())
last_messages = self.env['mail.message'].browse(channels_preview.keys()).message_format()
last_messages = self.env['mail.message'].browse(channels_preview).message_format()
for message in last_messages:
channel = channels_preview[message['id']]
del(channel['message_id'])
channel['last_message'] = message
return channels_preview.values()
return list(pycompat.values(channels_preview))
#------------------------------------------------------
# Commands
+10 -9
View File
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo.tools import pycompat
class Followers(models.Model):
@@ -41,7 +42,7 @@ class Followers(models.Model):
using the subtypes given in the parameters
"""
res_model_id = self.env['ir.model']._get(res_model).id
force_mode = force or (all(data for data in partner_data.values()) and all(data for data in channel_data.values()))
force_mode = force or (all(data for data in pycompat.values(partner_data)) and all(data for data in pycompat.values(channel_data)))
generic = []
specific = {}
existing = {} # {res_id: follower_ids}
@@ -51,7 +52,7 @@ class Followers(models.Model):
followers = self.sudo().search([
'&',
'&', ('res_model_id', '=', res_model_id), ('res_id', 'in', res_ids),
'|', ('partner_id', 'in', partner_data.keys()), ('channel_id', 'in', channel_data.keys())])
'|', ('partner_id', 'in', list(partner_data)), ('channel_id', 'in', list(channel_data))])
if force_mode:
followers.unlink()
@@ -69,20 +70,20 @@ class Followers(models.Model):
external_default_subtypes = default_subtypes.filtered(lambda subtype: not subtype.internal)
if force_mode:
employee_pids = self.env['res.users'].sudo().search([('partner_id', 'in', partner_data.keys()), ('share', '=', False)]).mapped('partner_id').ids
for pid, data in partner_data.iteritems():
employee_pids = self.env['res.users'].sudo().search([('partner_id', 'in', list(partner_data)), ('share', '=', False)]).mapped('partner_id').ids
for pid, data in pycompat.items(partner_data):
if not data:
if pid not in employee_pids:
partner_data[pid] = external_default_subtypes.ids
else:
partner_data[pid] = default_subtypes.ids
for cid, data in channel_data.iteritems():
for cid, data in pycompat.items(channel_data):
if not data:
channel_data[cid] = default_subtypes.ids
# create new followers, batch ok
gen_new_pids = [pid for pid in partner_data.keys() if pid not in p_exist]
gen_new_cids = [cid for cid in channel_data.keys() if cid not in c_exist]
gen_new_pids = [pid for pid in partner_data if pid not in p_exist]
gen_new_cids = [cid for cid in channel_data if cid not in c_exist]
for pid in gen_new_pids:
generic.append([0, 0, {'res_model_id': res_model_id, 'partner_id': pid, 'subtype_ids': [(6, 0, partner_data.get(pid) or default_subtypes.ids)]}])
for cid in gen_new_cids:
@@ -94,8 +95,8 @@ class Followers(models.Model):
command = []
doc_followers = existing.get(res_id, list())
new_pids = set(partner_data.keys()) - set([sub.partner_id.id for sub in doc_followers if sub.partner_id]) - set(gen_new_pids)
new_cids = set(channel_data.keys()) - set([sub.channel_id.id for sub in doc_followers if sub.channel_id]) - set(gen_new_cids)
new_pids = set(partner_data) - set([sub.partner_id.id for sub in doc_followers if sub.partner_id]) - set(gen_new_pids)
new_cids = set(channel_data) - set([sub.channel_id.id for sub in doc_followers if sub.channel_id]) - set(gen_new_cids)
# subscribe new followers
for new_pid in new_pids:
+2 -1
View File
@@ -13,6 +13,7 @@ from email.utils import formataddr
from odoo import _, api, fields, models
from odoo import tools
from odoo.addons.base.ir.ir_mail_server import MailDeliveryException
from odoo.tools import pycompat
from odoo.tools.safe_eval import safe_eval
_logger = logging.getLogger(__name__)
@@ -206,7 +207,7 @@ class MailMail(models.Model):
groups[mail.mail_server_id.id].append(mail.id)
sys_params = self.env['ir.config_parameter'].sudo()
batch_size = int(sys_params.get_param('mail.session.batch.size', 1000))
for server_id, record_ids in groups.iteritems():
for server_id, record_ids in pycompat.items(groups):
for mail_batch in tools.split_every(batch_size, record_ids):
yield server_id, mail_batch
+13 -13
View File
@@ -8,7 +8,7 @@ from email.utils import formataddr
from odoo import _, api, fields, models, SUPERUSER_ID, tools
from odoo.exceptions import UserError, AccessError
from odoo.osv import expression
from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -280,7 +280,7 @@ class Message(models.Model):
partners = self.env['res.partner'].sudo()
attachments = self.env['ir.attachment']
trackings = self.env['mail.tracking.value']
for key, message in message_tree.iteritems():
for key, message in pycompat.items(message_tree):
if message.author_id:
partners |= message.author_id
if message.subtype_id and message.partner_ids: # take notified people of message with a subtype
@@ -434,7 +434,7 @@ class Message(models.Model):
@api.model
def _find_allowed_model_wise(self, doc_model, doc_dict):
doc_ids = doc_dict.keys()
doc_ids = list(doc_dict)
allowed_doc_ids = self.env[doc_model].with_context(active_test=False).search([('id', 'in', doc_ids)]).ids
return set([message_id for allowed_doc_id in allowed_doc_ids for message_id in doc_dict[allowed_doc_id]])
@@ -442,7 +442,7 @@ class Message(models.Model):
def _find_allowed_doc_ids(self, model_ids):
IrModelAccess = self.env['ir.model.access']
allowed_ids = set()
for doc_model, doc_dict in model_ids.iteritems():
for doc_model, doc_dict in pycompat.items(model_ids):
if not IrModelAccess.check(doc_model, 'read', False):
continue
allowed_ids |= self._find_allowed_model_wise(doc_model, doc_dict)
@@ -603,17 +603,17 @@ class Message(models.Model):
# Author condition (READ, WRITE, CREATE (private))
author_ids = []
if operation == 'read' or operation == 'write':
author_ids = [mid for mid, message in message_values.iteritems()
author_ids = [mid for mid, message in pycompat.items(message_values)
if message.get('author_id') and message.get('author_id') == self.env.user.partner_id.id]
elif operation == 'create':
author_ids = [mid for mid, message in message_values.iteritems()
author_ids = [mid for mid, message in pycompat.items(message_values)
if not message.get('model') and not message.get('res_id')]
# Parent condition, for create (check for received notifications for the created message parent)
notified_ids = []
if operation == 'create':
# TDE: probably clean me
parent_ids = [message.get('parent_id') for mid, message in message_values.iteritems()
parent_ids = [message.get('parent_id') for mid, message in pycompat.items(message_values)
if message.get('parent_id')]
self._cr.execute("""SELECT DISTINCT m.id, partner_rel.res_partner_id, channel_partner.partner_id FROM "%s" m
LEFT JOIN "mail_message_res_partner_rel" partner_rel
@@ -626,37 +626,37 @@ class Message(models.Model):
ON channel_partner.channel_id = channel.id AND channel_partner.partner_id = (%%s)
WHERE m.id = ANY (%%s)""" % self._table, (self.env.user.partner_id.id, self.env.user.partner_id.id, parent_ids,))
not_parent_ids = [mid[0] for mid in self._cr.fetchall() if any([mid[1], mid[2]])]
notified_ids += [mid for mid, message in message_values.iteritems()
notified_ids += [mid for mid, message in pycompat.items(message_values)
if message.get('parent_id') in not_parent_ids]
# Recipients condition, for read and write (partner_ids) and create (message_follower_ids)
other_ids = set(self.ids).difference(set(author_ids), set(notified_ids))
model_record_ids = _generate_model_record_ids(message_values, other_ids)
if operation in ['read', 'write']:
notified_ids = [mid for mid, message in message_values.iteritems() if message.get('notified')]
notified_ids = [mid for mid, message in pycompat.items(message_values) if message.get('notified')]
elif operation == 'create':
for doc_model, doc_ids in model_record_ids.items():
for doc_model, doc_ids in pycompat.items(model_record_ids):
followers = self.env['mail.followers'].sudo().search([
('res_model', '=', doc_model),
('res_id', 'in', list(doc_ids)),
('partner_id', '=', self.env.user.partner_id.id),
])
fol_mids = [follower.res_id for follower in followers]
notified_ids += [mid for mid, message in message_values.iteritems()
notified_ids += [mid for mid, message in pycompat.items(message_values)
if message.get('model') == doc_model and message.get('res_id') in fol_mids]
# CRUD: Access rights related to the document
other_ids = other_ids.difference(set(notified_ids))
model_record_ids = _generate_model_record_ids(message_values, other_ids)
document_related_ids = []
for model, doc_ids in model_record_ids.items():
for model, doc_ids in pycompat.items(model_record_ids):
DocumentModel = self.env[model]
mids = DocumentModel.browse(doc_ids).exists()
if hasattr(DocumentModel, 'check_mail_message_access'):
DocumentModel.check_mail_message_access(mids.ids, operation) # ?? mids ?
else:
self.env['mail.thread'].check_mail_message_access(mids.ids, operation, model_name=model)
document_related_ids += [mid for mid, message in message_values.iteritems()
document_related_ids += [mid for mid, message in pycompat.items(message_values)
if message.get('model') == model and message.get('res_id') in mids.ids]
# Calculate remaining ids: if not void, raise an error
+9 -9
View File
@@ -356,7 +356,7 @@ class MailTemplate(models.Model):
return multi_mode and results or results[res_ids[0]]
# prepare template variables
records = self.env[model].browse(filter(None, res_ids)) # filter to avoid browsing [None]
records = self.env[model].browse(it for it in res_ids if it) # filter to avoid browsing [None]
res_to_rec = dict.fromkeys(res_ids, None)
for record in records:
res_to_rec[record.id] = record
@@ -366,7 +366,7 @@ class MailTemplate(models.Model):
'user': self.env.user,
'ctx': self._context, # context kw would clash with mako internals
}
for res_id, record in res_to_rec.iteritems():
for res_id, record in pycompat.items(res_to_rec):
variables['object'] = record
try:
render_result = template.render(variables)
@@ -378,7 +378,7 @@ class MailTemplate(models.Model):
results[res_id] = render_result
if post_process:
for res_id, result in results.iteritems():
for res_id, result in pycompat.items(results):
results[res_id] = self.render_post_process(result)
return multi_mode and results or results[res_ids[0]]
@@ -399,7 +399,7 @@ class MailTemplate(models.Model):
self.ensure_one()
langs = self.render_template(self.lang, self.model, res_ids)
for res_id, lang in langs.iteritems():
for res_id, lang in pycompat.items(langs):
if lang:
template = self.with_context(lang=lang)
else:
@@ -418,11 +418,11 @@ class MailTemplate(models.Model):
if self.use_default_to or self._context.get('tpl_force_default_to'):
default_recipients = self.env['mail.thread'].message_get_default_recipients(res_model=self.model, res_ids=res_ids)
for res_id, recipients in default_recipients.iteritems():
for res_id, recipients in pycompat.items(default_recipients):
results[res_id].pop('partner_to', None)
results[res_id].update(recipients)
for res_id, values in results.iteritems():
for res_id, values in pycompat.items(results):
partner_ids = values.get('partner_ids', list())
if self._context.get('tpl_partners_only'):
mails = tools.email_split(values.pop('email_to', '')) + tools.email_split(values.pop('email_cc', ''))
@@ -461,11 +461,11 @@ class MailTemplate(models.Model):
# templates: res_id -> template; template -> res_ids
templates_to_res_ids = {}
for res_id, template in res_ids_to_templates.iteritems():
for res_id, template in pycompat.items(res_ids_to_templates):
templates_to_res_ids.setdefault(template, []).append(res_id)
results = dict()
for template, template_res_ids in templates_to_res_ids.iteritems():
for template, template_res_ids in pycompat.items(templates_to_res_ids):
Template = self.env['mail.template']
# generate fields value for all res_ids linked to the current template
if template.lang:
@@ -475,7 +475,7 @@ class MailTemplate(models.Model):
generated_field_values = Template.render_template(
getattr(template, field), template.model, template_res_ids,
post_process=(field == 'body_html'))
for res_id, field_value in generated_field_values.iteritems():
for res_id, field_value in pycompat.items(generated_field_values):
results.setdefault(res_id, dict())[field] = field_value
# compute recipients
if any(field in fields for field in ['email_to', 'partner_to', 'email_cc']):
+20 -20
View File
@@ -235,10 +235,10 @@ class MailThread(models.AbstractModel):
# auto_subscribe: take values and defaults into account
create_values = dict(values)
for key, val in self._context.iteritems():
for key, val in pycompat.items(self._context):
if key.startswith('default_') and key[8:] not in create_values:
create_values[key[8:]] = val
thread.message_auto_subscribe(create_values.keys(), values=create_values)
thread.message_auto_subscribe(list(create_values), values=create_values)
# track values
if not self._context.get('mail_notrack'):
@@ -246,7 +246,7 @@ class MailThread(models.AbstractModel):
track_thread = thread.with_context(lang=self.env.user.lang)
else:
track_thread = thread
tracked_fields = track_thread._get_tracked_fields(values.keys())
tracked_fields = track_thread._get_tracked_fields(list(values))
if tracked_fields:
initial_values = {thread.id: dict.fromkeys(tracked_fields, False)}
track_thread.message_track(tracked_fields, initial_values)
@@ -266,7 +266,7 @@ class MailThread(models.AbstractModel):
tracked_fields = None
if not self._context.get('mail_notrack'):
tracked_fields = track_self._get_tracked_fields(values.keys())
tracked_fields = track_self._get_tracked_fields(list(values))
if tracked_fields:
initial_values = dict((record.id, dict((key, getattr(record, key)) for key in tracked_fields))
for record in track_self)
@@ -275,7 +275,7 @@ class MailThread(models.AbstractModel):
result = super(MailThread, self).write(values)
# update followers
self.message_auto_subscribe(values.keys(), values=values)
self.message_auto_subscribe(list(values), values=values)
# Perform the tracking
if tracked_fields:
@@ -385,7 +385,7 @@ class MailThread(models.AbstractModel):
always tracked fields and modified on_change fields
"""
tracked_fields = []
for name, field in self._fields.items():
for name, field in pycompat.items(self._fields):
if getattr(field, 'track_visibility', False):
tracked_fields.append(name)
@@ -413,10 +413,10 @@ class MailThread(models.AbstractModel):
@api.multi
def _message_track_post_template(self, tracking):
if not any(change for rec_id, (change, tracking_value_ids) in tracking.iteritems()):
if not any(change for rec_id, (change, tracking_value_ids) in pycompat.items(tracking)):
return True
templates = self._track_template(tracking)
for field_name, (template, post_kwargs) in templates.iteritems():
for field_name, (template, post_kwargs) in pycompat.items(templates):
if not template:
continue
if isinstance(template, basestring):
@@ -447,7 +447,7 @@ class MailThread(models.AbstractModel):
display_values_ids = []
# generate tracked_values data structure: {'col_name': {col_info, new_value, old_value}}
for col_name, col_info in tracked_fields.items():
for col_name, col_info in pycompat.items(tracked_fields):
track_visibility = getattr(self._fields[col_name], 'track_visibility', 'onchange')
initial_value = initial[col_name]
new_value = getattr(self, col_name)
@@ -565,7 +565,7 @@ class MailThread(models.AbstractModel):
@api.model
def _generate_notification_token(self, base_link, params):
secret = self.env['ir.config_parameter'].sudo().get_param('database.secret')
token = '%s?%s' % (base_link, ' '.join('%s=%s' % (key, params[key]) for key in sorted(params.keys())))
token = '%s?%s' % (base_link, ' '.join('%s=%s' % (key, params[key]) for key in sorted(params)))
hm = hmac.new(str(secret), token, hashlib.sha1).hexdigest()
return hm
@@ -765,20 +765,20 @@ class MailThread(models.AbstractModel):
aliases[alias.alias_parent_thread_id] = '%s@%s' % (alias.alias_name, alias_domain)
doc_names.update(
dict((ng_res[0], ng_res[1])
for ng_res in self.env[model_name].sudo().browse(aliases.keys()).name_get()))
for ng_res in self.env[model_name].sudo().browse(aliases).name_get()))
# left ids: use catchall
left_ids = set(res_ids).difference(set(aliases.keys()))
left_ids = set(res_ids).difference(set(aliases))
if left_ids:
catchall_alias = self.env['ir.config_parameter'].sudo().get_param("mail.catchall.alias")
if catchall_alias:
aliases.update(dict((res_id, '%s@%s' % (catchall_alias, alias_domain)) for res_id in left_ids))
# compute name of reply-to
company_name = self.env.user.company_id.name
for res_id in aliases.keys():
for res_id in aliases:
email_name = '%s%s' % (company_name, doc_names.get(res_id) and (' ' + doc_names[res_id]) or '')
email_addr = aliases[res_id]
res[res_id] = formataddr((email_name, email_addr))
left_ids = set(res_ids).difference(set(aliases.keys()))
left_ids = set(res_ids).difference(set(aliases))
if left_ids:
res.update(dict((res_id, default) for res_id in res_ids))
return res
@@ -813,7 +813,7 @@ class MailThread(models.AbstractModel):
def message_capable_models(self):
""" Used by the plugin addon, based for plugin_outlook and others. """
ret_dict = {}
for model_name, model in self.env.iteritems():
for model_name, model in pycompat.items(self.env):
if hasattr(model, "message_process") and hasattr(model, "message_post"):
ret_dict[model_name] = model._description
return ret_dict
@@ -823,7 +823,7 @@ class MailThread(models.AbstractModel):
:param string message: an email.message instance """
s = ', '.join([tools.decode_smtp_header(message.get(h)) for h in header_fields if message.get(h)])
return filter(lambda x: x, self._find_partner_from_emails(tools.email_split(s)))
return [x for x in self._find_partner_from_emails(tools.email_split(s)) if x]
def _routing_warn(self, error_message, warn_suffix, message_id, route, raise_exception):
""" Tools method used in message_route_verify: whether to log a warning or raise an error """
@@ -1836,7 +1836,7 @@ class MailThread(models.AbstractModel):
if self._context.get('mail_post_autofollow') and self.ids and partner_ids:
partner_to_subscribe = partner_ids
if self._context.get('mail_post_autofollow_partner_ids'):
partner_to_subscribe = filter(lambda item: item in self._context.get('mail_post_autofollow_partner_ids'), partner_ids)
partner_to_subscribe = [p for p in partner_ids if p in self._context.get('mail_post_autofollow_partner_ids')]
self.message_subscribe(list(partner_to_subscribe), force=False)
# _mail_flat_thread: automatically set free messages to the first posted message
@@ -2047,7 +2047,7 @@ class MailThread(models.AbstractModel):
if auto_follow_fields is None:
auto_follow_fields = ['user_id']
user_field_lst = []
for name, field in self._fields.items():
for name, field in pycompat.items(self._fields):
if name in auto_follow_fields and name in updated_fields and getattr(field, 'track_visibility', False) and field.comodel_name == 'res.users':
user_field_lst.append(name)
return user_field_lst
@@ -2142,10 +2142,10 @@ class MailThread(models.AbstractModel):
for partner in to_add_users.mapped('partner_id'):
new_partners.setdefault(partner.id, None)
for pid, subtypes in new_partners.items():
for pid, subtypes in pycompat.items(new_partners):
subtypes = list(subtypes) if subtypes is not None else None
self.message_subscribe(partner_ids=[pid], subtype_ids=subtypes, force=(subtypes != None))
for cid, subtypes in new_channels.items():
for cid, subtypes in pycompat.items(new_channels):
subtypes = list(subtypes) if subtypes is not None else None
self.message_subscribe(channel_ids=[cid], subtype_ids=subtypes, force=(subtypes != None))
+2 -1
View File
@@ -4,6 +4,7 @@
import logging
import threading
from odoo.tools import pycompat
from odoo.tools.misc import split_every
from odoo import _, api, fields, models, registry, SUPERUSER_ID
@@ -174,7 +175,7 @@ class Partner(models.Model):
emails = self.env['mail.mail']
recipients_nbr, recipients_max = 0, 50
for email_type, recipient_template_values in recipients.iteritems():
for email_type, recipient_template_values in pycompat.items(recipients):
if recipient_template_values['followers']:
# generate notification email content
template_fol_values = dict(base_template_ctx, **recipient_template_values) # fixme: set button_unfollow to none
+10 -9
View File
@@ -29,14 +29,15 @@ class TestMailFollowers(TestMail):
self.assertFalse(specific)
self.assertEqual(len(generic), 2)
self.assertEqual(set([generic[0][2]['res_model_id'], generic[1][2]['res_model_id']]),
set([mail_channel_model_id]))
self.assertEqual(set(filter(None, [generic[0][2].get('channel_id'), generic[1][2].get('channel_id')])),
set([test_channel.id]))
self.assertEqual(set(filter(None, [generic[0][2].get('partner_id'), generic[1][2].get('partner_id')])),
set([self.user_employee.partner_id.id]))
self.assertEqual(set(generic[0][2]['subtype_ids'][0][2] + generic[1][2]['subtype_ids'][0][2]),
set([self.mt_mg_nodef.id, self.mt_al_nodef.id]))
items = [it[2] for it in generic]
self.assertEqual({item['res_model_id'] for item in items},
{mail_channel_model_id})
self.assertEqual({item['channel_id'] for item in items if item.get('channel_id')},
{test_channel.id})
self.assertEqual({item['partner_id'] for item in items if item.get('partner_id')},
{self.user_employee.partner_id.id})
self.assertEqual(set(items[0]['subtype_ids'][0][2] + items[1]['subtype_ids'][0][2]),
{self.mt_mg_nodef.id, self.mt_al_nodef.id})
def test_m2o_command_update_selective(self):
test_channel = self.env['mail.channel'].create({'name': 'Test'})
@@ -55,7 +56,7 @@ class TestMailFollowers(TestMail):
self.assertEqual(generic[0][2]['channel_id'], test_channel.id)
self.assertEqual(set(generic[0][2]['subtype_ids'][0][2]), set(self.default_group_subtypes.ids))
self.assertEqual(specific.keys(), [self.test_public.id])
self.assertEqual(list(specific), [self.test_public.id])
self.assertEqual(specific[self.test_public.id][0][2]['res_model_id'], mail_channel_model_id)
self.assertEqual(specific[self.test_public.id][0][2]['partner_id'], self.user_employee.partner_id.id)
self.assertEqual(set(specific[self.test_public.id][0][2]['subtype_ids'][0][2]), set([self.mt_mg_nodef.id]))
+4 -4
View File
@@ -93,7 +93,7 @@ class MailComposer(models.TransientModel):
result['res_id'] = self.env.user.partner_id.id
if fields is not None:
[result.pop(field, None) for field in result.keys() if field not in fields]
[result.pop(field, None) for field in list(result) if field not in fields]
return result
@api.model
@@ -140,7 +140,7 @@ class MailComposer(models.TransientModel):
for mid, rmod, rid in self._cr.fetchall():
message_values[mid] = {'model': rmod, 'res_id': rid}
# remove from the set to check the ids that mail_compose_message accepts
author_ids = [mid for mid, message in message_values.iteritems()
author_ids = [mid for mid, message in pycompat.items(message_values)
if message.get('model') and not message.get('res_id')]
self = self.browse(list(set(self.ids) - set(author_ids))) # not sure slef = ...
@@ -248,7 +248,7 @@ class MailComposer(models.TransientModel):
for res_ids in sliced_res_ids:
batch_mails = Mail
all_mail_values = wizard.get_mail_values(res_ids)
for res_id, mail_values in all_mail_values.iteritems():
for res_id, mail_values in pycompat.items(all_mail_values):
if wizard.composition_mode == 'mass_mail':
batch_mails |= Mail.create(mail_values)
else:
@@ -338,7 +338,7 @@ class MailComposer(models.TransientModel):
def onchange_template_id_wrapper(self):
self.ensure_one()
values = self.onchange_template_id(self.template_id.id, self.composition_mode, self.model, self.res_id)['value']
for fname, value in values.iteritems():
for fname, value in pycompat.items(values):
setattr(self, fname, value)
@api.multi
@@ -9,6 +9,7 @@ import re
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
from odoo.tools import pycompat
from odoo.tools.safe_eval import safe_eval
import odoo.addons.decimal_precision as dp
@@ -466,10 +467,10 @@ class MarketingCampaignWorkitem(models.Model):
matching_workitems = []
for id, res_id, model in res:
workitem_map.setdefault(model, {}).setdefault(res_id, set()).add(id)
for model, id_map in workitem_map.iteritems():
for model, id_map in pycompat.items(workitem_map):
Model = self.env[model]
condition_name[0] = Model._rec_name
condition = [('id', 'in', id_map.keys()), condition_name]
condition = [('id', 'in', list(id_map)), condition_name]
for record in Model.search(condition):
matching_workitems.extend(id_map[record.id])
return [('id', 'in', list(set(matching_workitems)))]
+5 -5
View File
@@ -258,9 +258,9 @@ class MassMailingCampaign(models.Model):
# Update standard results with default results
result = []
for state_value, state_name in states:
res = filter(lambda x: x['stage_id'] == (state_value, state_name), read_group_res)
res = [x for x in read_group_res if x['stage_id'] == (state_value, state_name)]
if not res:
res = filter(lambda x: x['stage_id'] == state_value, read_group_all_states)
res = [x for x in read_group_all_states if x['stage_id'] == state_value]
res[0]['stage_id'] = [state_value, state_name]
result.append(res[0])
return result
@@ -456,7 +456,7 @@ class MassMailing(models.Model):
def _onchange_model_and_list(self):
if self.mailing_model == 'mail.mass_mailing.list':
if self.contact_list_ids:
self.mailing_domain = "[('list_ids', 'in', [%s]), ('opt_out', '=', False)]" % (','.join(map(str,self.contact_list_ids.ids)),)
self.mailing_domain = "[('list_ids', 'in', [%s]), ('opt_out', '=', False)]" % (','.join(str(id) for id in self.contact_list_ids.ids),)
else:
self.mailing_domain = "[(0, '=', 1)]"
elif 'opt_out' in self.env[self.mailing_model]._fields and not self.mailing_domain:
@@ -497,9 +497,9 @@ class MassMailing(models.Model):
# Update standard results with default results
result = []
for state_value, state_name in states:
res = filter(lambda x: x['state'] == state_value, read_group_res)
res = [x for x in read_group_res if x['state'] == state_value]
if not res:
res = filter(lambda x: x['state'] == state_value, read_group_all_states)
res = [x for x in read_group_all_states if x['state'] == state_value]
res[0]['state'] = [state_value, state_name]
result.append(res[0])
return result
+2 -1
View File
@@ -5,6 +5,7 @@ from dateutil import relativedelta
import datetime
from odoo import api, exceptions, fields, models, _
from odoo.tools import pycompat
class MrpWorkcenter(models.Model):
@@ -73,7 +74,7 @@ class MrpWorkcenter(models.Model):
if res_group['state'] in ('pending', 'ready', 'progress'):
result_duration_expected[res_group['workcenter_id'][0]] += res_group['duration_expected']
for workcenter in self:
workcenter.workorder_count = sum(count for state, count in result[workcenter.id].items() if state not in ('done', 'cancel'))
workcenter.workorder_count = sum(count for state, count in pycompat.items(result[workcenter.id]) if state not in ('done', 'cancel'))
workcenter.workorder_pending_count = result[workcenter.id].get('pending', 0)
workcenter.workcenter_load = result_duration_expected[workcenter.id]
workcenter.workorder_ready_count = result[workcenter.id].get('ready', 0)
+1 -1
View File
@@ -25,7 +25,7 @@ class StockPickingType(models.Model):
data = self.env['mrp.production'].read_group(domains[field] +
[('state', 'not in', ('done', 'cancel')), ('picking_type_id', 'in', self.ids)],
['picking_type_id'], ['picking_type_id'])
count = dict(map(lambda x: (x['picking_type_id'] and x['picking_type_id'][0], x['picking_type_id_count']), data))
count = {x['picking_type_id'] and x['picking_type_id'][0]: x['picking_type_id_count'] for x in data}
for record in mrp_picking_types:
record[field] = count.get(record.id, 0)
+1 -1
View File
@@ -59,7 +59,7 @@ class MrpProductProduce(models.TransientModel):
res['product_qty'] = quantity
res['product_id'] = production.product_id.id
res['product_uom_id'] = production.product_uom_id.id
res['consume_line_ids'] = map(lambda x: (0,0,x), lines) + map(lambda x:(4, x), existing_lines)
res['consume_line_ids'] = [(0,0,x) for x in lines] + [(4, x) for x in existing_lines]
return res
serial = fields.Boolean('Requires Serial')
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo.tools import pycompat
class MakeInvoice(models.TransientModel):
@@ -24,7 +25,7 @@ class MakeInvoice(models.TransientModel):
# but that second call will not do anything, since the repairs are already invoiced.
repairs.action_repair_invoice_create()
return {
'domain': [('id', 'in', new_invoice.values())],
'domain': [('id', 'in', list(pycompat.values(new_invoice)))],
'name': 'Invoices',
'view_type': 'form',
'view_mode': 'tree,form',
+4 -4
View File
@@ -9,7 +9,7 @@ import urllib2
from odoo import api, models, _
from odoo.exceptions import UserError
from odoo.tools import html2plaintext
from odoo.tools import html2plaintext, pycompat
from ..py_etherpad import EtherpadLiteClient
@@ -102,7 +102,7 @@ class PadCommon(models.AbstractModel):
# In case the pad is created programmatically, the content is not filled in yet since it is
# normally initialized by the JS layer
for k, field in self._fields.iteritems():
for k, field in pycompat.items(self._fields):
if hasattr(field, 'pad_content_field') and k not in vals:
ctx = {
'model': self._name,
@@ -115,7 +115,7 @@ class PadCommon(models.AbstractModel):
# Set the pad content in vals
def _set_pad_value(self, vals):
for k, v in vals.items():
for k, v in list(pycompat.items(vals)):
field = self._fields[k]
if hasattr(field, 'pad_content_field'):
vals[field.pad_content_field] = self.pad_get_content(v)
@@ -125,7 +125,7 @@ class PadCommon(models.AbstractModel):
self.ensure_one()
if not default:
default = {}
for k, field in self._fields.iteritems():
for k, field in pycompat.items(self._fields):
if hasattr(field, 'pad_content_field'):
pad = self.pad_generate_url()
default[k] = pad.get('url')
+3 -3
View File
@@ -4,7 +4,7 @@ import hmac
import logging
from odoo import api, exceptions, fields, models, _
from odoo.tools import consteq, float_round, image_resize_images, ustr
from odoo.tools import consteq, float_round, image_resize_images, ustr, pycompat
from odoo.addons.base.module import module
from odoo.exceptions import ValidationError
@@ -155,7 +155,7 @@ class PaymentAcquirer(models.Model):
""" If the field has 'required_if_provider="<provider>"' attribute, then it
required if record.provider is <provider>. """
for acquirer in self:
if any(getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k] for k, f in self._fields.items()):
if any(getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k] for k, f in pycompat.items(self._fields)):
return False
return True
@@ -686,7 +686,7 @@ class PaymentToken(models.Model):
if hasattr(self, custom_method_name):
values.update(getattr(self, custom_method_name)(values))
# remove all non-model fields used by (provider)_create method to avoid warning
fields_wl = set(self._fields.keys()) & set(values.keys())
fields_wl = set(self._fields) & set(values)
values = {field: values[field] for field in fields_wl}
return super(PaymentToken, self).create(values)
+8 -2
View File
@@ -8,11 +8,14 @@ import hashlib
import hmac
import logging
import urlparse
from itertools import chain
from odoo import api, fields, models, tools, _
from odoo.addons.payment.models.payment_acquirer import ValidationError
from odoo.addons.payment_adyen.controllers.main import AdyenController
from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -46,7 +49,10 @@ class AcquirerAdyen(models.Model):
return val.replace('\\', '\\\\').replace(':', '\\:')
def signParams(parms):
signing_string = ':'.join(map(escapeVal, parms.keys() + parms.values()))
signing_string = ':'.join(
escapeVal(v)
for v in chain(pycompat.keys(parms), pycompat.values(parms))
)
hm = hmac.new(hmac_key, signing_string, hashlib.sha256)
return base64.b64encode(hm.digest())
@@ -74,7 +80,7 @@ class AcquirerAdyen(models.Model):
hmac_key = binascii.a2b_hex(self.adyen_skin_hmac_key.encode('ascii'))
raw_values = {k: values.get(k.encode('ascii'), '') for k in keys if k in values}
raw_values_ordered = OrderedDict(sorted(raw_values.items(), key=lambda t: t[0]))
raw_values_ordered = OrderedDict(sorted(pycompat.items(raw_values), key=lambda t: t[0]))
return signParams(raw_values_ordered)
@@ -62,7 +62,7 @@ class AuthorizeAPI():
response = urlopen(request).read()
response = strip_ns(response, XMLNS)
if response.find('messages/resultCode').text == 'Error':
messages = map(lambda m: m.text, response.findall('messages/message/text'))
messages = [m.text for m in response.findall('messages/message/text')]
raise ValidationError('Authorize.net Error Message(s):\n %s' % '\n'.join(messages))
return response
@@ -11,7 +11,7 @@ import odoo
from odoo.addons.payment.models.payment_acquirer import ValidationError
from odoo.addons.payment.tests.common import PaymentAcquirerCommon
from odoo.addons.payment_authorize.controllers.main import AuthorizeController
from odoo.tools import mute_logger
from odoo.tools import mute_logger, pycompat
@odoo.tests.common.at_install(True)
@@ -91,7 +91,7 @@ class AuthorizeForm(AuthorizeCommon):
tree = objectify.fromstring(res)
self.assertEqual(tree.get('action'), 'https://test.authorize.net/gateway/transact.dll', 'Authorize: wrong form POST url')
for el in tree.iterfind('input'):
values = el.values()
values = list(pycompat.values(el.attrib))
if values[1] in ['submit', 'x_fp_hash', 'return_url', 'x_state', 'x_ship_to_state']:
continue
self.assertEqual(
+2 -1
View File
@@ -6,6 +6,7 @@ import werkzeug
from odoo import http
from odoo.http import request
from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -26,6 +27,6 @@ class BuckarooController(http.Controller):
""" Buckaroo."""
_logger.info('Buckaroo: entering form_feedback with post data %s', pprint.pformat(post)) # debug
request.env['payment.transaction'].sudo().form_feedback(post, 'buckaroo')
post = dict((key.upper(), value) for key, value in post.items())
post = {key.upper(): value for key, value in pycompat.items(post)}
return_url = post.get('ADD_RETURNDATA') or '/'
return werkzeug.utils.redirect(return_url)
+5 -3
View File
@@ -7,6 +7,8 @@ import urlparse
from odoo import api, fields, models, _
from odoo.addons.payment.models.payment_acquirer import ValidationError
from odoo.addons.payment_buckaroo.controllers.main import BuckarooController
from odoo.tools import pycompat
from odoo.tools.float_utils import float_compare
_logger = logging.getLogger(__name__)
@@ -19,7 +21,7 @@ def normalize_keys_upper(data):
convert everything to upper case to be able to easily detected the presence
of a parameter by checking the uppercase key only
"""
return dict((key.upper(), val) for key, val in data.items())
return {key.upper(): val for key, val in pycompat.items(data)}
class AcquirerBuckaroo(models.Model):
@@ -65,13 +67,13 @@ class AcquirerBuckaroo(models.Model):
values = dict(values or {})
if inout == 'out':
for key in values.keys():
for key in list(values):
# case insensitive keys
if key.upper() == 'BRQ_SIGNATURE':
del values[key]
break
items = sorted(values.items(), key=lambda pair: pair[0].lower())
items = sorted(pycompat.items(values), key=lambda pair: pair[0].lower())
sign = ''.join('%s=%s' % (k, urllib.unquote_plus(v)) for k, v in items)
else:
sign = ''.join('%s=%s' % (k, get_value(k)) for k in keys)
+1 -1
View File
@@ -143,7 +143,7 @@ class PaymentAcquirerOgone(models.Model):
]
return key.upper() in keys
items = sorted((k.upper(), v) for k, v in values.items())
items = sorted((k.upper(), v) for k, v in pycompat.items(values))
sign = ''.join('%s=%s%s' % (k, v, key) for k, v in items if v and filter_key(k))
sign = sign.encode("utf-8")
shasign = sha1(sign).hexdigest()
+1 -1
View File
@@ -37,7 +37,7 @@ class PaypalController(http.Controller):
:return: tuple containing the STATUS str and the key/value pairs
parsed as a dict
"""
lines = filter(None, response.split('\n'))
lines = [line for line in response.split('\n') if line]
status = lines.pop(0)
pdt_post = {}
+6 -6
View File
@@ -7,7 +7,7 @@ from functools import partial
import psycopg2
from odoo import api, fields, models, tools, _
from odoo.tools import float_is_zero
from odoo.tools import float_is_zero, pycompat
from odoo.exceptions import UserError
from odoo.http import request
import odoo.addons.decimal_precision as dp
@@ -308,7 +308,7 @@ class PosOrder(models.Model):
# round tax lines per order
if rounding_method == 'round_globally':
for group_key, group_value in grouped_data.iteritems():
for group_key, group_value in pycompat.items(grouped_data):
if group_key[0] == 'tax':
for line in group_value:
line['credit'] = cur.round(line['credit'])
@@ -326,7 +326,7 @@ class PosOrder(models.Model):
order.write({'state': 'done', 'account_move': move.id})
all_lines = []
for group_key, group_data in grouped_data.iteritems():
for group_key, group_data in pycompat.items(grouped_data):
for value in group_data:
all_lines.append((0, 0, value),)
if move: # In case no order was changed
@@ -697,7 +697,7 @@ class PosOrder(models.Model):
qty_done = pack_operation.product_qty
else:
has_wrong_lots = True
pack_operation.write({'pack_lot_ids': map(lambda x: (0, 0, x), pack_lots), 'qty_done': qty_done})
pack_operation.write({'pack_lot_ids': [(0, 0, x) for x in pack_lots], 'qty_done': qty_done})
return has_wrong_lots
def add_payment(self, data):
@@ -989,7 +989,7 @@ class ReportSaleDetails(models.AbstractModel):
'total_paid': user_currency.round(total),
'payments': payments,
'company_name': self.env.user.company_id.name,
'taxes': taxes.values(),
'taxes': list(pycompat.values(taxes)),
'products': sorted([{
'product_id': product.id,
'product_name': product.name,
@@ -998,7 +998,7 @@ class ReportSaleDetails(models.AbstractModel):
'price_unit': price_unit,
'discount': discount,
'uom': product.uom_id.name
} for (product, price_unit, discount), qty in products_sold.items()], key=lambda l: l['product_name'])
} for (product, price_unit, discount), qty in pycompat.items(products_sold)], key=lambda l: l['product_name'])
}
@api.multi
+1 -1
View File
@@ -20,7 +20,7 @@ class PosInvoiceReport(models.AbstractModel):
not_invoiced_orders_ids = list(set(docids) - set(invoiced_posorders_ids))
if not_invoiced_orders_ids:
not_invoiced_posorders = PosOrder.browse(not_invoiced_orders_ids)
not_invoiced_orders_names = list(map(lambda a: a.name, not_invoiced_posorders))
not_invoiced_orders_names = [a.name for a in not_invoiced_posorders]
raise UserError(_('No link to an invoice for %s.') % ', '.join(not_invoiced_orders_names))
return self.env['ir.actions.report'].sudo().render_template('account.report_invoice', {'docs': self.env['account.invoice'].sudo().browse(ids_to_print)})
+16 -6
View File
@@ -8,6 +8,8 @@ from odoo.exceptions import UserError, ValidationError
import odoo.addons.decimal_precision as dp
from odoo.tools import pycompat
class Pricelist(models.Model):
_name = "product.pricelist"
@@ -19,7 +21,7 @@ class Pricelist(models.Model):
def _get_default_item_ids(self):
ProductPricelistItem = self.env['product.pricelist.item']
vals = ProductPricelistItem.default_get(ProductPricelistItem._fields.keys())
vals = ProductPricelistItem.default_get(list(ProductPricelistItem._fields))
vals.update(compute_price='formula')
return [[0, False, vals]]
@@ -88,7 +90,7 @@ class Pricelist(models.Model):
results = {}
for pricelist in pricelists:
subres = pricelist._compute_price_rule(products_qty_partner, date=date, uom_id=uom_id)
for product_id, price in subres.items():
for product_id, price in pycompat.items(subres):
results.setdefault(product_id, {})
results[product_id][pricelist.id] = price
return results
@@ -126,7 +128,7 @@ class Pricelist(models.Model):
while categ:
categ_ids[categ.id] = True
categ = categ.parent_id
categ_ids = categ_ids.keys()
categ_ids = list(categ_ids)
is_product_template = products[0]._name == "product.template"
if is_product_template:
@@ -251,7 +253,14 @@ class Pricelist(models.Model):
""" For a given pricelist, return price for products
Returns: dict{product_id: product price}, in the given pricelist """
self.ensure_one()
return dict((product_id, res_tuple[0]) for product_id, res_tuple in self._compute_price_rule(zip(products, quantities, partners), date=date, uom_id=uom_id).iteritems())
return {
product_id: res_tuple[0]
for product_id, res_tuple in pycompat.items(self._compute_price_rule(
list(pycompat.izip(products, quantities, partners)),
date=date,
uom_id=uom_id
))
}
def get_product_price(self, product, quantity, partner, date=False, uom_id=False):
""" For a given pricelist, return price for a given product """
@@ -272,7 +281,7 @@ class Pricelist(models.Model):
@api.multi
def price_get(self, prod_id, qty, partner=None):
""" Multi pricelist, mono product - returns price per pricelist """
return dict((key, price[0]) for key, price in self.price_rule_get(prod_id, qty, partner=partner).items())
return {key: price[0] for key, price in pycompat.items(self.price_rule_get(prod_id, qty, partner=partner))}
@api.multi
def price_rule_get_multi(self, products_by_qty_by_partner):
@@ -288,7 +297,8 @@ class Pricelist(models.Model):
@api.model
def _price_get_multi(self, pricelist, products_by_qty_by_partner):
""" Mono pricelist, multi product - return price per product """
return pricelist.get_products_price(zip(**products_by_qty_by_partner))
return pricelist.get_products_price(
list(pycompat.izip(**products_by_qty_by_partner)))
def _get_partner_pricelist(self, partner_id, company_id=None):
""" Retrieve the applicable pricelist for a given partner in a given company.
+1 -1
View File
@@ -27,7 +27,7 @@ class report_product_pricelist(models.AbstractModel):
}
def _get_quantity(self, data):
return sorted([data['form'][key] for key in data['form'].keys() if key.startswith('qty') and data['form'][key]])
return sorted([data['form'][key] for key in data['form'] if key.startswith('qty') and data['form'][key]])
def _get_categories(self, pricelist, products, quantities):
categ_data = []
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import datetime
from odoo import api, fields, models
from odoo.tools import pycompat
class StockProductionLot(models.Model):
@@ -31,10 +32,10 @@ class StockProductionLot(models.Model):
'removal_date': 'removal_time',
'alert_date': 'alert_time'
}
res = dict.fromkeys(mapped_fields.keys(), False)
res = dict.fromkeys(mapped_fields, False)
product = self.env['product.product'].browse(product_id) or self.product_id
if product:
for field in mapped_fields.keys():
for field in mapped_fields:
duration = getattr(product, mapped_fields[field])
if duration:
date = datetime.datetime.now() + datetime.timedelta(days=duration)
@@ -45,7 +46,7 @@ class StockProductionLot(models.Model):
@api.model
def create(self, vals):
dates = self._get_dates(vals.get('product_id'))
for d in dates.keys():
for d in dates:
if not vals.get(d):
vals[d] = dates[d]
return super(StockProductionLot, self).create(vals)
@@ -53,5 +54,5 @@ class StockProductionLot(models.Model):
@api.onchange('product_id')
def _onchange_product(self):
dates_dict = self._get_dates()
for field, value in dates_dict.items():
for field, value in pycompat.items(dates_dict):
setattr(self, field, value)
@@ -4,6 +4,8 @@
import time
from odoo import api, fields, models
from odoo.tools import pycompat
class ProductProduct(models.Model):
_inherit = "product.product"
@@ -67,8 +69,8 @@ class ProductProduct(models.Model):
prod_re[prod.id] = re_ind
re_ind += 1
res_val = tot_products._compute_product_margin_fields_values(field_names=[x for x in fields if fields in fields_list])
for key in res_val.keys():
for l in res_val[key].keys():
for key in res_val:
for l in res_val[key]:
re = res[prod_re[key]]
if re.get(l):
re[l] += res_val[key][l]
@@ -137,6 +139,6 @@ class ProductProduct(models.Model):
res[val.id]['expected_margin'] = res[val.id]['sale_expected'] - res[val.id]['normal_cost']
res[val.id]['total_margin_rate'] = res[val.id]['turnover'] and res[val.id]['total_margin'] * 100 / res[val.id]['turnover'] or 0.0
res[val.id]['expected_margin_rate'] = res[val.id]['sale_expected'] and res[val.id]['expected_margin'] * 100 / res[val.id]['sale_expected'] or 0.0
for k, v in res[val.id].items():
for k, v in pycompat.items(res[val.id]):
setattr(val, k, v)
return res
+4 -4
View File
@@ -614,7 +614,7 @@ class Task(models.Model):
email_list = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or ''))
# check left-part is not already an alias
aliases = self.mapped('project_id.alias_name')
return filter(lambda x: x.split('@')[0] not in aliases, email_list)
return [x for x in email_list if x.split('@')[0] not in aliases]
@api.model
def message_new(self, msg, custom_values=None):
@@ -630,7 +630,7 @@ class Task(models.Model):
task = super(Task, self).message_new(msg, custom_values=defaults)
email_list = task.email_split(msg)
partner_ids = filter(None, task._find_partner_from_emails(email_list, force_create=False))
partner_ids = [p for p in task._find_partner_from_emails(email_list, force_create=False) if p]
task.message_subscribe(partner_ids)
return task
@@ -655,7 +655,7 @@ class Task(models.Model):
pass
email_list = self.email_split(msg)
partner_ids = filter(None, self._find_partner_from_emails(email_list, force_create=False))
partner_ids = [p for p in self._find_partner_from_emails(email_list, force_create=False) if p]
self.message_subscribe(partner_ids)
return super(Task, self).message_update(msg, update_vals=update_vals)
@@ -677,7 +677,7 @@ class Task(models.Model):
except Exception:
pass
if self.project_id:
current_objects = filter(None, headers.get('X-Odoo-Objects', '').split(','))
current_objects = [h for h in headers.get('X-Odoo-Objects', '').split(',') if h]
current_objects.insert(0, 'project.project-%s, ' % self.project_id.id)
headers['X-Odoo-Objects'] = ','.join(current_objects)
if self.tag_ids:
+4 -4
View File
@@ -278,7 +278,7 @@ class ProjectIssue(models.Model):
def email_split(self, msg):
email_list = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or ''))
# check left-part is not already an alias
return filter(lambda x: x.split('@')[0] not in self.mapped('project_id.alias_name'), email_list)
return [x for x in email_list if x.split('@')[0] not in self.mapped('project_id.alias_name')]
@api.model
def message_new(self, msg, custom_values=None):
@@ -303,7 +303,7 @@ class ProjectIssue(models.Model):
issue = super(ProjectIssue, self.with_context(create_context)).message_new(msg, custom_values=defaults)
email_list = issue.email_split(msg)
partner_ids = filter(None, issue._find_partner_from_emails(email_list))
partner_ids = [p for p in issue._find_partner_from_emails(email_list) if p]
issue.message_subscribe(partner_ids)
return issue
@@ -311,7 +311,7 @@ class ProjectIssue(models.Model):
def message_update(self, msg, update_vals=None):
""" Override to update the issue according to the email. """
email_list = self.email_split(msg)
partner_ids = filter(None, self._find_partner_from_emails(email_list))
partner_ids = [p for p in self._find_partner_from_emails(email_list) if p]
self.message_subscribe(partner_ids)
return super(ProjectIssue, self).message_update(msg, update_vals=update_vals)
@@ -351,7 +351,7 @@ class ProjectIssue(models.Model):
except Exception:
pass
if self.project_id:
current_objects = filter(None, headers.get('X-Odoo-Objects', '').split(','))
current_objects = [h for h in headers.get('X-Odoo-Objects', '').split(',') if h]
current_objects.insert(0, 'project.project-%s, ' % self.project_id.id)
headers['X-Odoo-Objects'] = ','.join(current_objects)
if self.tag_ids:
+1 -1
View File
@@ -455,7 +455,7 @@ class PurchaseOrder(models.Model):
pick_ids = sum([order.picking_ids.ids for order in self], [])
#choose the view_mode accordingly
if len(pick_ids) > 1:
result['domain'] = "[('id','in',[" + ','.join(map(str, pick_ids)) + "])]"
result['domain'] = "[('id','in',[" + ','.join(str(id) for id in pick_ids) + "])]"
elif len(pick_ids) == 1:
res = self.env.ref('stock.view_picking_form', False)
result['views'] = [(res and res.id or False, 'form')]
+2 -1
View File
@@ -6,6 +6,7 @@ import uuid
from odoo import api, fields, models, tools, _
from odoo.modules.module import get_resource_path
from odoo.tools import pycompat
class Rating(models.Model):
@@ -214,7 +215,7 @@ class RatingMixin(models.AbstractModel):
values.update((d['rating'], d['rating_count']) for d in data)
# add other stats
if add_stats:
rating_number = sum(values.values())
rating_number = sum(pycompat.values(values))
result = {
'repartition': values,
'avg': sum(float(key * values[key]) for key in values) / rating_number if rating_number > 0 else 0,
+3 -2
View File
@@ -4,6 +4,7 @@
from datetime import timedelta
from odoo import api, fields, models
from odoo.tools import pycompat
from odoo.tools.safe_eval import safe_eval
@@ -73,13 +74,13 @@ class Project(models.Model):
domain = [('create_date', '>=', fields.Datetime.to_string(fields.datetime.now() - timedelta(days=30)))]
for project in self:
activity = project.tasks.rating_get_grades(domain)
project.percentage_satisfaction_project = activity['great'] * 100 / sum(activity.values()) if sum(activity.values()) else -1
project.percentage_satisfaction_project = activity['great'] * 100 / sum(pycompat.values(activity)) if sum(pycompat.values(activity)) else -1
@api.one
@api.depends('tasks.rating_ids.rating')
def _compute_percentage_satisfaction_task(self):
activity = self.tasks.rating_get_grades()
self.percentage_satisfaction_task = activity['great'] * 100 / sum(activity.values()) if sum(activity.values()) else -1
self.percentage_satisfaction_task = activity['great'] * 100 / sum(pycompat.values(activity)) if sum(pycompat.values(activity)) else -1
percentage_satisfaction_task = fields.Integer(
compute='_compute_percentage_satisfaction_task', string="Happy % on Task", store=True, default=-1)
@@ -4,6 +4,7 @@
from datetime import timedelta
from odoo import api, fields, models
from odoo.tools import pycompat
from odoo.tools.safe_eval import safe_eval
@@ -56,11 +57,11 @@ class Project(models.Model):
if project.use_tasks:
activity_task = project.tasks.rating_get_grades(domain)
activity_great = activity_task['great']
activity_sum = sum(activity_task.values())
activity_sum = sum(pycompat.values(activity_task))
if project.use_issues:
activity_issue = self.env['project.issue'].search([('project_id', '=', project.id)]).rating_get_grades(domain)
activity_great += activity_issue['great']
activity_sum += sum(activity_issue.values())
activity_sum += sum(pycompat.values(activity_issue))
project.percentage_satisfaction_project = activity_great * 100 / activity_sum if activity_sum else -1
@api.one
@@ -68,7 +69,7 @@ class Project(models.Model):
def _compute_percentage_satisfaction_issue(self):
project_issue = self.env['project.issue'].search([('project_id', '=', self.id)])
activity = project_issue.rating_get_grades()
self.percentage_satisfaction_issue = activity['great'] * 100 / sum(activity.values()) if sum(activity.values()) else -1
self.percentage_satisfaction_issue = activity['great'] * 100 / sum(pycompat.values(activity)) if sum(pycompat.values(activity)) else -1
percentage_satisfaction_issue = fields.Integer(compute='_compute_percentage_satisfaction_issue', string="Happy % on Issue", store=True, default=-1)
+1 -1
View File
@@ -247,7 +247,7 @@ class ResourceCalendar(models.Model):
""" Return the list of weekdays that contain at least one working
interval. """
self.ensure_one()
return list(set(map(int, (self.attendance_ids.mapped('dayofweek')))))
return list({int(d) for d in self.attendance_ids.mapped('dayofweek')})
@api.multi
def _get_next_work_day(self, day_date):
+1 -1
View File
@@ -92,4 +92,4 @@ class ResourceMixin(models.AbstractModel):
attendances = calendar._get_day_attendances(day_date, False, False)
if not attendances:
return 0
return sum(map(lambda i: float(i.hour_to) - float(i.hour_from), attendances))
return sum(float(i.hour_to) - float(i.hour_from) for i in attendances)
+2 -1
View File
@@ -3,6 +3,7 @@
from itertools import groupby
from odoo import api, fields, models, _
from odoo.tools import pycompat
class AccountInvoice(models.Model):
@@ -57,7 +58,7 @@ class AccountInvoice(models.Model):
result = super(AccountInvoice, self)._refund_cleanup_lines(lines)
if self.env.context.get('mode') == 'modify':
for i, line in enumerate(lines):
for name, field in line._fields.iteritems():
for name, field in pycompat.items(line._fields):
if name == 'sale_line_ids':
result[i][2][name] = [(6, 0, line[name].ids)]
line[name] = False
+2 -2
View File
@@ -21,7 +21,7 @@ class ResPartner(models.Model):
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 from the read up there
partner_ids = filter(lambda r: r['id'] == partner.id, partner_child_ids)[0]
partner_ids = [partner_ids.get('id')] + partner_ids.get('child_ids')
item = next(p for p in partner_child_ids if p['id'] == partner.id)
partner_ids = [partner.id] + item.get('child_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)
+5 -5
View File
@@ -6,7 +6,7 @@ from datetime import datetime, timedelta
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import float_is_zero, float_compare, DEFAULT_SERVER_DATETIME_FORMAT
from odoo.tools import float_is_zero, float_compare, DEFAULT_SERVER_DATETIME_FORMAT, pycompat
from odoo.tools.misc import formatLang
import odoo.addons.decimal_precision as dp
@@ -362,7 +362,7 @@ class SaleOrder(models.Model):
if not invoices:
raise UserError(_('There is no invoicable line.'))
for invoice in invoices.values():
for invoice in pycompat.values(invoices):
if not invoice.invoice_line_ids:
raise UserError(_('There is no invoicable line.'))
# If invoice is negative, do a refund invoice instead
@@ -379,7 +379,7 @@ class SaleOrder(models.Model):
invoice.message_post_with_view('mail.message_origin_link',
values={'self': invoice, 'origin': references[invoice]},
subtype_id=self.env.ref('mail.mt_note').id)
return [inv.id for inv in invoices.values()]
return [inv.id for inv in pycompat.values(invoices)]
@api.multi
def action_draft(self):
@@ -517,8 +517,8 @@ class SaleOrder(models.Model):
if tax.include_base_amount:
base_tax += tax.compute_all(line.price_reduce + base_tax, quantity=1, product=line.product_id,
partner=self.partner_shipping_id)['taxes'][0]['amount']
res = sorted(res.items(), key=lambda l: l[0].sequence)
res = map(lambda l: (l[0].name, l[1]), res)
res = sorted(pycompat.items(res), key=lambda l: l[0].sequence)
res = [(l[0].name, l[1]) for l in res]
return res
+3 -1
View File
@@ -3,6 +3,8 @@
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import pycompat
class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
@@ -46,7 +48,7 @@ class SaleOrderLine(models.Model):
qty = d['unit_amount']
lines[line] += qty
for line, qty in lines.items():
for line, qty in pycompat.items(lines):
line.qty_delivered = qty
return True
+6 -5
View File
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.exceptions import UserError, AccessError
from odoo.tools import pycompat
from .test_sale_common import TestSale
@@ -17,10 +18,10 @@ class TestSaleOrder(TestSale):
'partner_id': self.partner.id,
'partner_invoice_id': self.partner.id,
'partner_shipping_id': self.partner.id,
'order_line': [(0, 0, {'name': p.name, 'product_id': p.id, 'product_uom_qty': 2, 'product_uom': p.uom_id.id, 'price_unit': p.list_price}) for (_, p) in self.products.iteritems()],
'order_line': [(0, 0, {'name': p.name, 'product_id': p.id, 'product_uom_qty': 2, 'product_uom': p.uom_id.id, 'price_unit': p.list_price}) for (_, p) in pycompat.items(self.products)],
'pricelist_id': self.env.ref('product.list0').id,
})
self.assertEqual(so.amount_total, sum([2 * p.list_price for (k, p) in self.products.iteritems()]), 'Sale: total amount is wrong')
self.assertEqual(so.amount_total, sum([2 * p.list_price for (k, p) in pycompat.items(self.products)]), 'Sale: total amount is wrong')
# send quotation
so.force_quotation_send()
@@ -35,7 +36,7 @@ class TestSaleOrder(TestSale):
inv_id = so.action_invoice_create()
inv = inv_obj.browse(inv_id)
self.assertEqual(len(inv.invoice_line_ids), 2, 'Sale: invoice is missing lines')
self.assertEqual(inv.amount_total, sum([2 * p.list_price if p.invoice_policy == 'order' else 0 for (k, p) in self.products.iteritems()]), 'Sale: invoice total amount is wrong')
self.assertEqual(inv.amount_total, sum([2 * p.list_price if p.invoice_policy == 'order' else 0 for (k, p) in pycompat.items(self.products)]), 'Sale: invoice total amount is wrong')
self.assertTrue(so.invoice_status == 'no', 'Sale: SO status after invoicing should be "nothing to invoice"')
self.assertTrue(len(so.invoice_ids) == 1, 'Sale: invoice is missing')
@@ -46,7 +47,7 @@ class TestSaleOrder(TestSale):
inv_id = so.action_invoice_create()
inv = inv_obj.browse(inv_id)
self.assertEqual(len(inv.invoice_line_ids), 2, 'Sale: second invoice is missing lines')
self.assertEqual(inv.amount_total, sum([2 * p.list_price if p.invoice_policy == 'delivery' else 0 for (k, p) in self.products.iteritems()]), 'Sale: second invoice total amount is wrong')
self.assertEqual(inv.amount_total, sum([2 * p.list_price if p.invoice_policy == 'delivery' else 0 for (k, p) in pycompat.items(self.products)]), 'Sale: second invoice total amount is wrong')
self.assertTrue(so.invoice_status == 'invoiced', 'Sale: SO status after invoicing everything should be "invoiced"')
self.assertTrue(len(so.invoice_ids) == 2, 'Sale: invoice is missing')
# go over the sold quantity
@@ -71,7 +72,7 @@ class TestSaleOrder(TestSale):
'partner_id': self.partner.id,
'partner_invoice_id': self.partner.id,
'partner_shipping_id': self.partner.id,
'order_line': [(0, 0, {'name': p.name, 'product_id': p.id, 'product_uom_qty': 2, 'product_uom': p.uom_id.id, 'price_unit': p.list_price}) for (_, p) in self.products.iteritems()],
'order_line': [(0, 0, {'name': p.name, 'product_id': p.id, 'product_uom_qty': 2, 'product_uom': p.uom_id.id, 'price_unit': p.list_price}) for (_, p) in pycompat.items(self.products)],
'pricelist_id': self.env.ref('product.list0').id,
})
@@ -47,7 +47,7 @@ class TestSaleExpense(TestSale):
# Create Expense Entries
sheet.action_sheet_move_create()
# expense should now be in sales order
self.assertTrue(prod_exp_1 in map(lambda so: so.product_id, so.order_line), 'Sale Expense: expense product should be in so')
self.assertIn(prod_exp_1, so.mapped('order_line.product_id'), 'Sale Expense: expense product should be in so')
sol = so.order_line.filtered(lambda sol: sol.product_id.id == prod_exp_1.id)
self.assertEqual((sol.price_unit, sol.qty_delivered), (621.54, 1.0), 'Sale Expense: error when invoicing an expense at cost')
self.assertEqual(so.amount_total, init_price, 'Sale Expense: price of so not updated after adding expense')
@@ -76,7 +76,7 @@ class TestSaleExpense(TestSale):
# Create Expense Entries
sheet.action_sheet_move_create()
# expense should now be in sales order
self.assertTrue(prod_exp_2 in map(lambda so: so.product_id, so.order_line), 'Sale Expense: expense product should be in so')
self.assertIn(prod_exp_2, so.mapped('order_line.product_id'), 'Sale Expense: expense product should be in so')
sol = so.order_line.filtered(lambda sol: sol.product_id.id == prod_exp_2.id)
self.assertEqual((sol.price_unit, sol.qty_delivered), (prod_exp_2.list_price, 100.0), 'Sale Expense: error when invoicing an expense at cost')
self.assertEqual(so.amount_total, init_price, 'Sale Expense: price of so not updated after adding expense')

Some files were not shown because too many files have changed in this diff Show More