diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index 0f1ea103ecb..b9f1cf4dd91 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -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'):
diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py
index 0a510fe0c9c..73a4102311e 100644
--- a/addons/account/models/account_bank_statement.py
+++ b/addons/account/models/account_bank_statement.py
@@ -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
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index eb744bf5c11..79a093d2203 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -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
diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py
index bf060df7344..89b2f8ef327 100644
--- a/addons/account/models/account_journal_dashboard.py
+++ b/addons/account/models/account_journal_dashboard.py
@@ -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,)
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 99c1b61d636..c7207a9e87b 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -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
diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py
index 83ecd07ea89..c8245752e4f 100644
--- a/addons/account/models/chart_template.py
+++ b/addons/account/models/chart_template.py
@@ -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),
diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py
index e826524eb57..72976451e8c 100644
--- a/addons/account/models/partner.py
+++ b/addons/account/models/partner.py
@@ -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
diff --git a/addons/account/report/account_balance.py b/addons/account/report/account_balance.py
index dc717c2829b..c4b2568bfa5 100644
--- a/addons/account/report/account_balance.py
+++ b/addons/account/report/account_balance.py
@@ -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')
diff --git a/addons/account/report/account_general_ledger.py b/addons/account/report/account_general_ledger.py
index 2dde8c979bc..3f08209b129 100644
--- a/addons/account/report/account_general_ledger.py
+++ b/addons/account/report/account_general_ledger.py
@@ -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:
diff --git a/addons/account/report/account_journal.py b/addons/account/report/account_journal.py
index 401ce8856d5..4dfe7d970e5 100644
--- a/addons/account/report/account_journal.py
+++ b/addons/account/report/account_journal.py
@@ -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):
diff --git a/addons/account/report/account_overdue_report.py b/addons/account/report/account_overdue_report.py
index 2fa1df2ff6c..dbfd933c99a 100644
--- a/addons/account/report/account_overdue_report.py
+++ b/addons/account/report/account_overdue_report.py
@@ -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) "
diff --git a/addons/account/report/account_report_financial.py b/addons/account/report/account_report_financial.py
index 53981c78364..bc6f9b53f1f 100644
--- a/addons/account/report/account_report_financial.py
+++ b/addons/account/report/account_report_financial.py
@@ -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...)
diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py
index ad316e2b0a2..50012321db5 100644
--- a/addons/account/tests/test_reconciliation.py
+++ b/addons/account/tests/test_reconciliation.py
@@ -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'])
\ No newline at end of file
+ self.assertEquals(aml.currency_id.id, line['currency_id'])
diff --git a/addons/account_asset/models/account_asset.py b/addons/account_asset/models/account_asset.py
index 6a49e33f547..103f0595524 100644
--- a/addons/account_asset/models/account_asset.py
+++ b/addons/account_asset/models/account_asset.py
@@ -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 = '%s' % message_description
- for name, values in tracked_values.iteritems():
+ for name, values in pycompat.items(tracked_values):
message += '
• %s: ' % name
message += '%s
' % values
return message
diff --git a/addons/account_asset/wizard/asset_depreciation_confirmation_wizard.py b/addons/account_asset/wizard/asset_depreciation_confirmation_wizard.py
index 00b3fd3e36e..4ba3245871f 100644
--- a/addons/account_asset/wizard/asset_depreciation_confirmation_wizard.py
+++ b/addons/account_asset/wizard/asset_depreciation_confirmation_wizard.py
@@ -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',
}
diff --git a/addons/account_test/report/report_account_test.py b/addons/account_test/report/report_account_test.py
index d2ba59edf2f..ac64c1faea0 100644
--- a/addons/account_test/report/report_account_test.py
+++ b/addons/account_test/report/report_account_test.py
@@ -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,
diff --git a/addons/account_voucher/models/account_voucher.py b/addons/account_voucher/models/account_voucher.py
index 0f120e000d8..5cf492c7654 100644
--- a/addons/account_voucher/models/account_voucher.py
+++ b/addons/account_voucher/models/account_voucher.py
@@ -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):
diff --git a/addons/anonymization/models/anonymization.py b/addons/anonymization/models/anonymization.py
index 65928db4b0a..2d4fe1b17d2 100644
--- a/addons/anonymization/models/anonymization.py
+++ b/addons/anonymization/models/anonymization.py
@@ -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)
diff --git a/addons/anonymization/wizard/anonymize_wizard.py b/addons/anonymization/wizard/anonymize_wizard.py
index a0b355d4330..b9c35483e18 100644
--- a/addons/anonymization/wizard/anonymize_wizard.py
+++ b/addons/anonymization/wizard/anonymize_wizard.py
@@ -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'))
diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py
index 6c1b0facad3..db636797d12 100644
--- a/addons/auth_signup/controllers/main.py
+++ b/addons/auth_signup/controllers/main.py
@@ -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:
diff --git a/addons/auth_signup/models/res_partner.py b/addons/auth_signup/models/res_partner.py
index 99e0b98c5ea..8527ad6e33d 100644
--- a/addons/auth_signup/models/res_partner.py
+++ b/addons/auth_signup/models/res_partner.py
@@ -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)
diff --git a/addons/base_address_extended/models/base_address_extended.py b/addons/base_address_extended/models/base_address_extended.py
index 0fcfceca399..f8ec7979ab8 100644
--- a/addons/base_address_extended/models/base_address_extended.py
+++ b/addons/base_address_extended/models/base_address_extended.py
@@ -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
diff --git a/addons/base_automation/models/base_automation.py b/addons/base_automation/models/base_automation.py
index c9c0a150364..d99d8c58a13 100644
--- a/addons/base_automation/models/base_automation.py
+++ b/addons/base_automation/models/base_automation.py
@@ -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:
diff --git a/addons/base_gengo/wizard/base_gengo_translations.py b/addons/base_gengo/wizard/base_gengo_translations.py
index d6e5ce9176a..98df9356576 100644
--- a/addons/base_gengo/wizard/base_gengo_translations.py
+++ b/addons/base_gengo/wizard/base_gengo_translations.py
@@ -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
diff --git a/addons/base_geolocalize/models/res_partner.py b/addons/base_geolocalize/models/res_partner.py
index 2cc4ac92ab8..916f6c0d9e0 100644
--- a/addons/base_geolocalize/models/res_partner.py
+++ b/addons/base_geolocalize/models/res_partner.py
@@ -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):
diff --git a/addons/base_import/models/base_import.py b/addons/base_import/models/base_import.py
index 996b91b0349..b09056f2ff1 100644
--- a/addons/base_import/models/base_import.py
+++ b/addons/base_import/models/base_import.py
@@ -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)
diff --git a/addons/base_import/models/odf_ods_reader.py b/addons/base_import/models/odf_ods_reader.py
index 0f0142f3f04..c2613c6baa5 100644
--- a/addons/base_import/models/odf_ods_reader.py
+++ b/addons/base_import/models/odf_ods_reader.py
@@ -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)))
diff --git a/addons/base_import/tests/test_base_import.py b/addons/base_import/tests/test_base_import.py
index 0502af6d0ab..2d1d850d3a9 100644
--- a/addons/base_import/tests/test_base_import.py
+++ b/addons/base_import/tests/test_base_import.py
@@ -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):
diff --git a/addons/base_import_module/models/ir_module.py b/addons/base_import_module/models/ir_module.py
index 9a703b40978..5219bc240d5 100644
--- a/addons/base_import_module/models/ir_module.py
+++ b/addons/base_import_module/models/ir_module.py
@@ -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
diff --git a/addons/base_import_module/models/ir_ui_view.py b/addons/base_import_module/models/ir_ui_view.py
index 871efe42c96..adaa6651f5b 100644
--- a/addons/base_import_module/models/ir_ui_view.py
+++ b/addons/base_import_module/models/ir_ui_view.py
@@ -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
diff --git a/addons/base_vat/models/res_partner.py b/addons/base_vat/models/res_partner.py
index 1d051bcfd82..8c57a0cf3c4 100644
--- a/addons/base_vat/models/res_partner.py
+++ b/addons/base_vat/models/res_partner.py
@@ -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
diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py
index 98f5e5c95d9..740f6281e9a 100644
--- a/addons/calendar/models/calendar.py
+++ b/addons/calendar/models/calendar.py
@@ -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:
diff --git a/addons/calendar/models/mail_message.py b/addons/calendar/models/mail_message.py
index df6810ebbf3..421b070dcb7 100644
--- a/addons/calendar/models/mail_message.py
+++ b/addons/calendar/models/mail_message.py
@@ -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)
diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py
index a8145e302ef..c50b2ed9b1f 100644
--- a/addons/crm/models/crm_lead.py
+++ b/addons/crm/models/crm_lead.py
@@ -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)
diff --git a/addons/crm/wizard/base_partner_merge.py b/addons/crm/wizard/base_partner_merge.py
index 27f3f3e002d..ccfa6d3e692 100644
--- a/addons/crm/wizard/base_partner_merge.py
+++ b/addons/crm/wizard/base_partner_merge.py
@@ -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
diff --git a/addons/event/models/event.py b/addons/event/models/event.py
index c5c3e50a157..ccec10601b1 100644
--- a/addons/event/models/event.py
+++ b/addons/event/models/event.py
@@ -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
diff --git a/addons/event/models/event_mail.py b/addons/event/models/event_mail.py
index 4db265d0091..d87c24dd663 100644
--- a/addons/event/models/event_mail.py
+++ b/addons/event/models/event_mail.py
@@ -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
diff --git a/addons/fleet/models/fleet_vehicle_cost.py b/addons/fleet/models/fleet_vehicle_cost.py
index 827863e3adc..12c6a7d7f2e 100644
--- a/addons/fleet/models/fleet_vehicle_cost.py
+++ b/addons/fleet/models/fleet_vehicle_cost.py
@@ -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'})
diff --git a/addons/gamification/models/goal.py b/addons/gamification/models/goal.py
index 5a306ae9a33..89f037d55d3 100644
--- a/addons/gamification/models/goal.py
+++ b/addons/gamification/models/goal.py
@@ -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)
diff --git a/addons/google_calendar/models/calendar.py b/addons/google_calendar/models/calendar.py
index bb87f2f7e48..ce8e50f6852 100644
--- a/addons/google_calendar/models/calendar.py
+++ b/addons/google_calendar/models/calendar.py
@@ -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)
diff --git a/addons/google_calendar/models/google_calendar.py b/addons/google_calendar/models/google_calendar.py
index 169368b9bd5..d8e136faf53 100644
--- a/addons/google_calendar/models/google_calendar.py
+++ b/addons/google_calendar/models/google_calendar.py
@@ -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 !
diff --git a/addons/hr/models/hr.py b/addons/hr/models/hr.py
index efbcb03a5c3..7bd91ccfa95 100644
--- a/addons/hr/models/hr.py
+++ b/addons/hr/models/hr.py
@@ -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
diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py
index facef9e2f6f..4c442c6f09c 100644
--- a/addons/hr_expense/models/hr_expense.py
+++ b/addons/hr_expense/models/hr_expense.py
@@ -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
diff --git a/addons/hr_holidays/models/hr_holidays.py b/addons/hr_holidays/models/hr_holidays.py
index ca6e4fa9362..ce3b7d1a827 100644
--- a/addons/hr_holidays/models/hr_holidays.py
+++ b/addons/hr_holidays/models/hr_holidays.py
@@ -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
diff --git a/addons/hr_payroll/models/hr_payslip.py b/addons/hr_payroll/models/hr_payslip.py
index 894dfa91a35..fd30b7a1b23 100644
--- a/addons/hr_payroll/models/hr_payslip.py
+++ b/addons/hr_payroll/models/hr_payslip.py
@@ -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,
diff --git a/addons/hr_payroll/report/report_payslip_details.py b/addons/hr_payroll/report/report_payslip_details.py
index 5789e07d158..23043d2edb9 100644
--- a/addons/hr_payroll/report/report_payslip_details.py
+++ b/addons/hr_payroll/report/report_payslip_details.py
@@ -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')),
diff --git a/addons/hr_recruitment/models/hr_recruitment.py b/addons/hr_recruitment/models/hr_recruitment.py
index 9302b9a5ce5..3080f328457 100644
--- a/addons/hr_recruitment/models/hr_recruitment.py
+++ b/addons/hr_recruitment/models/hr_recruitment.py
@@ -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'):
diff --git a/addons/hw_blackbox_be/controllers/main.py b/addons/hw_blackbox_be/controllers/main.py
index 616f70346ef..89d14af6153 100644
--- a/addons/hw_blackbox_be/controllers/main.py
+++ b/addons/hw_blackbox_be/controllers/main.py
@@ -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)
diff --git a/addons/hw_escpos/escpos/escpos.py b/addons/hw_escpos/escpos/escpos.py
index b0294d0f815..74c324a303e 100644
--- a/addons/hw_escpos/escpos/escpos.py
+++ b/addons/hw_escpos/escpos/escpos.py
@@ -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
diff --git a/addons/im_livechat/models/im_livechat_channel.py b/addons/im_livechat/models/im_livechat_channel.py
index c2f0e8cb3c2..85ca245c32c 100644
--- a/addons/im_livechat/models/im_livechat_channel.py
+++ b/addons/im_livechat/models/im_livechat_channel.py
@@ -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
diff --git a/addons/im_livechat/models/mail_channel.py b/addons/im_livechat/models/mail_channel.py
index 3f6c47ba5ce..5333b7ed4cb 100644
--- a/addons/im_livechat/models/mail_channel.py
+++ b/addons/im_livechat/models/mail_channel.py
@@ -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):
diff --git a/addons/l10n_in_hr_payroll/report/report_hr_yearly_salary_detail.py b/addons/l10n_in_hr_payroll/report/report_hr_yearly_salary_detail.py
index cdc82c23852..0464f8249ed 100644
--- a/addons/l10n_in_hr_payroll/report/report_hr_yearly_salary_detail.py
+++ b/addons/l10n_in_hr_payroll/report/report_hr_yearly_salary_detail.py
@@ -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)
diff --git a/addons/l10n_lu/scripts/tax2csv.py b/addons/l10n_lu/scripts/tax2csv.py
index 710ebea9d88..9993272573f 100644
--- a/addons/l10n_lu/scripts/tax2csv.py
+++ b/addons/l10n_lu/scripts/tax2csv.py
@@ -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__':
diff --git a/addons/link_tracker/models/link_tracker.py b/addons/link_tracker/models/link_tracker.py
index 9d688121423..12dcd46c140 100644
--- a/addons/link_tracker/models/link_tracker.py
+++ b/addons/link_tracker/models/link_tracker.py
@@ -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)
diff --git a/addons/lunch/models/lunch.py b/addons/lunch/models/lunch.py
index a3ec3e7f51b..c94dfd21d84 100644
--- a/addons/lunch/models/lunch.py
+++ b/addons/lunch/models/lunch.py
@@ -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 = []
diff --git a/addons/mail/models/html2text.py b/addons/mail/models/html2text.py
index 11844ef7720..5e2fb25150f 100755
--- a/addons/mail/models/html2text.py
+++ b/addons/mail/models/html2text.py
@@ -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
diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py
index 6012dc9c22b..fac6edfff98 100644
--- a/addons/mail/models/mail_channel.py
+++ b/addons/mail/models/mail_channel.py
@@ -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
diff --git a/addons/mail/models/mail_followers.py b/addons/mail/models/mail_followers.py
index ce169c91127..a6bd60f82c9 100644
--- a/addons/mail/models/mail_followers.py
+++ b/addons/mail/models/mail_followers.py
@@ -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:
diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py
index 3ac2285fab3..4d8f0ca0925 100644
--- a/addons/mail/models/mail_mail.py
+++ b/addons/mail/models/mail_mail.py
@@ -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
diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py
index ac2183d767a..18d73ecb4dd 100644
--- a/addons/mail/models/mail_message.py
+++ b/addons/mail/models/mail_message.py
@@ -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
diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py
index 45e562889f7..ca426189b38 100644
--- a/addons/mail/models/mail_template.py
+++ b/addons/mail/models/mail_template.py
@@ -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']):
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 6d3b1f0fcc3..b07adf60608 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -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))
diff --git a/addons/mail/models/res_partner.py b/addons/mail/models/res_partner.py
index 4958c043bb7..02cb12c07c7 100644
--- a/addons/mail/models/res_partner.py
+++ b/addons/mail/models/res_partner.py
@@ -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
diff --git a/addons/mail/tests/test_mail_followers.py b/addons/mail/tests/test_mail_followers.py
index 85e122bdb48..7e98bbea2c0 100644
--- a/addons/mail/tests/test_mail_followers.py
+++ b/addons/mail/tests/test_mail_followers.py
@@ -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]))
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
index 92822154ff3..76412ce041c 100644
--- a/addons/mail/wizard/mail_compose_message.py
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -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
diff --git a/addons/marketing_campaign/models/marketing_campaign.py b/addons/marketing_campaign/models/marketing_campaign.py
index a6a3acf303e..70a7b0c07af 100644
--- a/addons/marketing_campaign/models/marketing_campaign.py
+++ b/addons/marketing_campaign/models/marketing_campaign.py
@@ -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)))]
diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py
index cc3519ef737..8a683be0994 100644
--- a/addons/mass_mailing/models/mass_mailing.py
+++ b/addons/mass_mailing/models/mass_mailing.py
@@ -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
diff --git a/addons/mrp/models/mrp_workcenter.py b/addons/mrp/models/mrp_workcenter.py
index 0dc9cdc96af..49d87c2fe3c 100644
--- a/addons/mrp/models/mrp_workcenter.py
+++ b/addons/mrp/models/mrp_workcenter.py
@@ -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)
diff --git a/addons/mrp/models/stock_picking.py b/addons/mrp/models/stock_picking.py
index 4bd3c1b0083..627859d1a8e 100644
--- a/addons/mrp/models/stock_picking.py
+++ b/addons/mrp/models/stock_picking.py
@@ -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)
diff --git a/addons/mrp/wizard/mrp_product_produce.py b/addons/mrp/wizard/mrp_product_produce.py
index 0bb35fe9c90..70089f812c6 100644
--- a/addons/mrp/wizard/mrp_product_produce.py
+++ b/addons/mrp/wizard/mrp_product_produce.py
@@ -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')
diff --git a/addons/mrp_repair/wizard/mrp_repair_make_invoice.py b/addons/mrp_repair/wizard/mrp_repair_make_invoice.py
index 000e77171dd..a659e45e962 100644
--- a/addons/mrp_repair/wizard/mrp_repair_make_invoice.py
+++ b/addons/mrp_repair/wizard/mrp_repair_make_invoice.py
@@ -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',
diff --git a/addons/pad/models/pad.py b/addons/pad/models/pad.py
index dfa1dbf859b..1c8f08e79bd 100644
--- a/addons/pad/models/pad.py
+++ b/addons/pad/models/pad.py
@@ -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')
diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py
index bb98284f9f2..ae990799b38 100644
--- a/addons/payment/models/payment_acquirer.py
+++ b/addons/payment/models/payment_acquirer.py
@@ -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=""' attribute, then it
required if record.provider is . """
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)
diff --git a/addons/payment_adyen/models/payment.py b/addons/payment_adyen/models/payment.py
index b54adc58a9c..8b2d8944813 100644
--- a/addons/payment_adyen/models/payment.py
+++ b/addons/payment_adyen/models/payment.py
@@ -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)
diff --git a/addons/payment_authorize/models/authorize_request.py b/addons/payment_authorize/models/authorize_request.py
index 6eef3ae80cc..75a54d93526 100644
--- a/addons/payment_authorize/models/authorize_request.py
+++ b/addons/payment_authorize/models/authorize_request.py
@@ -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
diff --git a/addons/payment_authorize/tests/test_authorize.py b/addons/payment_authorize/tests/test_authorize.py
index 50e4e95fdc4..c16113cd52b 100644
--- a/addons/payment_authorize/tests/test_authorize.py
+++ b/addons/payment_authorize/tests/test_authorize.py
@@ -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(
diff --git a/addons/payment_buckaroo/controllers/main.py b/addons/payment_buckaroo/controllers/main.py
index e5e61f6e8f3..5af6a454b47 100644
--- a/addons/payment_buckaroo/controllers/main.py
+++ b/addons/payment_buckaroo/controllers/main.py
@@ -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)
diff --git a/addons/payment_buckaroo/models/payment.py b/addons/payment_buckaroo/models/payment.py
index 2235226769f..e46f078c134 100644
--- a/addons/payment_buckaroo/models/payment.py
+++ b/addons/payment_buckaroo/models/payment.py
@@ -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)
diff --git a/addons/payment_ogone/models/payment.py b/addons/payment_ogone/models/payment.py
index dbd17233e58..34cf982c82e 100644
--- a/addons/payment_ogone/models/payment.py
+++ b/addons/payment_ogone/models/payment.py
@@ -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()
diff --git a/addons/payment_paypal/controllers/main.py b/addons/payment_paypal/controllers/main.py
index 00359c4d038..768cb8062a3 100644
--- a/addons/payment_paypal/controllers/main.py
+++ b/addons/payment_paypal/controllers/main.py
@@ -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 = {}
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index f4916a42ca8..ebcddbfcfdf 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -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
diff --git a/addons/point_of_sale/report/pos_invoice.py b/addons/point_of_sale/report/pos_invoice.py
index aeafe819fe5..55482125444 100644
--- a/addons/point_of_sale/report/pos_invoice.py
+++ b/addons/point_of_sale/report/pos_invoice.py
@@ -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)})
diff --git a/addons/product/models/product_pricelist.py b/addons/product/models/product_pricelist.py
index c4dc828b540..a5e5d92fd2b 100644
--- a/addons/product/models/product_pricelist.py
+++ b/addons/product/models/product_pricelist.py
@@ -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.
diff --git a/addons/product/report/product_pricelist.py b/addons/product/report/product_pricelist.py
index 85555085df5..91f72d6cadd 100644
--- a/addons/product/report/product_pricelist.py
+++ b/addons/product/report/product_pricelist.py
@@ -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 = []
diff --git a/addons/product_expiry/models/production_lot.py b/addons/product_expiry/models/production_lot.py
index cfaf7984d28..64366c901c4 100644
--- a/addons/product_expiry/models/production_lot.py
+++ b/addons/product_expiry/models/production_lot.py
@@ -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)
diff --git a/addons/product_margin/models/product_product.py b/addons/product_margin/models/product_product.py
index fa65e79b38e..482d9b688f5 100644
--- a/addons/product_margin/models/product_product.py
+++ b/addons/product_margin/models/product_product.py
@@ -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
diff --git a/addons/project/models/project.py b/addons/project/models/project.py
index 5c8447344f8..2d243e7de78 100644
--- a/addons/project/models/project.py
+++ b/addons/project/models/project.py
@@ -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:
diff --git a/addons/project_issue/models/project_issue.py b/addons/project_issue/models/project_issue.py
index 5e4a8757d68..5513e4393ee 100644
--- a/addons/project_issue/models/project_issue.py
+++ b/addons/project_issue/models/project_issue.py
@@ -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:
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index ffd1c1fa559..2003cb7bbfd 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -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')]
diff --git a/addons/rating/models/rating.py b/addons/rating/models/rating.py
index 3bc5491b6ef..8126cc5bb23 100644
--- a/addons/rating/models/rating.py
+++ b/addons/rating/models/rating.py
@@ -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,
diff --git a/addons/rating_project/models/project.py b/addons/rating_project/models/project.py
index 2a8336addaf..7fd98913b8d 100644
--- a/addons/rating_project/models/project.py
+++ b/addons/rating_project/models/project.py
@@ -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)
diff --git a/addons/rating_project_issue/models/project_issue.py b/addons/rating_project_issue/models/project_issue.py
index 028d1a3af14..47704a347fd 100644
--- a/addons/rating_project_issue/models/project_issue.py
+++ b/addons/rating_project_issue/models/project_issue.py
@@ -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)
diff --git a/addons/resource/models/resource.py b/addons/resource/models/resource.py
index 32da153366d..de4e5617a53 100644
--- a/addons/resource/models/resource.py
+++ b/addons/resource/models/resource.py
@@ -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):
diff --git a/addons/resource/models/resource_mixin.py b/addons/resource/models/resource_mixin.py
index de5a9b5ecb4..8b922dd5ff8 100644
--- a/addons/resource/models/resource_mixin.py
+++ b/addons/resource/models/resource_mixin.py
@@ -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)
diff --git a/addons/sale/models/account_invoice.py b/addons/sale/models/account_invoice.py
index dcd930d3eb1..d0b11a6ac02 100644
--- a/addons/sale/models/account_invoice.py
+++ b/addons/sale/models/account_invoice.py
@@ -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
diff --git a/addons/sale/models/res_partner.py b/addons/sale/models/res_partner.py
index 27a70360d45..cb80ca6cf35 100644
--- a/addons/sale/models/res_partner.py
+++ b/addons/sale/models/res_partner.py
@@ -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)
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index 45bf0617b33..9582cc7579f 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -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
diff --git a/addons/sale/models/sale_analytic.py b/addons/sale/models/sale_analytic.py
index ef58a8e44aa..cad770f842d 100644
--- a/addons/sale/models/sale_analytic.py
+++ b/addons/sale/models/sale_analytic.py
@@ -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
diff --git a/addons/sale/tests/test_sale_order.py b/addons/sale/tests/test_sale_order.py
index 4e3cbabc9fb..a201e66185e 100644
--- a/addons/sale/tests/test_sale_order.py
+++ b/addons/sale/tests/test_sale_order.py
@@ -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,
})
diff --git a/addons/sale_expense/tests/test_sale_expense.py b/addons/sale_expense/tests/test_sale_expense.py
index 2dd9dba7726..bc8d314206e 100644
--- a/addons/sale_expense/tests/test_sale_expense.py
+++ b/addons/sale_expense/tests/test_sale_expense.py
@@ -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')
diff --git a/addons/sale_mrp/models/procurement.py b/addons/sale_mrp/models/procurement.py
index a00176ddfef..36f814c9385 100644
--- a/addons/sale_mrp/models/procurement.py
+++ b/addons/sale_mrp/models/procurement.py
@@ -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 ProcurementOrder(models.Model):
@@ -11,7 +12,7 @@ class ProcurementOrder(models.Model):
def make_mo(self):
""" override method to set link in production created from sale order."""
res = super(ProcurementOrder, self).make_mo()
- for procurement_id, production_id in res.items():
+ for procurement_id, production_id in pycompat.items(res):
if production_id:
production = self.env['mrp.production'].browse(production_id)
move = production._get_parent_move(production.move_finished_ids[0])
diff --git a/addons/sale_mrp/sale_mrp.py b/addons/sale_mrp/sale_mrp.py
index 8aa4074c8bb..846c6e656f6 100644
--- a/addons/sale_mrp/sale_mrp.py
+++ b/addons/sale_mrp/sale_mrp.py
@@ -68,7 +68,7 @@ class AccountInvoiceLine(models.Model):
if bom.type == 'phantom':
average_price_unit = 0
components = s_line._get_bom_component_qty(bom)
- for product_id in components.keys():
+ for product_id in components:
factor = components[product_id]['qty']
prod_moves = [m for m in moves if m.product_id.id == product_id]
prod_qty_done = factor * qty_done
diff --git a/addons/sale_stock/tests/test_sale_stock.py b/addons/sale_stock/tests/test_sale_stock.py
index 6b71bb07975..0d4b11489d1 100644
--- a/addons/sale_stock/tests/test_sale_stock.py
+++ b/addons/sale_stock/tests/test_sale_stock.py
@@ -3,6 +3,7 @@
from odoo.addons.sale.tests.test_sale_common import TestSale
from odoo.exceptions import UserError
+from odoo.tools import pycompat
class TestSaleStock(TestSale):
@@ -16,7 +17,7 @@ class TestSaleStock(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,
'picking_policy': 'direct',
})
@@ -73,7 +74,7 @@ class TestSaleStock(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,
'picking_policy': 'direct',
})
diff --git a/addons/stock/models/procurement.py b/addons/stock/models/procurement.py
index 7ce3bb2abed..8cf07b02c36 100644
--- a/addons/stock/models/procurement.py
+++ b/addons/stock/models/procurement.py
@@ -9,7 +9,8 @@ from psycopg2 import OperationalError
from odoo import api, fields, models, registry, _
from odoo.osv import expression
-from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_compare, float_round
+from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_compare, \
+ float_round, pycompat
import logging
@@ -307,7 +308,7 @@ class ProcurementOrder(models.Model):
location_data[key]['orderpoints'] += orderpoint
location_data[key]['groups'] = self._procurement_from_orderpoint_get_groups([orderpoint.id])
- for location_id, location_data in location_data.iteritems():
+ for location_id, location_data in pycompat.items(location_data):
location_orderpoints = location_data['orderpoints']
product_context = dict(self._context, location=location_orderpoints[0].location_id.id)
substract_quantity = location_orderpoints.subtract_procurements_from_orderpoints()
diff --git a/addons/stock/models/stock_inventory.py b/addons/stock/models/stock_inventory.py
index 5c5822a83d7..0771fdaaaec 100644
--- a/addons/stock/models/stock_inventory.py
+++ b/addons/stock/models/stock_inventory.py
@@ -4,7 +4,7 @@
from odoo import api, fields, models, _
from odoo.addons import decimal_precision as dp
from odoo.exceptions import UserError
-from odoo.tools import float_utils
+from odoo.tools import float_utils, pycompat
class Inventory(models.Model):
@@ -251,7 +251,7 @@ class Inventory(models.Model):
for product_data in self.env.cr.dictfetchall():
# replace the None the dictionary by False, because falsy values are tested later on
- for void_field in [item[0] for item in product_data.items() if item[1] is None]:
+ for void_field in [item[0] for item in pycompat.items(product_data) if item[1] is None]:
product_data[void_field] = False
product_data['theoretical_qty'] = product_data['product_qty']
if product_data['product_id']:
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index 591842e4225..7a806be4478 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -9,7 +9,7 @@ from odoo import api, fields, models, _
from odoo.addons import decimal_precision as dp
from odoo.addons.procurement.models import procurement
from odoo.exceptions import UserError
-from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
+from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, pycompat
from odoo.tools.float_utils import float_compare, float_round, float_is_zero
@@ -257,7 +257,7 @@ class StockMove(models.Model):
Picking = self.env['stock.picking']
# Check that we do not modify a stock.move which is done
frozen_fields = ['product_qty', 'product_uom', 'location_id', 'location_dest_id', 'product_id']
- if any(fname in frozen_fields for fname in vals.keys()) and any(move.state == 'done' for move in self):
+ if any(fname in frozen_fields for fname in vals) and any(move.state == 'done' for move in self):
raise UserError(_('Quantities, Units of Measure, Products and Locations cannot be modified on stock moves that have already been processed (except by the Administrator).'))
propagated_changes_dict = {}
@@ -490,7 +490,7 @@ class StockMove(models.Model):
(move_waiting | move_create_proc).write({'state': 'waiting'})
# assign picking in batch for all confirmed move that share the same details
- for key, moves in to_assign.items():
+ for key, moves in pycompat.items(to_assign):
moves.assign_picking()
self._push_apply()
return self
@@ -706,7 +706,7 @@ class StockMove(models.Model):
Add reserved false lots lot by lot
Check if there are not reserved quants or reserved elsewhere with that lot or without lot (with the traditional method)
"""
- return self.browse(lot_move_qty.keys())._move_quants_by_lot_v10(quants_taken, false_quants, ops, lot_qty, lot_move_qty, quant_dest_package_id)
+ return self.browse(lot_move_qty)._move_quants_by_lot_v10(quants_taken, false_quants, ops, lot_qty, lot_move_qty, quant_dest_package_id)
@api.multi
def _move_quants_by_lot_v10(self, quants_taken, false_quants, pack_operation, lot_quantities, lot_move_quantities, dest_package_id):
@@ -724,7 +724,7 @@ class StockMove(models.Model):
lot_to_quants[quant[0].lot_id.id].append(quant)
false_quants_move = [x for x in false_quants if x[0].reservation_id.id == move_rec_updateme.id]
- for lot_id in lot_quantities.keys():
+ for lot_id in lot_quantities:
redo_false_quants = False
# Take remaining reserved quants with no lot first
@@ -804,7 +804,7 @@ class StockMove(models.Model):
qty = operation.product_qty
if operation.product_uom_id and operation.product_uom_id != operation.product_id.uom_id:
qty = operation.product_uom_id._compute_quantity(qty, operation.product_id.uom_id)
- if operation.pack_lot_ids and float_compare(sum(lot_quantities.values()), qty, precision_rounding=operation.product_id.uom_id.rounding) != 0.0:
+ if operation.pack_lot_ids and float_compare(sum(pycompat.values(lot_quantities)), qty, precision_rounding=operation.product_id.uom_id.rounding) != 0.0:
raise UserError(_('You have a difference between the quantity on the operation and the quantities specified for the lots. '))
quants_taken = []
@@ -816,7 +816,7 @@ class StockMove(models.Model):
prout_move_qty[link.move_id] = prout_move_qty.get(link.move_id, 0.0) + link.qty
# Process every move only once for every pack operation
- for move in prout_move_qty.keys():
+ for move in list(prout_move_qty):
# TDE FIXME: do in batch ?
move.check_tracking(operation)
diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py
index 629015a4a10..1b3f3d34c90 100644
--- a/addons/stock/models/stock_picking.py
+++ b/addons/stock/models/stock_picking.py
@@ -6,7 +6,7 @@ import json
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.tools.float_utils import float_compare
from odoo.addons.procurement.models import procurement
from odoo.exceptions import UserError
@@ -83,7 +83,10 @@ class PickingType(models.Model):
data = self.env['stock.picking'].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'][0]: x['picking_type_id_count']
+ for x in data if x['picking_type_id']
+ }
for record in self:
record[field] = count.get(record.id, 0)
for record in self:
@@ -503,7 +506,7 @@ class Picking(models.Model):
valid_quants = quants.filtered(lambda quant: quant.qty > 0)
_Mapping = namedtuple('Mapping', ('product', 'package', 'owner', 'location', 'location_dst_id'))
- all_products = valid_quants.mapped('product_id') | self.env['product.product'].browse(p.id for p in forced_qties.keys()) | self.move_lines.mapped('product_id')
+ all_products = valid_quants.mapped('product_id') | self.env['product.product'].browse(p.id for p in forced_qties) | self.move_lines.mapped('product_id')
computed_putaway_locations = dict(
(product, self.location_dest_id.get_putaway_strategy(product).id or self.location_dest_id.id) for product in all_products)
@@ -545,7 +548,7 @@ class Picking(models.Model):
lots_grouped.setdefault(key, dict()).setdefault(quant.lot_id.id, 0.0)
lots_grouped[key][quant.lot_id.id] += quant.qty
# Do the same for the forced quantities (in cases of force_assign or incomming shipment for example)
- for product, qty in forced_qties.items():
+ for product, qty in pycompat.items(forced_qties):
if qty <= 0.0:
continue
key = _Mapping(product, self.env['stock.quant.package'], self.owner_id, self.location_id, computed_putaway_locations[product])
@@ -555,7 +558,7 @@ class Picking(models.Model):
# Create the necessary operations for the grouped quants and remaining qtys
Uom = self.env['product.uom']
product_id_to_vals = {} # use it to create operations using the same order as the picking stock moves
- for mapping, qty in qtys_grouped.items():
+ for mapping, qty in pycompat.items(qtys_grouped):
uom = product_to_uom[mapping.product.id]
val_dict = {
'picking_id': self.id,
@@ -568,7 +571,7 @@ class Picking(models.Model):
'product_uom_id': uom.id,
'pack_lot_ids': [
(0, 0, {'lot_id': lot, 'qty': 0.0, 'qty_todo': lots_grouped[mapping][lot]})
- for lot in lots_grouped.get(mapping, {}).keys()],
+ for lot in lots_grouped.get(mapping, {})],
}
product_id_to_vals.setdefault(mapping.product.id, list()).append(val_dict)
@@ -915,7 +918,7 @@ class Picking(models.Model):
self.ensure_one()
moves = self.env['stock.move']
for pack_operation in self.pack_operation_ids:
- for product, remaining_qty in pack_operation._get_remaining_prod_quantities().items():
+ for product, remaining_qty in pycompat.items(pack_operation._get_remaining_prod_quantities()):
if float_compare(remaining_qty, 0, precision_rounding=product.uom_id.rounding) > 0:
vals = self._prepare_values_extra_move(pack_operation, product, remaining_qty)
moves |= moves.create(vals)
diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py
index 1d144695f52..e100d32f3ee 100644
--- a/addons/stock/models/stock_quant.py
+++ b/addons/stock/models/stock_quant.py
@@ -3,7 +3,7 @@ from datetime import datetime
from odoo import api, fields, models
from odoo.tools.float_utils import float_compare, float_round
from odoo.tools.translate import _
-from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
+from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, pycompat
from odoo.exceptions import UserError
import logging
@@ -611,7 +611,7 @@ class QuantPackage(models.Model):
@api.multi
def name_get(self):
- return self._compute_complete_name().items()
+ return list(pycompat.items(self._compute_complete_name()))
def _compute_complete_name(self):
""" Forms complete name of location from parent location to child location. """
diff --git a/addons/stock/models/stock_warehouse.py b/addons/stock/models/stock_warehouse.py
index bb4916e30de..ea6fb20b962 100644
--- a/addons/stock/models/stock_warehouse.py
+++ b/addons/stock/models/stock_warehouse.py
@@ -8,7 +8,7 @@ from dateutil import relativedelta
from odoo import api, fields, models, _
from odoo.addons import decimal_precision as dp
from odoo.exceptions import UserError, ValidationError
-from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
+from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, pycompat
import logging
@@ -99,7 +99,7 @@ class Warehouse(models.Model):
'wh_output_stock_loc_id': {'name': _('Output'), 'active': delivery_steps != 'ship_only', 'usage': 'internal'},
'wh_pack_stock_loc_id': {'name': _('Packing Zone'), 'active': delivery_steps == 'pick_pack_ship', 'usage': 'internal'},
}
- for field_name, values in sub_locations.iteritems():
+ for field_name, values in pycompat.items(sub_locations):
values['location_id'] = vals['view_location_id']
if vals.get('company_id'):
values['company_id'] = vals.get('company_id')
@@ -240,10 +240,10 @@ class Warehouse(models.Model):
},
}
data = self._get_picking_type_values(self.reception_steps, self.delivery_steps, self.wh_pack_stock_loc_id)
- for field_name, values in data.iteritems():
+ for field_name, values in pycompat.items(data):
data[field_name].update(create_data[field_name])
- for picking_type, values in data.iteritems():
+ for picking_type, values in pycompat.items(data):
sequence = IrSequenceSudo.create(sequence_data[picking_type])
values.update(warehouse_id=self.id, color=color, sequence_id=sequence.id)
warehouse_data[picking_type] = PickingType.create(values).id
@@ -511,7 +511,8 @@ class Warehouse(models.Model):
'auto': 'manual',
'picking_type_id': routing.picking_type.id,
'warehouse_id': self.id}
- route_push_values.update((values or {}).items() + (push_values or {}).items())
+ route_push_values.update(values or {})
+ route_push_values.update(push_values or {})
push_rules_list.append(route_push_values)
route_pull_values = {
'name': self._format_rulename(routing.from_loc, routing.dest_loc, name_suffix),
@@ -521,7 +522,8 @@ class Warehouse(models.Model):
'picking_type_id': routing.picking_type.id,
'procure_method': first_rule is True and 'make_to_stock' or 'make_to_order',
'warehouse_id': self.id}
- route_pull_values.update((values or {}).items() + (pull_values or {}).items())
+ route_pull_values.update(values or {})
+ route_pull_values.update(pull_values or {})
pull_rules_list.append(route_pull_values)
first_rule = False
return push_rules_list, pull_rules_list
@@ -594,7 +596,7 @@ class Warehouse(models.Model):
@api.one
def _update_picking_type(self):
picking_type_values = self._get_picking_type_values(self.reception_steps, self.delivery_steps, self.wh_pack_stock_loc_id)
- for field_name, values in picking_type_values.iteritems():
+ for field_name, values in pycompat.items(picking_type_values):
getattr(self, field_name).write(values)
@api.multi
diff --git a/addons/stock/wizard/make_procurement.py b/addons/stock/wizard/make_procurement.py
index 3ae8f6d6bf8..369995e7a15 100644
--- a/addons/stock/wizard/make_procurement.py
+++ b/addons/stock/wizard/make_procurement.py
@@ -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 MakeProcurement(models.TransientModel):
@@ -50,7 +51,7 @@ class MakeProcurement(models.TransientModel):
@api.onchange('product_id')
def onchange_product_id(self):
if self.product_id:
- for key, value in self.onchange_product_id_dict(self.product_id.id).iteritems():
+ for key, value in pycompat.items(self.onchange_product_id_dict(self.product_id.id)):
setattr(self, key, value)
@api.model
diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py
index fa373b6c9eb..df6d8d7ecf9 100644
--- a/addons/stock_account/models/stock.py
+++ b/addons/stock_account/models/stock.py
@@ -5,7 +5,7 @@ from collections import defaultdict
from odoo import api, fields, models, _
from odoo.exceptions import UserError
-from odoo.tools import float_compare, float_round
+from odoo.tools import float_compare, float_round, pycompat
import logging
_logger = logging.getLogger(__name__)
@@ -127,7 +127,7 @@ class StockQuant(models.Model):
quant_cost_qty[quant.cost] += quant.qty
AccountMove = self.env['account.move']
- for cost, qty in quant_cost_qty.iteritems():
+ for cost, qty in pycompat.items(quant_cost_qty):
move_lines = move._prepare_account_move_line(qty, cost, credit_account_id, debit_account_id)
if move_lines:
date = self._context.get('force_period_date', fields.Date.context_today(self))
diff --git a/addons/stock_landed_costs/models/stock_landed_cost.py b/addons/stock_landed_costs/models/stock_landed_cost.py
index 12134cc0f0f..def612f5d43 100644
--- a/addons/stock_landed_costs/models/stock_landed_cost.py
+++ b/addons/stock_landed_costs/models/stock_landed_cost.py
@@ -7,6 +7,7 @@ from odoo import api, fields, models, tools, _
from odoo.addons import decimal_precision as dp
from odoo.addons.stock_landed_costs.models import product
from odoo.exceptions import UserError
+from odoo.tools import pycompat
class LandedCost(models.Model):
@@ -122,7 +123,7 @@ class LandedCost(models.Model):
quant_dict[quant] = quant.cost + diff
if quant_correct:
quant_dict[quant_correct] = quant_correct.cost + diff_correct
- for quant, value in quant_dict.items():
+ for quant, value in pycompat.items(quant_dict):
quant.sudo().write({'cost': value})
qty_out = 0
for quant in line.move_id.quant_ids:
@@ -147,7 +148,7 @@ class LandedCost(models.Model):
for val_line in landed_cost.valuation_adjustment_lines:
val_to_cost_lines[val_line.cost_line_id] += val_line.additional_landed_cost
if any(tools.float_compare(cost_line.price_unit, val_amount, precision_digits=prec_digits) != 0
- for cost_line, val_amount in val_to_cost_lines.iteritems()):
+ for cost_line, val_amount in pycompat.items(val_to_cost_lines)):
return False
return True
@@ -229,7 +230,7 @@ class LandedCost(models.Model):
else:
towrite_dict[valuation.id] += value
if towrite_dict:
- for key, value in towrite_dict.items():
+ for key, value in pycompat.items(towrite_dict):
AdjustementLines.browse(key).write({'additional_landed_cost': value})
return True
diff --git a/addons/survey/models/survey.py b/addons/survey/models/survey.py
index 3d367b0364b..a35ae5ab84e 100644
--- a/addons/survey/models/survey.py
+++ b/addons/survey/models/survey.py
@@ -14,6 +14,8 @@ from odoo.exceptions import UserError, ValidationError
from odoo.addons.website.models.website import slug
+from odoo.tools import pycompat
+
email_validator = re.compile(r"[^@]+@[^@]+\.[^@]+")
_logger = logging.getLogger(__name__)
@@ -23,8 +25,7 @@ def dict_keys_startswith(dictionary, string):
.. note::
This function uses dictionary comprehensions (Python >= 2.7)
"""
- matched_keys = [key for key in dictionary.keys() if key.startswith(string)]
- return dict((k, dictionary[k]) for k in matched_keys)
+ return {k: v for k, v in pycompat.items(dictionary) if k.startswith(string)}
class SurveyStage(models.Model):
@@ -151,7 +152,7 @@ class Survey(models.Model):
if page_id == 0:
return (pages[0][1], 0, len(pages) == 1)
- current_page_index = pages.index((filter(lambda p: p[1].id == page_id, pages))[0])
+ current_page_index = pages.index(next(p for p in pages if p[1].id == page_id))
# All the pages have been displayed
if current_page_index == len(pages) - 1 and not go_back:
@@ -238,7 +239,7 @@ class Survey(models.Model):
answers[input_line.value_suggested.id]['count'] += 1
if input_line.answer_type == 'text' and (not(current_filters) or input_line.user_input_id.id in current_filters):
comments.append(input_line)
- result_summary = {'answers': answers.values(), 'comments': comments}
+ result_summary = {'answers': list(pycompat.values(answers)), 'comments': comments}
# Calculate and return statistics for matrix
if question.type == 'matrix':
@@ -248,7 +249,7 @@ class Survey(models.Model):
comments = []
[rows.update({label.id: label.value}) for label in question.labels_ids_2]
[answers.update({label.id: label.value}) for label in question.labels_ids]
- for cell in product(rows.keys(), answers.keys()):
+ for cell in product(rows, answers):
res[cell] = 0
for input_line in question.user_input_line_ids:
if input_line.answer_type == 'suggestion' and (not(current_filters) or input_line.user_input_id.id in current_filters) and input_line.value_suggested_row:
@@ -638,7 +639,7 @@ class SurveyQuestion(models.Model):
if self.comments_allowed:
comment_answer = answer_candidates.pop(("%s_%s" % (answer_tag, 'comment')), '').strip()
# Preventing answers with blank value
- if all([True if not answer.strip() else False for answer in answer_candidates.values()]) and answer_candidates:
+ if all(not answer.strip() for answer in pycompat.values(answer_candidates)) and answer_candidates:
errors.update({answer_tag: self.constr_error_msg})
# There is no answer neither comments (if comments count as answer)
if not answer_candidates and self.comment_count_as_answer and (not comment_flag or not comment_answer):
@@ -660,7 +661,7 @@ class SurveyQuestion(models.Model):
if self.matrix_subtype == 'simple':
answer_number = len(answer_candidates)
elif self.matrix_subtype == 'multiple':
- answer_number = len(set([sk.rsplit('_', 1)[0] for sk in answer_candidates.keys()]))
+ answer_number = len({sk.rsplit('_', 1)[0] for sk in answer_candidates})
else:
raise RuntimeError("Invalid matrix subtype")
# Validate that each line has been answered
diff --git a/addons/survey/tests/test_survey.py b/addons/survey/tests/test_survey.py
index 322289d4e33..01786368b72 100644
--- a/addons/survey/tests/test_survey.py
+++ b/addons/survey/tests/test_survey.py
@@ -12,6 +12,8 @@ from odoo.exceptions import UserError
from odoo.tests.common import TransactionCase
from odoo.addons.website.models.website import slug
+from odoo.tools import pycompat
+
class TestSurvey(TransactionCase):
@@ -176,7 +178,7 @@ class TestSurvey(TransactionCase):
base_url = self.env['ir.config_parameter'].get_param('web.base.url')
urltypes = {'public': 'start', 'print': 'print', 'result': 'results'}
- for urltype, urltxt in urltypes.iteritems():
+ for urltype, urltxt in pycompat.items(urltypes):
survey_url = getattr(self.survey1, urltype + '_url')
survey_url_relative = getattr(self.survey1.with_context({'relative_url': True}), urltype + '_url')
self.assertTrue(validate_url(survey_url))
@@ -208,7 +210,7 @@ class TestSurvey(TransactionCase):
answers = [input_portal.user_input_line_ids[0], input_public.user_input_line_ids[0]]
expected_values = {'answer_type': 'free_text', 'value_free_text': "Test Answer"}
for answer in answers:
- for field, value in expected_values.iteritems():
+ for field, value in pycompat.items(expected_values):
self.assertEqual(getattr(answer, field), value, msg="Unable to answer the survey. Expected behaviour of %s is not proper." % (field))
def test_10_survey_result_simple_multiple_choice(self):
@@ -244,7 +246,7 @@ class TestSurvey(TransactionCase):
def test_12_survey_result_numeric_box(self):
question = self.env['survey.question'].sudo(self.survey_manager).create({'page_id': self.page1.id, 'question': 'Q0', 'type': 'numerical_box'})
- num = map(float, random.sample(range(1, 100), 3))
+ num = [float(n) for n in random.sample(range(1, 100), 3)]
nsum = sum(num)
for i in range(3):
self.env['survey.user_input'].sudo(self.user_public).create({'survey_id': self.survey1.id, 'user_input_line_ids': [(0, 0, {
@@ -253,7 +255,7 @@ class TestSurvey(TransactionCase):
'average': round((nsum / len(num)), 2), 'max': round(max(num), 2),
'min': round(min(num), 2), 'sum': nsum, 'most_common': Counter(num).most_common(5)}
result = self.env['survey.survey'].prepare_result(question)
- for key in exresult.keys():
+ for key in exresult:
self.assertEqual(result[key], exresult[key], msg="Statistics of numeric box type questions are different from expectations")
def test_13_survey_actions(self):
@@ -264,7 +266,7 @@ class TestSurvey(TransactionCase):
'print': {'method': 'print', 'token': '/test', 'text': 'Print'},
'result': {'method': 'result', 'token': '', 'text': 'Results of the'},
'test': {'method': 'public', 'token': '/phantom', 'text': 'Results of the'}}
- for action, val in actions.iteritems():
+ for action, val in pycompat.items(actions):
result = getattr(self.survey1.with_context({'survey_token': val['token'][1:]}), 'action_' + action + '_survey')()
url = getattr(self.survey1.with_context({'relative_url': True}), val['method'] + '_url') + val['token']
self.assertEqual(result['url'], url)
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index b0763580fef..f00c6ff5122 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -30,7 +30,7 @@ import odoo
import odoo.modules.registry
from odoo.api import call_kw, Environment
from odoo.modules import get_resource_path
-from odoo.tools import topological_sort, html_escape
+from odoo.tools import topological_sort, html_escape, pycompat
from odoo.tools.translate import _
from odoo.tools.misc import str2bool, xlwt
from odoo.tools.safe_eval import safe_eval
@@ -148,7 +148,7 @@ def ensure_db(redirect='/web/database/selector'):
def module_installed(environment):
# Candidates module the current heuristic is the /static dir
- loadable = http.addons_manifest.keys()
+ loadable = list(http.addons_manifest)
# Retrieve database installed modules
# TODO The following code should move to ir.module.module.list_installed_modules()
@@ -409,7 +409,7 @@ def xml2json_from_elementtree(el, preserve_whitespaces=False):
else:
res["tag"] = el.tag
res["attrs"] = {}
- for k, v in el.items():
+ for k, v in pycompat.items(el):
res["attrs"][k] = v
kids = []
if el.text and (preserve_whitespaces or el.text.strip() != ''):
@@ -764,7 +764,7 @@ class Session(http.Controller):
@http.route('/web/session/change_password', type='json', auth="user")
def change_password(self, fields):
old_password, new_password,confirm_password = operator.itemgetter('old_pwd', 'new_password','confirm_pwd')(
- dict(map(operator.itemgetter('name', 'value'), fields)))
+ {f['name']: f['value'] for f in fields})
if not (old_password.strip() and new_password.strip() and confirm_password.strip()):
return {'error':_('You cannot leave any password empty.'),'title': _('Change Password')}
if new_password != confirm_password:
@@ -956,7 +956,7 @@ class Binary(http.Controller):
def force_contenttype(self, headers, contenttype='image/png'):
dictheaders = dict(headers)
dictheaders['Content-Type'] = contenttype
- return dictheaders.items()
+ return list(pycompat.items(dictheaders))
@http.route(['/web/content',
'/web/content/',
@@ -1207,7 +1207,7 @@ class Export(http.Controller):
else:
fields['.id'] = fields.pop('id', {'string': 'ID'})
- fields_sequence = sorted(fields.iteritems(),
+ fields_sequence = sorted(pycompat.items(fields),
key=lambda field: odoo.tools.ustr(field[1].get('string', '')))
records = []
@@ -1218,7 +1218,7 @@ class Export(http.Controller):
if field.get('readonly'):
# If none of the field's states unsets readonly, skip the field
if all(dict(attrs).get('readonly', True)
- for attrs in field.get('states', {}).values()):
+ for attrs in pycompat.values(field.get('states', {}))):
continue
if not field.get('exportable', True):
continue
@@ -1250,7 +1250,7 @@ class Export(http.Controller):
export_fields_list = request.env['ir.exports.line'].browse(export['export_fields']).read()
fields_data = self.fields_info(
- model, map(operator.itemgetter('name'), export_fields_list))
+ model, [f['name'] for f in export_fields_list])
return [
{'name': field['name'], 'label': fields_data[field['name']]}
@@ -1310,7 +1310,7 @@ class Export(http.Controller):
export_fields = [field.split('/', 1)[1] for field in fields]
return (
(prefix + '/' + k, prefix_string + '/' + v)
- for k, v in self.fields_info(model, export_fields).iteritems())
+ for k, v in pycompat.items(self.fields_info(model, export_fields)))
class ExportFormat(object):
raw_data = False
@@ -1348,7 +1348,7 @@ class ExportFormat(object):
if not Model._is_an_ordinary_table():
fields = [field for field in fields if field['name'] != 'id']
- field_names = map(operator.itemgetter('name'), fields)
+ field_names = [f['name'] for f in fields]
import_data = records.export_data(field_names, self.raw_data).get('datas',[])
if import_compat:
diff --git a/addons/web/tests/test_serving_base.py b/addons/web/tests/test_serving_base.py
index ebbc4dfac62..3974e798461 100644
--- a/addons/web/tests/test_serving_base.py
+++ b/addons/web/tests/test_serving_base.py
@@ -15,7 +15,7 @@ def sample(population):
class TestModulesLoading(unittest.TestCase):
def setUp(self):
- self.mods = map(str, range(1000))
+ self.mods = [str(i) for i in range(1000)]
def test_topological_sort(self):
random.shuffle(self.mods)
diff --git a/addons/web_diagram/controllers/main.py b/addons/web_diagram/controllers/main.py
index ba23c839e67..56b6c76eefe 100644
--- a/addons/web_diagram/controllers/main.py
+++ b/addons/web_diagram/controllers/main.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import odoo.http as http
+from odoo.tools import pycompat
from odoo.tools.safe_eval import safe_eval
@@ -43,7 +44,12 @@ class DiagramView(http.Controller):
isolate_nodes = {}
for blnk_node in graphs['blank_nodes']:
isolate_nodes[blnk_node['id']] = blnk_node
- y = map(lambda t: t['y'], filter(lambda x: x['y'] if x['x'] == 20 else None, nodes.values()))
+ y = [
+ t['y']
+ for t in pycompat.values(nodes)
+ if t['x'] == 20
+ if t['y']
+ ]
y_max = (y and max(y)) or 120
connectors = {}
@@ -93,11 +99,11 @@ class DiagramView(http.Controller):
color='white',
options={}
)
- for color, expr in bgcolors.items():
+ for color, expr in pycompat.items(bgcolors):
if safe_eval(expr, act):
n['color'] = color
- for shape, expr in shapes.items():
+ for shape, expr in pycompat.items(shapes):
if safe_eval(expr, act):
n['shape'] = shape
diff --git a/addons/web_editor/models/ir_qweb.py b/addons/web_editor/models/ir_qweb.py
index 751767800d6..e3bd9ec94e2 100644
--- a/addons/web_editor/models/ir_qweb.py
+++ b/addons/web_editor/models/ir_qweb.py
@@ -26,7 +26,7 @@ from PIL import Image as I
import odoo.modules
from odoo import api, models, fields
-from odoo.tools import ustr
+from odoo.tools import ustr, pycompat
from odoo.tools import html_escape as escape
from odoo.addons.base.ir import ir_qweb
@@ -283,7 +283,7 @@ class Image(models.AbstractModel):
"hose again."
aclasses = ['img', 'img-responsive'] + options.get('class', '').split()
- classes = ' '.join(itertools.imap(escape, aclasses))
+ classes = ' '.join(pycompat.imap(escape, aclasses))
max_size = None
if options.get('resize'):
diff --git a/addons/web_editor/models/ir_ui_view.py b/addons/web_editor/models/ir_ui_view.py
index 65464fa7493..4c065a5b634 100644
--- a/addons/web_editor/models/ir_ui_view.py
+++ b/addons/web_editor/models/ir_ui_view.py
@@ -84,8 +84,8 @@ class IrUiView(models.Model):
@api.model
def to_field_ref(self, el):
# filter out meta-information inserted in the document
- attributes = dict((k, v) for k, v in el.items()
- if not k.startswith('data-oe-'))
+ attributes = {k: v for k, v in pycompat.items(el.attrib)
+ if not k.startswith('data-oe-')}
attributes['t-field'] = el.get('data-oe-expression')
out = html.html_parser.makeelement(el.tag, attrib=attributes)
diff --git a/addons/web_tour/models/ir_ui_menu.py b/addons/web_tour/models/ir_ui_menu.py
index 94a90e3a2ff..1cfb14a8d0a 100644
--- a/addons/web_tour/models/ir_ui_menu.py
+++ b/addons/web_tour/models/ir_ui_menu.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, tools
+from odoo.tools import pycompat
class IrUiMenu(models.Model):
@@ -31,7 +32,7 @@ class IrUiMenu(models.Model):
if subtree:
return subtree
- for menu_id, menu_xmlid in xmlids.iteritems():
+ for menu_id, menu_xmlid in pycompat.items(xmlids):
_find_subtree(menu_root, menu_id)['xmlid'] = menu_xmlid
return menu_root
diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py
index ccdd44e1c4d..1d0e0a85dd8 100644
--- a/addons/website/controllers/main.py
+++ b/addons/website/controllers/main.py
@@ -21,6 +21,8 @@ from odoo.exceptions import AccessError
from odoo.addons.website.models.website import slug
from odoo.addons.web.controllers.main import WebClient, Binary, Home
+from odoo.tools import pycompat
+
logger = logging.getLogger(__name__)
# Completely arbitrary limits
@@ -37,11 +39,11 @@ class QueryURL(object):
def __call__(self, path=None, path_args=None, **kw):
path = path or self.path
- for key, value in self.args.items():
+ for key, value in pycompat.items(self.args):
kw.setdefault(key, value)
path_args = set(path_args or []).union(self.path_args)
paths, fragments = [], []
- for key, value in kw.items():
+ for key, value in pycompat.items(kw):
if value and key in path_args:
if isinstance(value, browse_record):
paths.append((key, slug(value)))
@@ -193,7 +195,7 @@ class Website(Home):
})
else:
# TODO: in master/saas-15, move current_website_id in template directly
- pages_with_website = map(lambda p: "%d-%d" % (current_website.id, p), range(1, pages + 1))
+ pages_with_website = ["%d-%d" % (current_website.id, p) for p in range(1, pages + 1)]
# Sitemaps must be split in several smaller files with a sitemap index
content = View.render_template('website.sitemap_index_xml', {
diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py
index b6902b17c41..a4648e7d99d 100644
--- a/addons/website/models/ir_http.py
+++ b/addons/website/models/ir_http.py
@@ -14,7 +14,7 @@ import odoo
from odoo import api, models
from odoo import SUPERUSER_ID
from odoo.http import request
-from odoo.tools import config
+from odoo.tools import config, pycompat
from odoo.exceptions import QWebException
from odoo.tools.safe_eval import safe_eval
@@ -241,7 +241,7 @@ class Http(models.AbstractModel):
def _postprocess_args(cls, arguments, rule):
super(Http, cls)._postprocess_args(arguments, rule)
- for key, val in arguments.items():
+ for key, val in pycompat.items(arguments):
# Replace uid placeholder by the current request.uid
if isinstance(val, models.BaseModel) and isinstance(val._uid, RequestUID):
arguments[key] = val.sudo(request.uid)
diff --git a/addons/website/models/ir_qweb.py b/addons/website/models/ir_qweb.py
index 4ecee90fd5e..e6cedfe07d2 100644
--- a/addons/website/models/ir_qweb.py
+++ b/addons/website/models/ir_qweb.py
@@ -5,6 +5,7 @@ import ast
from odoo import models
from odoo.http import request
+from odoo.tools import pycompat
class QWeb(models.AbstractModel):
@@ -65,7 +66,7 @@ class QWeb(models.AbstractModel):
else:
return item
- return map(process, items)
+ return [process(it) for it in items]
def _compile_static_attributes(self, el, options):
items = super(QWeb, self)._compile_static_attributes(el, options)
@@ -81,7 +82,7 @@ class QWeb(models.AbstractModel):
atts = super(QWeb, self)._get_dynamic_att(tagName, atts, options, values)
if options.get('rendering_bundle'):
return atts
- for name, value in atts.iteritems():
+ for name, value in pycompat.items(atts):
atts[name] = self._website_build_attribute(tagName, name, value, options, values)
return atts
diff --git a/addons/website/models/website.py b/addons/website/models/website.py
index bd7621c767d..9968ad8e636 100644
--- a/addons/website/models/website.py
+++ b/addons/website/models/website.py
@@ -367,7 +367,7 @@ class Website(models.Model):
def get_url_localized(router, lang):
arguments = dict(request.endpoint_arguments)
- for key, val in arguments.items():
+ for key, val in list(pycompat.items(arguments)):
if isinstance(val, models.BaseModel):
arguments[key] = val.with_context(lang=lang)
return router.build(request.endpoint, arguments)
@@ -486,7 +486,7 @@ class Website(models.Model):
'num': pmax
},
"pages": [
- {'url': get_url(page), 'num': page} for page in pycompat.range(pmin, pmax+1)
+ {'url': get_url(page), 'num': page} for page in range(pmin, pmax+1)
]
}
@@ -499,7 +499,7 @@ class Website(models.Model):
endpoint = rule.endpoint
methods = endpoint.routing.get('methods') or ['GET']
- converters = rule._converters.values()
+ converters = list(pycompat.values(rule._converters))
if not ('GET' in methods
and endpoint.routing['type'] == 'http'
and endpoint.routing['auth'] in ('none', 'public')
@@ -542,9 +542,10 @@ class Website(models.Model):
if query_string and not converters and (query_string not in rule.build([{}], append_unknown=False)[1]):
continue
values = [{}]
- convitems = converters.items()
# converters with a domain are processed after the other ones
- convitems.sort(key=lambda x: hasattr(x[1], 'domain') and (x[1].domain != '[]'))
+ convitems = sorted(
+ pycompat.items(converters),
+ key=lambda x: hasattr(x[1], 'domain') and (x[1].domain != '[]'))
for (i, (name, converter)) in enumerate(convitems):
newval = []
for val in values:
@@ -559,7 +560,7 @@ class Website(models.Model):
for value in values:
domain_part, url = rule.build(value, append_unknown=False)
page = {'loc': url}
- for key, val in value.items():
+ for key, val in pycompat.items(value):
if key.startswith('__'):
page[key[2:]] = val
if url in ('/sitemap.xml',):
diff --git a/addons/website/tests/test_converter.py b/addons/website/tests/test_converter.py
index 0c3dfe0434a..9267826c689 100644
--- a/addons/website/tests/test_converter.py
+++ b/addons/website/tests/test_converter.py
@@ -4,6 +4,8 @@
import unittest
from odoo.addons.website.models.website import slugify, unslug
+from odoo.tools import pycompat
+
class TestUnslug(unittest.TestCase):
@@ -23,7 +25,7 @@ class TestUnslug(unittest.TestCase):
'foo1': (None, None),
}
- for slug, expected in tests.iteritems():
+ for slug, expected in pycompat.items(tests):
self.assertEqual(unslug(slug), expected)
diff --git a/addons/website/tests/test_crawl.py b/addons/website/tests/test_crawl.py
index 704cd62a41b..a9f1e9b1f84 100644
--- a/addons/website/tests/test_crawl.py
+++ b/addons/website/tests/test_crawl.py
@@ -40,7 +40,7 @@ class Crawler(odoo.tests.HttpCase):
_logger.info("%s %s", msg, url)
r = self.url_open(url)
code = r.getcode()
- self.assertIn(code, pycompat.range(200, 300), "%s Fetching %s returned error response (%d)" % (msg, url, code))
+ self.assertIn(code, range(200, 300), "%s Fetching %s returned error response (%d)" % (msg, url, code))
if r.info().gettype() == 'text/html':
doc = lxml.html.fromstring(r.read())
diff --git a/addons/website/tests/test_views.py b/addons/website/tests/test_views.py
index a90c14f3193..415e1668ce1 100644
--- a/addons/website/tests/test_views.py
+++ b/addons/website/tests/test_views.py
@@ -8,10 +8,11 @@ from lxml import etree as ET, html
from lxml.html import builder as h
from odoo.tests import common
+from odoo.tools import pycompat
def attrs(**kwargs):
- return dict(('data-oe-%s' % key, str(value)) for key, value in kwargs.iteritems())
+ return {'data-oe-%s' % key: str(value) for key, value in pycompat.items(kwargs)}
class TestViewSaving(common.TransactionCase):
diff --git a/addons/website_blog/controllers/main.py b/addons/website_blog/controllers/main.py
index 6ae960fc9c9..3a5ef95c3d4 100644
--- a/addons/website_blog/controllers/main.py
+++ b/addons/website_blog/controllers/main.py
@@ -104,7 +104,7 @@ class WebsiteBlog(http.Controller):
# build the domain for blog post to display
domain = []
# retrocompatibility to accept tag as slug
- active_tag_ids = tag and map(int, [unslug(t)[1] for t in tag.split(',')]) or []
+ active_tag_ids = tag and [int(unslug(t)[1]) for t in tag.split(',')] or []
if active_tag_ids:
domain += [('tag_ids', 'in', active_tag_ids)]
if blog:
@@ -149,7 +149,7 @@ class WebsiteBlog(http.Controller):
else:
tag_ids.append(current_tag)
tag_ids = request.env['blog.tag'].browse(tag_ids).exists()
- return ','.join(map(slug, tag_ids))
+ return ','.join(slug(tag) for tag in tag_ids)
values = {
'blog': blog,
'blogs': blogs,
diff --git a/addons/website_blog/models/website_blog.py b/addons/website_blog/models/website_blog.py
index eee9acc3c32..7e9c3acaa8e 100644
--- a/addons/website_blog/models/website_blog.py
+++ b/addons/website_blog/models/website_blog.py
@@ -4,6 +4,8 @@
from datetime import datetime
import random
+import itertools
+
from odoo import api, models, fields, _
from odoo.addons.website.models.website import slug
from odoo.tools.translate import html_translate
@@ -165,7 +167,10 @@ class BlogPost(models.Model):
blog_post.teaser = blog_post.teaser_manual
else:
content = html2plaintext(blog_post.content).replace('\n', ' ')
- blog_post.teaser = ' '.join(filter(None, content.split(' '))[:50]) + '...'
+ blog_post.teaser = ' '.join(itertools.islice(
+ (c for c in content.split(' ') if c),
+ 50
+ )) + '...'
@api.multi
def _set_teaser(self):
diff --git a/addons/website_crm_partner_assign/controllers/main.py b/addons/website_crm_partner_assign/controllers/main.py
index 5e69b2a2852..3c18c7cf470 100644
--- a/addons/website_crm_partner_assign/controllers/main.py
+++ b/addons/website_crm_partner_assign/controllers/main.py
@@ -12,6 +12,8 @@ from odoo import http
from odoo.http import request
from odoo.addons.website.models.website import slug, unslug
from odoo.addons.website_partner.controllers.main import WebsitePartnerPage
+
+from odoo.tools import pycompat
from odoo.tools.translate import _
from odoo.addons.website_portal.controllers.main import website_account
@@ -149,7 +151,7 @@ class WebsiteAccount(website_account):
'pager': pager,
'searchbar_sortings': searchbar_sortings,
'sortby': sortby,
- 'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
+ 'searchbar_filters': OrderedDict(sorted(pycompat.items(searchbar_filters))),
'filterby': filterby,
})
return request.render("website_crm_partner_assign.portal_my_opportunities", values)
@@ -274,7 +276,7 @@ class WebsiteCrmPartnerAssign(WebsitePartnerPage):
offset=pager['offset'], limit=self._references_per_page)
partners = partner_ids.sudo()
- google_map_partner_ids = ','.join(map(str, [p.id for p in partners]))
+ google_map_partner_ids = ','.join(str(p.id) for p in partners)
google_maps_api_key = request.env['ir.config_parameter'].sudo().get_param('google_maps_api_key')
values = {
diff --git a/addons/website_crm_partner_assign/models/crm_lead.py b/addons/website_crm_partner_assign/models/crm_lead.py
index 99a39ac9c91..f501a64eefa 100644
--- a/addons/website_crm_partner_assign/models/crm_lead.py
+++ b/addons/website_crm_partner_assign/models/crm_lead.py
@@ -5,6 +5,7 @@ import random
from odoo.addons.base_geolocalize.models.res_partner import geo_find, geo_query_address
from odoo import api, fields, models, _
+from odoo.tools import pycompat
class CrmLead(models.Model):
@@ -44,7 +45,7 @@ class CrmLead(models.Model):
if lead.partner_assigned_id and lead.partner_assigned_id.user_id != lead.user_id:
salesmans_leads.setdefault(lead.partner_assigned_id.user_id.id, []).append(lead.id)
- for salesman_id, leads_ids in salesmans_leads.items():
+ for salesman_id, leads_ids in pycompat.items(salesmans_leads):
leads = self.browse(leads_ids)
leads.write({'user_id': salesman_id})
for lead in leads:
@@ -218,7 +219,7 @@ class CrmLead(models.Model):
if tag_spam and tag_spam not in self.tag_ids:
values['tag_ids'] = [(4, tag_spam.id, False)]
if partner_ids:
- values['partner_declined_ids'] = map(lambda p: (4, p, 0), partner_ids.ids)
+ values['partner_declined_ids'] = [(4, p, 0) for p in partner_ids.ids]
self.sudo().write(values)
@api.multi
diff --git a/addons/website_crm_partner_assign/wizard/crm_forward_to_partner.py b/addons/website_crm_partner_assign/wizard/crm_forward_to_partner.py
index 5aa8073192f..cb2fbf3ba03 100644
--- a/addons/website_crm_partner_assign/wizard/crm_forward_to_partner.py
+++ b/addons/website_crm_partner_assign/wizard/crm_forward_to_partner.py
@@ -3,6 +3,7 @@
from odoo import api, fields, models, _
from odoo.exceptions import UserError
+from odoo.tools import pycompat
class CrmLeadForwardToPartner(models.TransientModel):
@@ -85,7 +86,7 @@ class CrmLeadForwardToPartner(models.TransientModel):
else:
partners_leads[partner.id] = {'partner': partner, 'leads': [lead_details]}
- for partner_id, partner_leads in partners_leads.items():
+ for partner_id, partner_leads in pycompat.items(partners_leads):
in_portal = False
if portal_group:
for contact in (partner.child_ids or partner).filtered(lambda contact: contact.user_ids):
diff --git a/addons/website_customer/controllers/main.py b/addons/website_customer/controllers/main.py
index b69ffaf3e29..4946c5b25de 100644
--- a/addons/website_customer/controllers/main.py
+++ b/addons/website_customer/controllers/main.py
@@ -77,7 +77,7 @@ class WebsiteCustomer(http.Controller):
)
partners = Partner.sudo().search(domain, offset=pager['offset'], limit=self._references_per_page)
- google_map_partner_ids = ','.join(map(str, partners.ids))
+ google_map_partner_ids = ','.join(str(it) for it in partners.ids)
google_maps_api_key = request.env['ir.config_parameter'].sudo().get_param('google_maps_api_key')
tags = Tag.search([('website_published', '=', True), ('partner_ids', 'in', partners.ids)], order='classname, name ASC')
diff --git a/addons/website_event/controllers/main.py b/addons/website_event/controllers/main.py
index 9a9ef605118..00e51627c0c 100644
--- a/addons/website_event/controllers/main.py
+++ b/addons/website_event/controllers/main.py
@@ -9,6 +9,7 @@ from dateutil.relativedelta import relativedelta
from odoo import fields, http, _
from odoo.addons.website.models.website import slug
from odoo.http import request
+from odoo.tools import pycompat
class WebsiteEventController(http.Controller):
@@ -79,7 +80,7 @@ class WebsiteEventController(http.Controller):
def dom_without(without):
domain = [('state', "in", ['draft', 'confirm', 'done'])]
- for key, search in domain_search.items():
+ for key, search in pycompat.items(domain_search):
if key != without:
domain += search
return domain
@@ -232,16 +233,16 @@ class WebsiteEventController(http.Controller):
''' Process data posted from the attendee details form. '''
registrations = {}
global_values = {}
- for key, value in details.iteritems():
+ for key, value in pycompat.items(details):
counter, field_name = key.split('-', 1)
if counter == '0':
global_values[field_name] = value
else:
registrations.setdefault(counter, dict())[field_name] = value
- for key, value in global_values.iteritems():
- for registration in registrations.values():
+ for key, value in pycompat.items(global_values):
+ for registration in pycompat.values(registrations):
registration[key] = value
- return registrations.values()
+ return list(pycompat.values(registrations))
@http.route(['/event//registration/confirm'], type='http', auth="public", methods=['POST'], website=True)
def registration_confirm(self, event, **post):
diff --git a/addons/website_event_questions/controllers/main.py b/addons/website_event_questions/controllers/main.py
index 99c4fabe783..e709e0b3d06 100644
--- a/addons/website_event_questions/controllers/main.py
+++ b/addons/website_event_questions/controllers/main.py
@@ -3,6 +3,8 @@
from odoo.addons.website_event.controllers.main import WebsiteEventController
+from odoo.tools import pycompat
+
class WebsiteEvent(WebsiteEventController):
@@ -11,7 +13,7 @@ class WebsiteEvent(WebsiteEventController):
registrations = super(WebsiteEvent, self)._process_registration_details(details)
for registration in registrations:
answer_ids = []
- for key, value in registration.iteritems():
+ for key, value in pycompat.items(registration):
if key.startswith('answer_ids-'):
answer_ids.append([4, int(value)])
registration['answer_ids'] = answer_ids
diff --git a/addons/website_event_sale/controllers/main.py b/addons/website_event_sale/controllers/main.py
index edb473d90e0..26ce63962c7 100644
--- a/addons/website_event_sale/controllers/main.py
+++ b/addons/website_event_sale/controllers/main.py
@@ -4,6 +4,7 @@
from odoo import http, _
from odoo.addons.website_event.controllers.main import WebsiteEventController
from odoo.http import request
+from odoo.tools import pycompat
class WebsiteEventSaleController(WebsiteEventController):
@@ -20,14 +21,14 @@ class WebsiteEventSaleController(WebsiteEventController):
def _process_tickets_details(self, data):
ticket_post = {}
- for key, value in data.iteritems():
+ for key, value in pycompat.items(data):
if not key.startswith('nb_register') or '-' not in key:
continue
items = key.split('-')
if len(items) < 2:
continue
ticket_post[int(items[1])] = int(value)
- tickets = request.env['event.event.ticket'].browse(ticket_post.keys())
+ tickets = request.env['event.event.ticket'].browse(tuple(ticket_post))
return [{'id': ticket.id, 'name': ticket.name, 'quantity': ticket_post[ticket.id], 'price': ticket.price} for ticket in tickets if ticket_post[ticket.id]]
@http.route(['/event//registration/confirm'], type='http', auth="public", methods=['POST'], website=True)
diff --git a/addons/website_event_track/controllers/main.py b/addons/website_event_track/controllers/main.py
index 3463ec0b3be..3fb37210589 100644
--- a/addons/website_event_track/controllers/main.py
+++ b/addons/website_event_track/controllers/main.py
@@ -8,7 +8,7 @@ import pytz
from odoo import fields, http
from odoo.http import request
-from odoo.tools import html_escape as escape, html2plaintext
+from odoo.tools import html_escape as escape, html2plaintext, pycompat
class WebsiteEventTrackController(http.Controller):
@@ -47,7 +47,7 @@ class WebsiteEventTrackController(http.Controller):
if forcetr or (start_date>dates[-1][0]) or not location:
formatted_time = self._get_locale_time(start_date, lang_code)
dates.append((start_date, {}, bool(location), formatted_time))
- for loc in locations.keys():
+ for loc in list(locations):
if locations[loc] and (locations[loc][-1][2] > start_date):
locations[loc][-1][3] += 1
elif not locations[loc] or locations[loc][-1][2] <= start_date:
@@ -75,7 +75,7 @@ class WebsiteEventTrackController(http.Controller):
days = {}
tracks_by_days = {}
- for day, tracks in days_tracks.iteritems():
+ for day, tracks in pycompat.items(days_tracks):
tracks_by_days[day] = tracks
days[day] = self._prepare_calendar(event, tracks)
diff --git a/addons/website_form/controllers/main.py b/addons/website_form/controllers/main.py
index 6a93f387ec1..cf70e0dd62a 100644
--- a/addons/website_form/controllers/main.py
+++ b/addons/website_form/controllers/main.py
@@ -10,7 +10,7 @@ from psycopg2 import IntegrityError
from odoo import http
from odoo.http import request
-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
from odoo.tools.translate import _
from odoo.exceptions import ValidationError
from odoo.addons.base.ir.ir_qweb.fields import nl2br
@@ -118,7 +118,7 @@ class WebsiteForm(http.Controller):
error_fields = []
- for field_name, field_value in values.items():
+ for field_name, field_value in pycompat.items(values):
# If the value of the field if a file
if hasattr(field_value, 'filename'):
# Undo file upload field name indexing
@@ -164,7 +164,7 @@ class WebsiteForm(http.Controller):
if hasattr(dest_model, "website_form_input_filter"):
data['record'] = dest_model.website_form_input_filter(request, data['record'])
- missing_required_fields = [label for label, field in authorized_fields.iteritems() if field['required'] and not label in data['record']]
+ missing_required_fields = [label for label, field in pycompat.items(authorized_fields) if field['required'] and not label in data['record']]
if any(error_fields):
raise ValidationError(error_fields + missing_required_fields)
diff --git a/addons/website_form/models/models.py b/addons/website_form/models/models.py
index 3c118176ec6..75a2ce33fb2 100644
--- a/addons/website_form/models/models.py
+++ b/addons/website_form/models/models.py
@@ -5,6 +5,7 @@ import itertools
from odoo import models, fields, api
from odoo.http import request
+from odoo.tools import pycompat
class website_form_config(models.Model):
@@ -47,7 +48,7 @@ class website_form_model(models.Model):
])
}
return {
- k: v for k, v in self.get_authorized_fields(self.model).iteritems()
+ k: v for k, v in pycompat.items(self.get_authorized_fields(self.model))
if k not in excluded
}
@@ -57,17 +58,17 @@ class website_form_model(models.Model):
model = self.env[model_name]
fields_get = model.fields_get()
- for key, val in model._inherits.iteritems():
+ for key, val in pycompat.items(model._inherits):
fields_get.pop(val, None)
# Unrequire fields with default values
- default_values = model.default_get(fields_get.keys())
+ default_values = model.default_get(list(fields_get))
for field in [f for f in fields_get if f in default_values]:
fields_get[field]['required'] = False
# Remove readonly and magic fields
MAGIC_FIELDS = models.MAGIC_COLUMNS + [model.CONCURRENCY_CHECK_FIELD]
- for field in fields_get.keys():
+ for field in list(fields_get):
if fields_get[field]['readonly'] or field in MAGIC_FIELDS:
del fields_get[field]
diff --git a/addons/website_forum/controllers/main.py b/addons/website_forum/controllers/main.py
index ca536bc28bd..0c8535c9b30 100644
--- a/addons/website_forum/controllers/main.py
+++ b/addons/website_forum/controllers/main.py
@@ -656,8 +656,8 @@ class WebsiteForum(http.Controller):
posts = {}
for act in activities:
posts[act.res_id] = True
- posts_ids = Post.search([('id', 'in', posts.keys())])
- posts = dict(map(lambda x: (x.id, (x.parent_id or x, x.parent_id and x or False)), posts_ids))
+ posts_ids = Post.search([('id', 'in', list(posts))])
+ posts = {x.id: (x.parent_id or x, x.parent_id and x or False) for x in posts_ids}
# TDE CLEANME MASTER: couldn't it be rewritten using a 'menu' key instead of one key for each menu ?
if user == request.env.user:
diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py
index d780e803e40..f303f764293 100644
--- a/addons/website_forum/models/forum.py
+++ b/addons/website_forum/models/forum.py
@@ -12,6 +12,7 @@ from werkzeug.exceptions import Forbidden
from odoo import api, fields, models, modules, tools, SUPERUSER_ID, _
from odoo.exceptions import UserError, ValidationError
+from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -166,7 +167,7 @@ class Forum(models.Model):
post_tags = []
existing_keep = []
user = self.env.user
- for tag in filter(None, tags.split(',')):
+ for tag in (tag for tag in tags.split(',') if tag):
if tag.startswith('_'): # it's a new tag
# check that not arleady created meanwhile or maybe excluded by the limit on the search
tag_ids = Tag.search([('name', '=', tag[1:])])
@@ -379,7 +380,7 @@ class Post(models.Model):
is_admin = user.id == SUPERUSER_ID
# sudoed recordset instead of individual posts so values can be
# prefetched in bulk
- for post, post_sudo in itertools.izip(self, self.sudo()):
+ for post, post_sudo in pycompat.izip(self, self.sudo()):
is_creator = post.create_uid == user
post.karma_accept = post.forum_id.karma_answer_accept_own if post.parent_id.create_uid == user else post.forum_id.karma_answer_accept_all
@@ -492,7 +493,7 @@ class Post(models.Model):
tag_ids = set(tag.get('id') for tag in self.resolve_2many_commands('tag_ids', vals['tag_ids']))
if any(set(post.tag_ids) != tag_ids for post in self) and any(self.env.user.karma < post.forum_id.karma_edit_retag for post in self):
raise KarmaError(_('Not enough karma to retag.'))
- if any(key not in ['state', 'active', 'is_correct', 'closed_uid', 'closed_date', 'closed_reason_id', 'tag_ids'] for key in vals.keys()) and any(not post.can_edit for post in self):
+ if any(key not in ['state', 'active', 'is_correct', 'closed_uid', 'closed_date', 'closed_reason_id', 'tag_ids'] for key in vals) and any(not post.can_edit for post in self):
raise KarmaError('Not enough karma to edit a post.')
res = super(Post, self).write(vals)
diff --git a/addons/website_gengo/controllers/main.py b/addons/website_gengo/controllers/main.py
index c36d5aef718..d3e274a2430 100644
--- a/addons/website_gengo/controllers/main.py
+++ b/addons/website_gengo/controllers/main.py
@@ -44,7 +44,7 @@ class WebsiteGengo(http.Controller):
if not translation_ids:
translations = IrTranslation.search_read([('lang', '=', lang), ('src', '=', initial_content)], fields=['id'])
if translations:
- translation_ids = map(lambda t_id: t_id['id'], translations)
+ translation_ids = [t_id['id'] for t_id in translations]
vals = {
'gengo_comment': term['gengo_comment'],
diff --git a/addons/website_hr_recruitment/controllers/main.py b/addons/website_hr_recruitment/controllers/main.py
index 71968b3d850..2ed3eafc2f6 100644
--- a/addons/website_hr_recruitment/controllers/main.py
+++ b/addons/website_hr_recruitment/controllers/main.py
@@ -50,7 +50,7 @@ class WebsiteHrRecruitment(http.Controller):
if department:
jobs = (j for j in jobs if j.department_id and j.department_id.id == department.id)
- if office_id and office_id in map(lambda x: x.id, offices):
+ if office_id and office_id in [x.id for x in offices]:
jobs = (j for j in jobs if j.address_id and j.address_id.id == office_id)
else:
office_id = False
diff --git a/addons/website_livechat/controllers/main.py b/addons/website_livechat/controllers/main.py
index 753e4eef1ae..675b56e561f 100644
--- a/addons/website_livechat/controllers/main.py
+++ b/addons/website_livechat/controllers/main.py
@@ -3,6 +3,7 @@
from odoo import http
from odoo.http import request
+from odoo.tools import pycompat
class WebsiteLivechat(http.Controller):
@@ -26,7 +27,7 @@ class WebsiteLivechat(http.Controller):
# compute percentage
percentage = dict.fromkeys(['great', 'okay', 'bad'], 0)
for grade in repartition:
- percentage[grade] = repartition[grade] * 100 / sum(repartition.values()) if sum(repartition.values()) else 0
+ percentage[grade] = repartition[grade] * 100 / sum(pycompat.values(repartition)) if sum(pycompat.values(repartition)) else 0
# the value dict to render the template
values = {
diff --git a/addons/website_membership/controllers/main.py b/addons/website_membership/controllers/main.py
index 1784f30b949..3097ac0437c 100644
--- a/addons/website_membership/controllers/main.py
+++ b/addons/website_membership/controllers/main.py
@@ -75,7 +75,7 @@ class WebsiteMembership(http.Controller):
'country_id_count': 0,
'country_id': (country_id, current_country["name"])
})
- countries = filter(lambda d:d['country_id'], countries)
+ countries = [d for d in countries if d['country_id']]
countries.sort(key=lambda d: d['country_id'][1])
countries.insert(0, {
@@ -133,7 +133,7 @@ class WebsiteMembership(http.Controller):
google_map_partner_ids += free_partner_ids[:2000-len(google_map_partner_ids)]
count_members += len(free_partner_ids)
- google_map_partner_ids = ",".join(map(str, google_map_partner_ids))
+ google_map_partner_ids = ",".join(str(it) for it in google_map_partner_ids)
google_maps_api_key = request.env['ir.config_parameter'].sudo().get_param('google_maps_api_key')
partners = {p.id: p for p in Partner.sudo().browse(list(page_partner_ids))}
diff --git a/addons/website_portal/controllers/main.py b/addons/website_portal/controllers/main.py
index 3f89ed2b80d..e9385d68e72 100644
--- a/addons/website_portal/controllers/main.py
+++ b/addons/website_portal/controllers/main.py
@@ -4,6 +4,7 @@
from odoo import http
from odoo.http import request
from odoo import tools
+from odoo.tools import pycompat
from odoo.tools.translate import _
from odoo.fields import Date
@@ -130,10 +131,10 @@ class website_account(http.Controller):
error["vat"] = 'error'
# error message for empty required fields
- if [err for err in error.values() if err == 'missing']:
+ if [err for err in pycompat.values(error) if err == 'missing']:
error_message.append(_('Some required fields are empty.'))
- unknown = [k for k in data.iterkeys() if k not in self.MANDATORY_BILLING_FIELDS + self.OPTIONAL_BILLING_FIELDS]
+ unknown = [k for k in data if k not in self.MANDATORY_BILLING_FIELDS + self.OPTIONAL_BILLING_FIELDS]
if unknown:
error['common'] = 'Unknown field'
error_message.append("Unknown field '%s'" % ','.join(unknown))
diff --git a/addons/website_portal_purchase/controllers/website_portal.py b/addons/website_portal_purchase/controllers/website_portal.py
index 4d8762e4346..874c311f92a 100644
--- a/addons/website_portal_purchase/controllers/website_portal.py
+++ b/addons/website_portal_purchase/controllers/website_portal.py
@@ -6,6 +6,7 @@ from collections import OrderedDict
from odoo import http
from odoo.exceptions import AccessError
from odoo.http import request
+from odoo.tools import pycompat
from odoo.tools.translate import _
from odoo.addons.website_portal.controllers.main import website_account, get_records_pager
@@ -88,7 +89,7 @@ class WebsitePortal(website_account):
'archive_groups': archive_groups,
'searchbar_sortings': searchbar_sortings,
'sortby': sortby,
- 'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
+ 'searchbar_filters': OrderedDict(sorted(pycompat.items(searchbar_filters))),
'filterby': filterby,
'default_url': '/my/purchase',
})
diff --git a/addons/website_project/controllers/main.py b/addons/website_project/controllers/main.py
index 8c1241f7ac8..4eb7cb68fa7 100644
--- a/addons/website_project/controllers/main.py
+++ b/addons/website_project/controllers/main.py
@@ -8,6 +8,7 @@ from odoo.http import request
from odoo.addons.website_portal.controllers.main import website_account, get_records_pager
from odoo.osv.expression import OR
+from odoo.tools import pycompat
class WebsiteAccount(website_account):
@@ -154,7 +155,7 @@ class WebsiteAccount(website_account):
'searchbar_sortings': searchbar_sortings,
'searchbar_inputs': searchbar_inputs,
'sortby': sortby,
- 'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
+ 'searchbar_filters': OrderedDict(sorted(pycompat.items(searchbar_filters))),
'filterby': filterby,
})
return request.render("website_project.my_tasks", values)
diff --git a/addons/website_project_issue/controllers/main.py b/addons/website_project_issue/controllers/main.py
index 847ad64b303..1ce21cff8e8 100644
--- a/addons/website_project_issue/controllers/main.py
+++ b/addons/website_project_issue/controllers/main.py
@@ -7,6 +7,7 @@ from odoo import http, _
from odoo.addons.website_portal.controllers.main import website_account, get_records_pager
from odoo.http import request
from odoo.osv.expression import OR
+from odoo.tools import pycompat
class WebsiteAccount(website_account):
@@ -104,7 +105,7 @@ class WebsiteAccount(website_account):
'searchbar_sortings': searchbar_sortings,
'searchbar_inputs': searchbar_inputs,
'sortby': sortby,
- 'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
+ 'searchbar_filters': OrderedDict(sorted(pycompat.items(searchbar_filters))),
'filterby': filterby,
'search_in': search_in,
'search': search,
diff --git a/addons/website_quote/controllers/main.py b/addons/website_quote/controllers/main.py
index f36c7c6111f..9561280c424 100644
--- a/addons/website_quote/controllers/main.py
+++ b/addons/website_quote/controllers/main.py
@@ -50,7 +50,7 @@ class sale_quote(http.Controller):
values = {
'quotation': order_sudo,
'message': message and int(message) or False,
- 'option': bool(filter(lambda x: not x.line_id, order_sudo.options)),
+ 'option': any(not x.line_id for x in order_sudo.options),
'order_valid': (not order_sudo.validity_date) or (now <= order_sudo.validity_date),
'days_valid': days,
'action': request.env.ref('sale.action_quotations').id,
diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py
index d00377d7322..9ff71a4f1a0 100644
--- a/addons/website_sale/controllers/main.py
+++ b/addons/website_sale/controllers/main.py
@@ -12,6 +12,8 @@ from odoo.addons.website.controllers.main import QueryURL
from odoo.exceptions import ValidationError
from odoo.addons.website_form.controllers.main import WebsiteForm
+from odoo.tools import pycompat
+
_logger = logging.getLogger(__name__)
PPG = 20 # Products Per Page
@@ -43,6 +45,7 @@ class TableCompute(object):
minpos = 0
index = 0
maxy = 0
+ x = 0
for p in products:
x = min(max(p.website_size_x, 1), PPR)
y = min(max(p.website_size_y, 1), PPR)
@@ -68,21 +71,19 @@ class TableCompute(object):
self.table[(pos / PPR) + y2][(pos % PPR) + x2] = False
self.table[pos / PPR][pos % PPR] = {
'product': p, 'x': x, 'y': y,
- 'class': " ".join(map(lambda x: x.html_class or '', p.website_style_ids))
+ 'class': " ".join(x.html_class for x in p.website_style_ids if x.html_class)
}
if index <= ppg:
maxy = max(maxy, y + (pos / PPR))
index += 1
# Format table according to HTML needs
- rows = self.table.items()
- rows.sort()
- rows = map(lambda x: x[1], rows)
+ rows = sorted(pycompat.items(self.table))
+ rows = [r[1] for r in rows]
for col in range(len(rows)):
- cols = rows[col].items()
- cols.sort()
+ cols = sorted(pycompat.items(rows[col]))
x += len(cols)
- rows[col] = [c for c in map(lambda x: x[1], cols) if c]
+ rows[col] = [r[1] for r in cols if r[1]]
return rows
@@ -207,9 +208,9 @@ class WebsiteSale(http.Controller):
ppg = PPG
attrib_list = request.httprequest.args.getlist('attrib')
- attrib_values = [map(int, v.split("-")) for v in attrib_list if v]
- attributes_ids = set([v[0] for v in attrib_values])
- attrib_set = set([v[1] for v in attrib_values])
+ attrib_values = [[int(x) for x in v.split("-")] for v in attrib_list if v]
+ attributes_ids = {v[0] for v in attrib_values}
+ attrib_set = {v[1] for v in attrib_values}
domain = self._get_search_domain(search, category, attrib_values)
@@ -280,8 +281,8 @@ class WebsiteSale(http.Controller):
category = ProductCategory.browse(int(category)).exists()
attrib_list = request.httprequest.args.getlist('attrib')
- attrib_values = [map(int, v.split("-")) for v in attrib_list if v]
- attrib_set = set([v[1] for v in attrib_values])
+ attrib_values = [[int(x) for x in v.split("-")] for v in attrib_list if v]
+ attrib_set = {v[1] for v in attrib_values}
keep = QueryURL('/shop', category=category and category.id, search=search, attrib=attrib_list)
@@ -373,7 +374,7 @@ class WebsiteSale(http.Controller):
return request.redirect("/shop/cart")
def _filter_attributes(self, **kw):
- return {k: v for k, v in kw.items() if "attribute" in k}
+ return {k: v for k, v in pycompat.items(kw) if "attribute" in k}
@http.route(['/shop/cart/update_json'], type='json', auth="public", methods=['POST'], website=True, csrf=False)
def cart_update_json(self, product_id, line_id=None, add_qty=None, set_qty=None, display=True):
@@ -458,7 +459,7 @@ class WebsiteSale(http.Controller):
error_message = []
# Required fields from form
- required_fields = filter(None, (all_form_values.get('field_required') or '').split(','))
+ required_fields = [f for f in (all_form_values.get('field_required') or '').split(',') if f]
# Required fields from mandatory field function
required_fields += mode[1] == 'shipping' and self._get_mandatory_shipping_fields() or self._get_mandatory_billing_fields()
# Check if state required
@@ -485,7 +486,7 @@ class WebsiteSale(http.Controller):
if not check_func(vat_country, vat_number):
error["vat"] = 'error'
- if [err for err in error.values() if err == 'missing']:
+ if [err for err in pycompat.items(error) if err == 'missing']:
error_message.append(_('Some required fields are empty.'))
return error, error_message
@@ -511,7 +512,7 @@ class WebsiteSale(http.Controller):
def values_postprocess(self, order, mode, values, errors, error_msg):
new_values = {}
authorized_fields = request.env['ir.model']._get('res.partner')._get_form_writable_fields()
- for k, v in values.items():
+ for k, v in pycompat.items(values):
# don't drop empty value, it could be a field to reset
if k in authorized_fields and v is not None:
new_values[k] = v
@@ -674,7 +675,7 @@ class WebsiteSale(http.Controller):
# if form posted
if 'post_values' in post:
values = {}
- for field_name, field_value in post.items():
+ for field_name, field_value in pycompat.items(post):
if field_name in request.env['sale.order']._fields and field_name.startswith('x_'):
values[field_name] = field_value
if values:
diff --git a/addons/website_sale/models/product.py b/addons/website_sale/models/product.py
index d5a8f77a72d..05ed4c78500 100644
--- a/addons/website_sale/models/product.py
+++ b/addons/website_sale/models/product.py
@@ -2,6 +2,8 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, tools, _
import odoo.addons.decimal_precision as dp
+
+from odoo.tools import pycompat
from odoo.tools.translate import html_translate
@@ -197,7 +199,7 @@ class Product(models.Model):
ret = self.env.user.has_group('sale.group_show_price_subtotal') and 'total_excluded' or 'total_included'
- for p, p2 in zip(self, self2):
+ for p, p2 in pycompat.izip(self, self2):
taxes = partner.property_account_position_id.map_tax(p.taxes_id)
p.website_price = taxes.compute_all(p2.price, pricelist.currency_id, quantity=qty, product=p2, partner=partner)[ret]
p.website_public_price = taxes.compute_all(p2.lst_price, quantity=qty, product=p2, partner=partner)[ret]
diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py
index fc2d69de53e..baf78e4c01b 100644
--- a/addons/website_sale/models/sale_order.py
+++ b/addons/website_sale/models/sale_order.py
@@ -6,6 +6,7 @@ import random
from odoo import api, models, fields, tools, _
from odoo.http import request
from odoo.exceptions import UserError, ValidationError
+from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -95,7 +96,7 @@ class SaleOrder(models.Model):
# add untracked attributes in the name
untracked_attributes = []
- for k, v in attributes.items():
+ for k, v in pycompat.items(attributes):
# attribute should be like 'attribute-48-1' where 48 is the product_id, 1 is the attribute_id and v is the attribute value
attribute_value = self.env['product.attribute.value'].sudo().browse(int(v))
if attribute_value and not attribute_value.attribute_id.create_variant:
diff --git a/addons/website_sale/tests/test_website_sale_pricelist.py b/addons/website_sale/tests/test_website_sale_pricelist.py
index 20f4147f0bd..ec04a71d6ab 100644
--- a/addons/website_sale/tests/test_website_sale_pricelist.py
+++ b/addons/website_sale/tests/test_website_sale_pricelist.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from mock import patch
from odoo.tests.common import TransactionCase
+from odoo.tools import pycompat
class TestWebsitePriceList(TransactionCase):
@@ -60,7 +61,7 @@ class TestWebsitePriceList(TransactionCase):
'CA': ['Canada'],
'US': ['USD', 'EUR', 'Benelux', 'Canada']
}
- for country, result in country_list.items():
+ for country, result in pycompat.items(country_list):
pls = self.get_pl(show, current_pl, country)
self.assertEquals(len(set(pls.mapped('name')) & set(result)), len(pls), 'Test failed for %s (%s %s vs %s %s)'
% (country, len(pls), pls.mapped('name'), len(result), result))
@@ -77,7 +78,7 @@ class TestWebsitePriceList(TransactionCase):
'CA': ['Canada']
}
- for country, result in country_list.items():
+ for country, result in pycompat.items(country_list):
pls = self.get_pl(show, current_pl, country)
self.assertEquals(len(set(pls.mapped('name')) & set(result)), len(pls), 'Test failed for %s (%s %s vs %s %s)'
% (country, len(pls), pls.mapped('name'), len(result), result))
@@ -98,7 +99,7 @@ class TestWebsitePriceList(TransactionCase):
'CA': False
}
- for country, result in country_list.items():
+ for country, result in pycompat.items(country_list):
self.args['country'] = country
# mock patch method could not pass env context
available = self.website.is_pricelist_available(christmas_pl)
@@ -119,7 +120,7 @@ class TestWebsitePriceList(TransactionCase):
'CA': ['EUR', 'Canada'],
'US': ['USD', 'EUR', 'Benelux', 'Canada']
}
- for country, result in country_list.items():
+ for country, result in pycompat.items(country_list):
pls = self.get_pl(show, current_pl, country)
self.assertEquals(len(set(pls.mapped('name')) & set(result)), len(pls), 'Test failed for %s (%s %s vs %s %s)'
% (country, len(pls), pls.mapped('name'), len(result), result))
diff --git a/addons/website_sale_digital/controllers/main.py b/addons/website_sale_digital/controllers/main.py
index 089f2b97f35..162daa5c0c8 100644
--- a/addons/website_sale_digital/controllers/main.py
+++ b/addons/website_sale_digital/controllers/main.py
@@ -17,8 +17,8 @@ class WebsiteSaleDigitalConfirmation(WebsiteSale):
def payment_confirmation(self, **post):
response = super(WebsiteSaleDigitalConfirmation, self).payment_confirmation(**post)
order_lines = response.qcontext['order'].order_line
- digital_content = map(lambda x: x.product_id.type == 'digital', order_lines)
- response.qcontext.update(digital=any(digital_content))
+ digital_content = any(x.product_id.type == 'digital' for x in order_lines)
+ response.qcontext.update(digital=digital_content)
return response
@@ -85,8 +85,7 @@ class WebsiteSaleDigital(website_account):
# Also check for attachments in the product templates
elif res_model == 'product.template':
- P = request.env['product.product']
- template_ids = map(lambda x: P.browse(x).product_tmpl_id.id, purchased_products)
+ template_ids = request.env['product.product'].browse(purchased_products).mapped('product_tmpl_id').ids
if res_id not in template_ids:
return redirect(self.orders_page)
diff --git a/addons/website_sale_digital/models/account_invoice.py b/addons/website_sale_digital/models/account_invoice.py
index 798faca55a0..a47ffcd244b 100644
--- a/addons/website_sale_digital/models/account_invoice.py
+++ b/addons/website_sale_digital/models/account_invoice.py
@@ -25,4 +25,4 @@ class AccountInvoiceLine(models.Model):
# I only want product_ids, but search_read insists in giving me a list of
# (product_id: , name: )
- return map(lambda x: x['product_id'][0], purchases)
+ return purchases.mapped('product_id').ids
diff --git a/addons/website_sale_options/controllers/main.py b/addons/website_sale_options/controllers/main.py
index f55374eda98..bf9970e9beb 100644
--- a/addons/website_sale_options/controllers/main.py
+++ b/addons/website_sale_options/controllers/main.py
@@ -5,12 +5,15 @@ from odoo import http
from odoo.http import request
from odoo.addons.website_sale.controllers.main import WebsiteSale
+from odoo.tools import pycompat
+
+
class WebsiteSaleOptions(WebsiteSale):
@http.route(['/shop/product/'], type='http', auth="public", website=True)
def product(self, product, category='', search='', **kwargs):
r = super(WebsiteSaleOptions, self).product(product, category, search, **kwargs)
- r.qcontext['optional_product_ids'] = map(lambda p: p.with_context({'active_id': p.id}), product.optional_product_ids)
+ r.qcontext['optional_product_ids'] = [p.with_context({'active_id': p.id}) for p in product.optional_product_ids]
return r
@http.route(['/shop/cart/update_option'], type='http', auth="public", methods=['POST'], website=True, multilang=False)
@@ -23,7 +26,7 @@ class WebsiteSaleOptions(WebsiteSale):
option_ids = product.optional_product_ids.mapped('product_variant_ids').ids
optional_product_ids = []
- for k, v in kw.items():
+ for k, v in pycompat.items(kw):
if "optional-product-" in k and int(kw.get(k.replace("product", "add"))) and int(v) in option_ids:
optional_product_ids.append(int(v))
diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py
index 4b2cee0b411..6385c84069a 100644
--- a/addons/website_slides/controllers/main.py
+++ b/addons/website_slides/controllers/main.py
@@ -263,10 +263,10 @@ class WebsiteSlides(http.Controller):
'caption': slide.name,
'url': slide.website_url
}
- vals = map(slide_mapped_dict, slide.get_related_slides(slides_to_suggest))
+ vals = [slide_mapped_dict(s) for s in slide.get_related_slides(slides_to_suggest)]
add_more_slide = slides_to_suggest - len(vals)
if max(add_more_slide, 0):
- vals += map(slide_mapped_dict, slide.get_most_viewed_slides(add_more_slide))
+ vals.extend(slide_mapped_dict(s) for s in slide.get_most_viewed_slides(add_more_slide))
return vals
# --------------------------------------------------
diff --git a/addons/website_slides/models/slides.py b/addons/website_slides/models/slides.py
index bd2a544a599..d391246b9cd 100644
--- a/addons/website_slides/models/slides.py
+++ b/addons/website_slides/models/slides.py
@@ -12,7 +12,7 @@ import re
import urllib2
from odoo import api, fields, models, SUPERUSER_ID, _
-from odoo.tools import image
+from odoo.tools import image, pycompat
from odoo.tools.translate import html_translate
from odoo.exceptions import Warning
from odoo.addons.website.models.website import slug
@@ -323,7 +323,7 @@ class Slide(models.Model):
values = res['values']
if not values.get('document_id'):
raise Warning(_('Please enter valid Youtube or Google Doc URL'))
- for key, value in values.iteritems():
+ for key, value in pycompat.items(values):
setattr(self, key, value)
# website
@@ -386,7 +386,7 @@ class Slide(models.Model):
values['date_published'] = datetime.datetime.now()
if values.get('url'):
doc_data = self._parse_document_url(values['url']).get('values', dict())
- for key, value in doc_data.iteritems():
+ for key, value in pycompat.items(doc_data):
values.setdefault(key, value)
# Do not publish slide if user has not publisher rights
if not self.user_has_groups('website.group_website_publisher'):
@@ -400,7 +400,7 @@ class Slide(models.Model):
def write(self, values):
if values.get('url'):
doc_data = self._parse_document_url(values['url']).get('values', dict())
- for key, value in doc_data.iteritems():
+ for key, value in pycompat.items(doc_data):
values.setdefault(key, value)
if values.get('channel_id'):
custom_channels = self.env['slide.channel'].search([('custom_slide_id', '=', self.id), ('id', '!=', values.get('channel_id'))])
diff --git a/doc/_extensions/odoo_ext/translator.py b/doc/_extensions/odoo_ext/translator.py
index 48c5e901f28..1f7fba93772 100644
--- a/doc/_extensions/odoo_ext/translator.py
+++ b/doc/_extensions/odoo_ext/translator.py
@@ -71,9 +71,9 @@ class BootstrapTranslator(nodes.NodeVisitor, object):
tagname = unicode(tagname).lower()
# extract generic attributes
- attrs = {name.lower(): value for name, value in attributes.iteritems()}
+ attrs = {name.lower(): value for name, value in attributes.items()}
attrs.update(
- (name, value) for name, value in node.attributes.iteritems()
+ (name, value) for name, value in node.attributes.items()
if name.startswith('data-')
)
@@ -97,7 +97,7 @@ class BootstrapTranslator(nodes.NodeVisitor, object):
prefix=u''.join(prefix),
tag=tagname,
attrs=u' '.join(u'{}="{}"'.format(name, self.attval(value))
- for name, value in attrs.iteritems()),
+ for name, value in attrs.items()),
postfix=u''.join(postfix),
)
# only "space characters" SPACE, CHARACTER TABULATION, LINE FEED,
diff --git a/doc/python3.rst b/doc/python3.rst
index df86107c35b..ba368572c13 100644
--- a/doc/python3.rst
+++ b/doc/python3.rst
@@ -172,6 +172,37 @@ statement to the following cross-language forms::
exec(source, globals)
exec(source, globals, locals)
+List/iteration builtins and methods
+-----------------------------------
+
+In Python 3, a number of builtins and methods formerly returning *lists* were
+converted to return *iterators* or *views*, with the corresponding redundant
+methods or functions having been *removed entirely*:
+
+* In Python 3, ``map``, ``filter`` and ``zip`` return iterators,
+ ``itertools.imap``, ``itertools.ifilter`` and ``itertools.izip`` have been
+ removed.
+
+ .. important::
+
+ When possible, use comprehensions (list, generator, ...) rather than
+ ``map`` or ``filter``, otherwise use the cross-version ``pycompat``
+ versions (``pycompat.imap``, ``pycompat.ifilter`` and
+ ``pycompat.izip``). The ``pycompat`` versions all return *iterators* and
+ may need to be wrapped in a ``list()`` call to yield a list.
+
+* In Python 3, ``dict.keys``, ``dict.values`` and ``dict.items`` return
+ *views* rather than lists, and the ``iter*`` and ``view*`` methods have
+ been removed.
+
+ .. important::
+
+ Prefer using :func:`odoo.tools.pycompat.keys`,
+ :func:`odoo.tools.pycompat.values` and :func:`odoo.tools.pycompat.items`
+ return cross-version iterators. When needing actual lists (e.g. to
+ modify a dictionary during iteration), wrap one of the calls above in a
+ ``list()``.
+
builtins
--------
@@ -258,21 +289,12 @@ code by replacing it with some other method altogether.
``xrange``
##########
-In Python 3, ``range()`` behaves the same as Python 3's ``xrange``. For
-cross-versions code you can:
+In Python 3, ``range()`` behaves the same as Python 3's ``xrange``.
-* just use ``range()`` everywhere and ignore the allocation cost of a list in
- Python 2 (often not an issue)
-* conditionally alias ``xrange`` to ``range`` in Python 3 and use that
-* use a combination of ``itertools.count`` and ``takewhile`` for a
- cross-compatible lazy increasing sequence of numbers
-
-.. warning::
-
- In the *rare* cases where you need conditional code (code which applies
- for one version of python and not the other), use ``sys.version_info``
- e.g. ``sys.version_info() >= (3,)`` for Python3+ code or
- ``sys.version_info() < (3,)`` for Python 2 code.
+For cross-version code, you can just use ``range()`` everywhere: while this
+will incur a slight allocation cost on Python 2, Python 3's ``range`` supports
+the entire Sequence protocol and thus behaves very much like a regular
+list or tuple.
Removed/renamed methods
-----------------------
diff --git a/odoo/addons/base/ir/ir_actions.py b/odoo/addons/base/ir/ir_actions.py
index 918e5d90880..4979c898e08 100644
--- a/odoo/addons/base/ir/ir_actions.py
+++ b/odoo/addons/base/ir/ir_actions.py
@@ -5,6 +5,7 @@ import odoo
from odoo import api, fields, models, tools, SUPERUSER_ID, _
from odoo.exceptions import MissingError, UserError, ValidationError, AccessError
from odoo.tools.safe_eval import safe_eval, test_python_expr
+from odoo.tools import pycompat
from odoo.http import request
import datetime
@@ -649,17 +650,19 @@ class IrActionsTodo(models.Model):
"""
user_groups = self.env.user.groups_id
- def groups_match(todo):
- """ Checks if the todo's groups match those of the current user """
- return not todo.groups_id or bool(todo.groups_id & user_groups)
-
- done = filter(groups_match, self.browse(self.search([('state', '!=', 'open')])))
- total = filter(groups_match, self.browse(self.search([])))
-
+ done_count = self.search_count([
+ ('state', '!=', open),
+ '|', ('groups_id', '=', False),
+ ('groups_id', 'in', user_groups.ids),
+ ])
+ total_count = self.search_count([
+ '|', ('groups_id', '=', False),
+ ('groups_id', 'in', user_groups.ids),
+ ])
return {
- 'done': len(done),
- 'total': len(total),
- 'todo': len(total) - len(done),
+ 'done': done_count,
+ 'total': total_count,
+ 'todo': total_count - done_count,
}
@@ -688,7 +691,7 @@ class IrActionsActClient(models.Model):
@api.depends('params_store')
def _compute_params(self):
self_bin = self.with_context(bin_size=False, bin_size_params_store=False)
- for record, record_bin in zip(self, self_bin):
+ for record, record_bin in pycompat.izip(self, self_bin):
record.params = record_bin.params_store and safe_eval(record_bin.params_store, {'uid': self._uid})
def _inverse_params(self):
diff --git a/odoo/addons/base/ir/ir_attachment.py b/odoo/addons/base/ir/ir_attachment.py
index b6bd9a08ee7..9d19d076438 100644
--- a/odoo/addons/base/ir/ir_attachment.py
+++ b/odoo/addons/base/ir/ir_attachment.py
@@ -11,7 +11,7 @@ from collections import defaultdict
from odoo import api, fields, models, tools, SUPERUSER_ID, _
from odoo.exceptions import AccessError
-from odoo.tools import config, human_size, ustr, html_escape
+from odoo.tools import config, human_size, ustr, html_escape, pycompat
from odoo.tools.mimetypes import guess_mimetype
_logger = logging.getLogger(__name__)
@@ -167,7 +167,7 @@ class IrAttachment(models.Model):
# remove garbage files, and clean up checklist
removed = 0
- for fname, filepath in checklist.iteritems():
+ for fname, filepath in pycompat.items(checklist):
if fname not in whitelist:
try:
os.unlink(self._full_path(fname))
@@ -318,7 +318,7 @@ class IrAttachment(models.Model):
model_ids[values['res_model']].add(values['res_id'])
# check access rights on the records
- for res_model, res_ids in model_ids.iteritems():
+ for res_model, res_ids in pycompat.items(model_ids):
# ignore attachments that are not attached to a resource anymore
# when checking access rights (resource was deleted but attachment
# was not)
@@ -374,12 +374,12 @@ class IrAttachment(models.Model):
# To avoid multiple queries for each attachment found, checks are
# performed in batch as much as possible.
- for res_model, targets in model_attachments.iteritems():
+ for res_model, targets in pycompat.items(model_attachments):
if res_model not in self.env:
continue
if not self.env[res_model].check_access_rights('read', False):
# remove all corresponding attachment ids
- ids.difference_update(itertools.chain(*targets.itervalues()))
+ ids.difference_update(itertools.chain(*pycompat.values(targets)))
continue
# filter ids according to what access rules permit
target_ids = list(targets)
diff --git a/odoo/addons/base/ir/ir_config_parameter.py b/odoo/addons/base/ir/ir_config_parameter.py
index cbb44ca6457..8e4ea20b5a5 100644
--- a/odoo/addons/base/ir/ir_config_parameter.py
+++ b/odoo/addons/base/ir/ir_config_parameter.py
@@ -8,7 +8,7 @@ import uuid
import logging
from odoo import api, fields, models
-from odoo.tools import config, ormcache, mute_logger
+from odoo.tools import config, ormcache, mute_logger, pycompat
_logger = logging.getLogger(__name__)
@@ -42,7 +42,7 @@ class IrConfigParameter(models.Model):
Initializes the parameters listed in _default_parameters.
It overrides existing parameters if force is ``True``.
"""
- for key, func in _default_parameters.iteritems():
+ for key, func in pycompat.items(_default_parameters):
# force=True skips search and always performs the 'if' body (because ids=False)
params = self.sudo().search([('key', '=', key)])
if force or not params:
diff --git a/odoo/addons/base/ir/ir_fields.py b/odoo/addons/base/ir/ir_fields.py
index 26c351d6fa8..204d4599425 100644
--- a/odoo/addons/base/ir/ir_fields.py
+++ b/odoo/addons/base/ir/ir_fields.py
@@ -8,13 +8,13 @@ import psycopg2
import pytz
from odoo import api, fields, models, _
-from odoo.tools import ustr
+from odoo.tools import ustr, pycompat
REFERENCING_FIELDS = {None, 'id', '.id'}
def only_ref_fields(record):
- return {k: v for k, v in record.iteritems() if k in REFERENCING_FIELDS}
+ return {k: v for k, v in pycompat.items(record) if k in REFERENCING_FIELDS}
def exclude_ref_fields(record):
- return {k: v for k, v in record.iteritems() if k not in REFERENCING_FIELDS}
+ return {k: v for k, v in pycompat.items(record) if k not in REFERENCING_FIELDS}
CREATE = lambda values: (0, False, values)
UPDATE = lambda id, values: (1, id, values)
@@ -43,9 +43,9 @@ class IrFieldsConverter(models.AbstractModel):
if isinstance(error_params, basestring):
error_params = sanitize(error_params)
elif isinstance(error_params, dict):
- error_params = {k: sanitize(v) for k, v in error_params.iteritems()}
+ error_params = {k: sanitize(v) for k, v in pycompat.items(error_params)}
elif isinstance(error_params, tuple):
- error_params = tuple(map(sanitize, error_params))
+ error_params = tuple(sanitize(v) for v in error_params)
return error_type(error_msg % error_params, error_args)
@api.model
@@ -64,12 +64,12 @@ class IrFieldsConverter(models.AbstractModel):
converters = {
name: self.to_field(model, field, fromtype)
- for name, field in model._fields.iteritems()
+ for name, field in pycompat.items(model._fields)
}
def fn(record, log):
converted = {}
- for field, value in record.iteritems():
+ for field, value in pycompat.items(record):
if field in REFERENCING_FIELDS:
continue
if not value:
@@ -369,7 +369,7 @@ class IrFieldsConverter(models.AbstractModel):
:rtype: str, list
"""
# Can import by name_get, external id or database id
- fieldset = set(record.iterkeys())
+ fieldset = set(record)
if fieldset - REFERENCING_FIELDS:
raise ValueError(
_(u"Can not create Many-To-One records indirectly, import the field separately"))
diff --git a/odoo/addons/base/ir/ir_http.py b/odoo/addons/base/ir/ir_http.py
index 3d9c72aec60..aa23531055e 100644
--- a/odoo/addons/base/ir/ir_http.py
+++ b/odoo/addons/base/ir/ir_http.py
@@ -21,6 +21,7 @@ import odoo
from odoo import api, http, models, tools, SUPERUSER_ID
from odoo.exceptions import AccessDenied, AccessError
from odoo.http import request, STATIC_CACHE, content_disposition
+from odoo.tools import pycompat
from odoo.tools.mimetypes import guess_mimetype
from odoo.modules.module import get_resource_path, get_module_path
@@ -54,7 +55,7 @@ class ModelsConverter(werkzeug.routing.BaseConverter):
def to_python(self, value):
env = api.Environment(request.cr, UID_PLACEHOLDER, request.context)
- return env[self.model].browse(map(int, value.split(',')))
+ return env[self.model].browse(int(v) for v in value.split(','))
def to_url(self, value):
return ",".join(value.ids)
@@ -202,7 +203,7 @@ class IrHttp(models.AbstractModel):
@classmethod
def _postprocess_args(cls, arguments, rule):
""" post process arg to set uid on browse records """
- for name, arg in arguments.items():
+ for name, arg in list(pycompat.items(arguments)):
if isinstance(arg, models.BaseModel) and arg._uid is UID_PLACEHOLDER:
arguments[name] = arg.sudo(request.uid)
if not arg.exists():
diff --git a/odoo/addons/base/ir/ir_mail_server.py b/odoo/addons/base/ir/ir_mail_server.py
index 22f78da6c18..737e9806d68 100644
--- a/odoo/addons/base/ir/ir_mail_server.py
+++ b/odoo/addons/base/ir/ir_mail_server.py
@@ -13,9 +13,11 @@ import re
import smtplib
import threading
+import itertools
+
from odoo import api, fields, models, tools, _
from odoo.exceptions import except_orm, UserError
-from odoo.tools import html2text, ustr
+from odoo.tools import html2text, ustr, pycompat
_logger = logging.getLogger(__name__)
_test_logger = logging.getLogger('odoo.tests')
@@ -110,7 +112,7 @@ def extract_rfc2822_addresses(text):
if not text:
return []
candidates = address_pattern.findall(ustr(text).encode('utf-8'))
- return filter(try_coerce_ascii, candidates)
+ return [c for c in candidates if try_coerce_ascii(c)]
def encode_rfc2822_address_header(header_text):
@@ -127,7 +129,7 @@ def encode_rfc2822_address_header(header_text):
return formataddr((name, email))
addresses = getaddresses([ustr(header_text).encode('utf-8')])
- return COMMASPACE.join(map(encode_addr, addresses))
+ return COMMASPACE.join(encode_addr(a) for a in addresses)
class IrMailServer(models.Model):
@@ -333,7 +335,7 @@ class IrMailServer(models.Model):
msg['Bcc'] = encode_rfc2822_address_header(COMMASPACE.join(email_bcc))
msg['Date'] = formatdate()
# Custom headers may override normal headers or provide additional ones
- for key, value in headers.iteritems():
+ for key, value in pycompat.items(headers):
msg[ustr(key).encode('utf-8')] = encode_header(value)
if subtype == 'html' and not body_alternative and html2text:
@@ -442,7 +444,12 @@ class IrMailServer(models.Model):
email_cc = message['Cc']
email_bcc = message['Bcc']
- smtp_to_list = filter(None, tools.flatten(map(extract_rfc2822_addresses, [email_to, email_cc, email_bcc])))
+ smtp_to_list = [
+ address
+ for base in [email_to, email_cc, email_bcc]
+ for address in extract_rfc2822_addresses(base)
+ if address
+ ]
assert smtp_to_list, self.NO_VALID_RECIPIENT
x_forge_to = message['X-Forge-To']
diff --git a/odoo/addons/base/ir/ir_model.py b/odoo/addons/base/ir/ir_model.py
index ad72694d410..9272a790fbc 100644
--- a/odoo/addons/base/ir/ir_model.py
+++ b/odoo/addons/base/ir/ir_model.py
@@ -45,7 +45,7 @@ def query_insert(cr, table, values):
query = INSERT_QUERY.format(
table=table,
cols=",".join(values),
- vals=",".join(map("%({0})s".format, values)),
+ vals=",".join("%({0})s".format(v) for v in values),
)
cr.execute(query, values)
@@ -53,8 +53,8 @@ def query_update(cr, table, values, selectors):
setters = set(values) - set(selectors)
query = UPDATE_QUERY.format(
table=table,
- assignment=",".join(map("{0}=%({0})s".format, setters)),
- condition=" AND ".join(map("{0}=%({0})s".format, selectors)),
+ assignment=",".join("{0}=%({0})s".format(s) for s in setters),
+ condition=" AND ".join("{0}=%({0})s".format(s) for s in selectors),
)
cr.execute(query, values)
@@ -188,7 +188,7 @@ class IrModel(models.Model):
@api.multi
def write(self, vals):
if '__last_update' in self._context:
- self = self.with_context({k: v for k, v in self._context.iteritems() if k != '__last_update'})
+ self = self.with_context({k: v for k, v in pycompat.items(self._context) if k != '__last_update'})
if 'model' in vals and any(rec.model != vals['model'] for rec in self):
raise UserError(_('Field "Model" cannot be modified on models.'))
if 'state' in vals and any(rec.state != vals['state'] for rec in self):
@@ -559,8 +559,8 @@ class IrModelFields(models.Model):
except Exception:
raise UserError("\n".join([
_("Cannot rename/delete fields that are still present in views:"),
- _("Fields:") + " " + ", ".join(map(str, fields)),
- _("View:") + " " + view.name,
+ _("Fields: %s") % ", ".join(str(f) for f in fields),
+ _("View: %s") % view.name,
]))
finally:
# the registry has been modified, restore it
@@ -760,7 +760,7 @@ class IrModelFields(models.Model):
fields_data[field.name] = dict(params, id=record.id)
return record
- diff = {key for key, val in params.items() if field_data[key] != val}
+ diff = {key for key, val in pycompat.items(params) if field_data[key] != val}
if diff:
cr = self.env.cr
# update the entry in this table
@@ -778,7 +778,7 @@ class IrModelFields(models.Model):
def _reflect_model(self, model):
""" Reflect the given model's fields. """
self.clear_caches()
- for field in model._fields.itervalues():
+ for field in pycompat.values(model._fields):
self._reflect_field(field)
if not self.pool._init:
@@ -1228,7 +1228,7 @@ class IrModelData(models.Model):
@api.depends('module', 'name')
def _compute_complete_name(self):
for res in self:
- res.complete_name = ".".join(filter(None, [res.module, res.name]))
+ res.complete_name = ".".join(n for n in [res.module, res.name] if n)
@api.depends('model', 'res_id')
def _compute_reference(self):
@@ -1259,7 +1259,7 @@ class IrModelData(models.Model):
model_id_name[xid.model][xid.res_id] = None
# fill in model_id_name with name_get() of corresponding records
- for model, id_name in model_id_name.iteritems():
+ for model, id_name in pycompat.items(model_id_name):
try:
ng = self.env[model].browse(id_name).name_get()
id_name.update(ng)
@@ -1356,7 +1356,7 @@ class IrModelData(models.Model):
record = self.get_object(module, xml_id)
if record:
self.loads[(module, xml_id)] = (model, record.id)
- for parent_model, parent_field in self.env[model]._inherits.iteritems():
+ for parent_model, parent_field in pycompat.items(self.env[model]._inherits):
parent = record[parent_field]
parent_xid = '%s_%s' % (xml_id, parent_model.replace('.', '_'))
self.loads[(module, parent_xid)] = (parent_model, parent.id)
@@ -1411,7 +1411,7 @@ class IrModelData(models.Model):
elif record:
record.write(values)
if xml_id:
- for parent_model, parent_field in record._inherits.iteritems():
+ for parent_model, parent_field in pycompat.items(record._inherits):
self.sudo().create({
'name': xml_id + '_' + parent_model.replace('.', '_'),
'model': parent_model,
@@ -1430,7 +1430,7 @@ class IrModelData(models.Model):
elif mode == 'init' or (mode == 'update' and xml_id):
existing_parents = set() # {parent_model, ...}
if xml_id:
- for parent_model, parent_field in record._inherits.iteritems():
+ for parent_model, parent_field in pycompat.items(record._inherits):
xid = self.sudo().search([
('module', '=', module),
('name', '=', xml_id + '_' + parent_model.replace('.', '_')),
@@ -1450,7 +1450,7 @@ class IrModelData(models.Model):
inherit_models = [record]
while inherit_models:
current_model = inherit_models.pop()
- for parent_model_name, parent_field in current_model._inherits.iteritems():
+ for parent_model_name, parent_field in pycompat.items(current_model._inherits):
inherit_models.append(self.env[parent_model_name])
if parent_model_name in existing_parents:
continue
@@ -1476,7 +1476,7 @@ class IrModelData(models.Model):
if xml_id and record:
self.loads[(module, xml_id)] = (model, record.id)
- for parent_model, parent_field in record._inherits.iteritems():
+ for parent_model, parent_field in pycompat.items(record._inherits):
parent_xml_id = xml_id + '_' + parent_model.replace('.', '_')
self.loads[(module, parent_xml_id)] = (parent_model, record[parent_field].id)
diff --git a/odoo/addons/base/ir/ir_qweb/assetsbundle.py b/odoo/addons/base/ir/ir_qweb/assetsbundle.py
index 5bc2dec8a9b..19902e78d1b 100644
--- a/odoo/addons/base/ir/ir_qweb/assetsbundle.py
+++ b/odoo/addons/base/ir/ir_qweb/assetsbundle.py
@@ -13,7 +13,7 @@ from odoo.http import request
from odoo.modules.module import get_resource_path
import psycopg2
import werkzeug
-from odoo.tools import func, misc
+from odoo.tools import func, misc, pycompat
import logging
_logger = logging.getLogger(__name__)
@@ -308,7 +308,7 @@ class AssetsBundle(object):
outdated = False
assets = dict((asset.html_url, asset) for asset in self.stylesheets if isinstance(asset, atype))
if assets:
- assets_domain = [('url', 'in', assets.keys())]
+ assets_domain = [('url', 'in', list(assets))]
attachments = self.env['ir.attachment'].sudo().search(assets_domain)
for attachment in attachments:
asset = assets[attachment.url]
@@ -320,7 +320,7 @@ class AssetsBundle(object):
if not asset._content and attachment.file_size > 0:
asset._content = None # file missing, force recompile
- if any(asset._content is None for asset in assets.itervalues()):
+ if any(asset._content is None for asset in pycompat.values(assets)):
outdated = True
if outdated:
@@ -452,7 +452,7 @@ class WebAsset(object):
def stat(self):
if not (self.inline or self._filename or self._ir_attach):
- path = filter(None, self.url.split('/'))
+ path = (segment for segment in self.url.split('/') if segment)
self._filename = get_resource_path(*path)
if self._filename:
return
diff --git a/odoo/addons/base/ir/ir_qweb/ir_qweb.py b/odoo/addons/base/ir/ir_qweb/ir_qweb.py
index 68c4bf048d7..5548fcd47d8 100644
--- a/odoo/addons/base/ir/ir_qweb/ir_qweb.py
+++ b/odoo/addons/base/ir/ir_qweb/ir_qweb.py
@@ -75,7 +75,7 @@ class IrQWeb(models.AbstractModel, QWeb):
# apply ormcache_context decorator unless in dev mode...
@tools.conditional(
'xml' not in tools.config['dev_mode'],
- tools.ormcache('id_or_xml_id', 'tuple(map(options.get, self._get_template_cache_keys()))'),
+ tools.ormcache('id_or_xml_id', 'tuple(options.get(k) for k in self._get_template_cache_keys())'),
)
def compile(self, id_or_xml_id, options):
return super(IrQWeb, self).compile(id_or_xml_id, options=options)
@@ -173,7 +173,7 @@ class IrQWeb(models.AbstractModel, QWeb):
if field_options and 'monetary' in field_options:
try:
options = "{'widget': 'monetary'"
- for k, v in json.loads(field_options).iteritems():
+ for k, v in pycompat.items(json.loads(field_options)):
if k in ('display_currency', 'from_currency'):
options = "%s, '%s': %s" % (options, k, v)
else:
@@ -238,12 +238,12 @@ class IrQWeb(models.AbstractModel, QWeb):
atype = 'text/less'
if atype not in ('text/less', 'text/sass'):
atype = 'text/css'
- path = filter(None, href.split('/'))
+ path = [segment for segment in href.split('/') if segment]
filename = get_resource_path(*path) if path else None
files.append({'atype': atype, 'url': href, 'filename': filename, 'content': el.text, 'media': media})
elif el.tag == 'script':
atype = 'text/javascript'
- path = filter(None, src.split('/'))
+ path = [segment for segment in src.split('/') if segment]
filename = get_resource_path(*path) if path else None
files.append({'atype': atype, 'url': src, 'filename': filename, 'content': el.text, 'media': media})
else:
diff --git a/odoo/addons/base/ir/ir_qweb/qweb.py b/odoo/addons/base/ir/ir_qweb/qweb.py
index 14a0a54fbd9..06aba8ccdb4 100644
--- a/odoo/addons/base/ir/ir_qweb/qweb.py
+++ b/odoo/addons/base/ir/ir_qweb/qweb.py
@@ -6,14 +6,15 @@ import traceback
from collections import OrderedDict, Sized, Mapping, defaultdict
from functools import reduce
-from itertools import chain, izip, tee, count
+from itertools import tee, count
from textwrap import dedent
+import itertools
from lxml import etree, html
import werkzeug
from werkzeug.utils import escape as _escape
-from odoo.tools import pycompat
+from odoo.tools import pycompat, freehash
try:
import builtins
@@ -82,7 +83,7 @@ class Contextifier(ast.NodeTransformer):
return ast.copy_location(ast.Lambda(
args=ast.arguments(
args=args.args,
- defaults=map(self.visit, args.defaults),
+ defaults=[self.visit(default) for default in args.defaults],
vararg=args.vararg,
kwarg=args.kwarg,
),
@@ -109,7 +110,7 @@ class Contextifier(ast.NodeTransformer):
for field, value in ast.iter_fields(node):
# map transformation of comprehensions
if isinstance(value, list):
- setattr(newnode, field, map(transformer.visit, value))
+ setattr(newnode, field, [transformer.visit(v) for v in value])
else: # set transformation of key/value/expr fields
setattr(newnode, field, transformer.visit(value))
return newnode
@@ -168,14 +169,14 @@ def foreach_iterator(base_ctx, enum, name):
if not enum:
return
if isinstance(enum, int):
- enum = pycompat.range(enum)
+ enum = range(enum)
size = None
if isinstance(enum, Sized):
ctx["%s_size" % name] = size = len(enum)
if isinstance(enum, Mapping):
- enum = enum.iteritems()
+ enum = pycompat.items(enum)
else:
- enum = izip(*tee(enum))
+ enum = pycompat.izip(*tee(enum))
value_key = '%s_value' % name
index_key = '%s_index' % name
first_key = '%s_first' % name
@@ -201,7 +202,7 @@ def foreach_iterator(base_ctx, enum, name):
yield ctx
# copy changed items back into source context (?)
# FIXME: maybe values could provide a ChainMap-style clone?
- for k in base_ctx.keys():
+ for k in list(base_ctx):
base_ctx[k] = ctx[k]
_FORMAT_REGEX = re.compile(
@@ -226,7 +227,7 @@ class frozendict(dict):
def update(self, *args, **kwargs):
raise NotImplementedError("'update' not supported on frozendict")
def __hash__(self):
- return hash(frozenset((key, freehash(val)) for key, val in self.iteritems()))
+ return hash(frozenset((key, freehash(val)) for key, val in pycompat.items(self)))
####################################
@@ -510,6 +511,7 @@ class QWeb(object):
"""
return ast.parse(dedent("""
from collections import OrderedDict
+ from odoo.tools import pycompat
from odoo.addons.base.ir.ir_qweb.qweb import escape, unicodifier, foreach_iterator
"""))
@@ -665,7 +667,7 @@ class QWeb(object):
# all directives have been compiled, there should be none left
if any(att.startswith('t-') for att in el.attrib):
- raise "Unknown directive on %s" % ("', '".join(directives), etree.tostring(el))
+ raise NameError("Unknown directive on %s" % etree.tostring(el))
return []
def _values_var(self, varname, ctx):
@@ -726,7 +728,7 @@ class QWeb(object):
attrib = {}
# If `el` introduced new namespaces, write them as attribute by using the
# `attrib` dict.
- for ns_prefix, ns_definition in set(el.nsmap.items()) - set(options['nsmap'].items()):
+ for ns_prefix, ns_definition in set(pycompat.items(el.nsmap)) - set(pycompat.items(options['nsmap'])):
if ns_prefix is None:
attrib['xmlns'] = ns_definition
else:
@@ -735,8 +737,9 @@ class QWeb(object):
# Etree will also remove the ns prefixes indirection in the attributes. As we only have
# the namespace definition, we'll use an nsmap where the keys are the definitions and
# the values the prefixes in order to get back the right prefix and restore it.
- nsprefixmap = {v: k for k, v in options['nsmap'].items() + el.nsmap.items()}
- for key, value in el.attrib.items():
+ ns = itertools.chain(pycompat.items(options['nsmap']), pycompat.items(el.nsmap))
+ nsprefixmap = {v: k for k, v in ns}
+ for key, value in pycompat.items(el.attrib):
attrib_qname = etree.QName(key)
if attrib_qname.namespace:
attrib['%s:%s' % (nsprefixmap[attrib_qname.namespace], attrib_qname.localname)] = value
@@ -754,7 +757,7 @@ class QWeb(object):
if unqualified_el_tag == 't':
return content
- tag = u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in attrib.iteritems()]))
+ tag = u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in pycompat.items(attrib)]))
if unqualified_el_tag in self._void_elements:
return [self._append(ast.Str(tag + '/>'))] + content
else:
@@ -766,10 +769,10 @@ class QWeb(object):
# Etree will also remove the ns prefixes indirection in the attributes. As we only have
# the namespace definition, we'll use an nsmap where the keys are the definitions and
# the values the prefixes in order to get back the right prefix and restore it.
- nsprefixmap = {v: k for k, v in options['nsmap'].items() + el.nsmap.items()}
+ nsprefixmap = {v: k for k, v in itertools.chain(pycompat.items(options['nsmap']), pycompat.items(el.nsmap))}
nodes = []
- for key, value in el.attrib.iteritems():
+ for key, value in pycompat.items(el.attrib):
if not key.startswith('t-'):
attrib_qname = etree.QName(key)
if attrib_qname.namespace:
@@ -784,7 +787,7 @@ class QWeb(object):
We do not support namespaced dynamic attributes.
"""
nodes = []
- for name, value in el.attrib.iteritems():
+ for name, value in pycompat.items(el.attrib):
if name.startswith('t-attf-'):
nodes.append((name[7:], self._compile_format(value)))
elif name.startswith('t-att-'):
@@ -810,7 +813,7 @@ class QWeb(object):
def _compile_all_attributes(self, el, options, attr_already_created=False):
""" Compile the attributes of the given elements into a list of AST nodes. """
body = []
- if any(name.startswith('t-att') or not name.startswith('t-') for name, value in el.attrib.iteritems()):
+ if any(name.startswith('t-att') or not name.startswith('t-') for name, value in pycompat.items(el.attrib)):
if not attr_already_created:
attr_already_created = True
body.append(
@@ -851,7 +854,7 @@ class QWeb(object):
)))
if attr_already_created:
- # for name, value in t_attrs.iteritems():
+ # for name, value in pycompat.items(t_attrs):
# if value or isinstance(value, basestring)):
# append(u' ')
# append(name)
@@ -862,11 +865,11 @@ class QWeb(object):
target=ast.Tuple(elts=[ast.Name(id='name', ctx=ast.Store()), ast.Name(id='value', ctx=ast.Store())], ctx=ast.Store()),
iter=ast.Call(
func=ast.Attribute(
- value=ast.Name(id='t_attrs', ctx=ast.Load()),
- attr='iteritems',
+ value=ast.Name(id='pycompat', ctx=ast.Load()),
+ attr='items',
ctx=ast.Load()
),
- args=[], keywords=[],
+ args=[ast.Name(id='t_attrs', ctx=ast.Load())], keywords=[],
starargs=None, kwargs=None
),
body=[ast.If(
@@ -923,7 +926,7 @@ class QWeb(object):
# If `el` introduced new namespaces, write them as attribute by using the
# `extra_attrib` dict.
- for ns_prefix, ns_definition in set(el.nsmap.items()) - set(options['nsmap'].items()):
+ for ns_prefix, ns_definition in set(pycompat.items(el.nsmap)) - set(pycompat.items(options['nsmap'])):
if ns_prefix is None:
extra_attrib['xmlns'] = ns_definition
else:
@@ -932,7 +935,7 @@ class QWeb(object):
if unqualified_el_tag == 't':
return content
- body = [self._append(ast.Str(u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in extra_attrib.iteritems()]))))]
+ body = [self._append(ast.Str(u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in pycompat.items(extra_attrib)]))))]
body.extend(self._compile_all_attributes(el, options, attr_already_created))
if unqualified_el_tag in self._void_elements:
body.append(self._append(ast.Str(u'/>')))
@@ -1054,7 +1057,7 @@ class QWeb(object):
def _compile_directive_if(self, el, options):
orelse = []
next_el = el.getnext()
- if next_el is not None and {'t-else', 't-elif'} & set(next_el.attrib.keys()):
+ if next_el is not None and {'t-else', 't-elif'} & set(next_el.attrib):
if el.tail and not el.tail.isspace():
raise ValueError("Unexpected non-whitespace characters between t-if and t-else directives")
el.tail = None
@@ -1437,7 +1440,7 @@ class QWeb(object):
# make the nsmap an ast dict
keys = []
values = []
- for key, value in options['nsmap'].items():
+ for key, value in pycompat.items(options['nsmap']):
if isinstance(key, basestring):
keys.append(ast.Str(s=key))
elif key is None:
diff --git a/odoo/addons/base/ir/ir_sequence.py b/odoo/addons/base/ir/ir_sequence.py
index a46f5e95eca..58961e921ba 100644
--- a/odoo/addons/base/ir/ir_sequence.py
+++ b/odoo/addons/base/ir/ir_sequence.py
@@ -6,6 +6,7 @@ import pytz
from odoo import api, fields, models, _
from odoo.exceptions import UserError
+from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -178,7 +179,7 @@ class IrSequence(models.Model):
'weekday': '%w', 'h24': '%H', 'h12': '%I', 'min': '%M', 'sec': '%S'
}
res = {}
- for key, format in sequences.iteritems():
+ for key, format in pycompat.items(sequences):
res[key] = effective_date.strftime(format)
res['range_' + key] = range_date.strftime(format)
res['current_' + key] = now.strftime(format)
diff --git a/odoo/addons/base/ir/ir_translation.py b/odoo/addons/base/ir/ir_translation.py
index 7d73f40d65b..219ebcc471d 100644
--- a/odoo/addons/base/ir/ir_translation.py
+++ b/odoo/addons/base/ir/ir_translation.py
@@ -128,7 +128,7 @@ class IrTranslationImport(object):
env = api.Environment(cr, SUPERUSER_ID, {})
src_relevant_fields = []
for model in env:
- for field_name, field in env[model]._fields.items():
+ for field_name, field in pycompat.items(env[model]._fields):
if hasattr(field, 'translate') and callable(field.translate):
src_relevant_fields.append("%s,%s" % (model, field_name))
@@ -530,7 +530,7 @@ class IrTranslation(models.Model):
# check for read/write access on translated field records
fmode = 'read' if mode == 'read' else 'write'
- for mname, ids in model_ids.iteritems():
+ for mname, ids in pycompat.items(model_ids):
records = self.env[mname].browse(ids)
records.check_access_rights(fmode)
records.check_field_access_rights(fmode, model_fields[mname])
@@ -640,7 +640,7 @@ class IrTranslation(models.Model):
return ['&', ('res_id', '=', rec.id), ('name', '=', name)]
# insert missing translations, and extend domain for related fields
- for name, fld in record._fields.items():
+ for name, fld in pycompat.items(record._fields):
if not fld.translate:
continue
diff --git a/odoo/addons/base/ir/ir_ui_view.py b/odoo/addons/base/ir/ir_ui_view.py
index c557d15ee6b..0a709985ff6 100644
--- a/odoo/addons/base/ir/ir_ui_view.py
+++ b/odoo/addons/base/ir/ir_ui_view.py
@@ -8,6 +8,8 @@ import logging
import os
import re
import time
+
+import itertools
from dateutil.relativedelta import relativedelta
from operator import itemgetter
@@ -49,7 +51,7 @@ def keep_query(*keep_params, **additional_params):
if not keep_params and not additional_params:
keep_params = ('*',)
params = additional_params.copy()
- qs_keys = request.httprequest.args.keys()
+ qs_keys = list(request.httprequest.args)
for keep_param in keep_params:
for param in fnmatch.filter(qs_keys, keep_param):
if param not in additional_params and param in qs_keys:
@@ -251,11 +253,11 @@ actual arch.
@api.depends('arch')
def _compute_arch_base(self):
# 'arch_base' is the same as 'arch' without translation
- for view, view_wo_lang in zip(self, self.with_context(lang=None)):
+ for view, view_wo_lang in pycompat.izip(self, self.with_context(lang=None)):
view.arch_base = view_wo_lang.arch
def _inverse_arch_base(self):
- for view, view_wo_lang in zip(self, self.with_context(lang=None)):
+ for view, view_wo_lang in pycompat.izip(self, self.with_context(lang=None)):
view_wo_lang.arch = view.arch_base
@api.depends('write_date')
@@ -458,7 +460,7 @@ actual arch.
# not required. The root cause is the INNER JOIN
# used to implement it.
views = self.search(conditions + [('model_ids.module', 'in', tuple(self.pool._init_modules))])
- views = self.search(conditions + [('id', 'in', list(self._context.get('check_view_ids') or (0,)) + map(int, views))])
+ views = self.search(conditions + [('id', 'in', list(self._context.get('check_view_ids') or (0,)) + views.ids)])
else:
views = self.search(conditions)
@@ -581,10 +583,16 @@ actual arch.
separator = child.get('separator', ',')
if separator == ' ':
separator = None # squash spaces
- to_add = filter(bool, map(str.strip, child.get('add', '').split(separator)))
- to_remove = map(str.strip, child.get('remove', '').split(separator))
- values = map(str.strip, node.get(attribute, '').split(separator))
- value = (separator or ' ').join(filter(lambda s: s not in to_remove, values) + to_add)
+ to_add = (
+ s for s in (s.strip() for s in child.get('add', '').split(separator))
+ if s
+ )
+ to_remove = {s.strip() for s in child.get('remove', '').split(separator)}
+ values = (s.strip() for s in node.get(attribute, '').split(separator))
+ value = (separator or ' ').join(itertools.chain(
+ (v for v in values if v not in to_remove),
+ to_add
+ ))
if value:
node.set(attribute, value)
elif attribute in node.attrib:
@@ -834,7 +842,7 @@ actual arch.
collect(arch, self.env[model_name])
- for field, nodes in field_nodes.iteritems():
+ for field, nodes in pycompat.items(field_nodes):
# if field should trigger an onchange, add on_change="1" on the
# nodes referring to field
model = self.env[field.model_name]
@@ -900,7 +908,7 @@ actual arch.
node.set(action, 'false')
arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
- for k in fields.keys():
+ for k in list(fields):
if k not in fields_def:
del fields[k]
for field in fields_def:
@@ -924,7 +932,7 @@ actual arch.
@tools.conditional(
'xml' not in config['dev_mode'],
tools.ormcache('frozenset(self.env.user.groups_id.ids)', 'view_id',
- 'tuple(map(self._context.get, self._read_template_keys()))'),
+ 'tuple(self._context.get(k) for k in self._read_template_keys())'),
)
def _read_template(self, view_id):
arch = self.browse(view_id).read_combined(['arch'])['arch']
@@ -1091,12 +1099,12 @@ actual arch.
Model = self.env[model]
Node = self.env[node_obj]
- for model_key, model_value in Model._fields.iteritems():
+ for model_key, model_value in pycompat.items(Model._fields):
if model_value.type == 'one2many':
if model_value.comodel_name == node_obj:
_Node_Field = model_key
_Model_Field = model_value.inverse_name
- for node_key, node_value in Node._fields.iteritems():
+ for node_key, node_value in pycompat.items(Node._fields):
if node_value.type == 'one2many':
if node_value.comodel_name == conn_obj:
# _Source_Field = "Incoming Arrows" (connected via des_node)
@@ -1159,7 +1167,7 @@ actual arch.
GROUP BY coalesce(v.inherit_id, v.id)"""
self._cr.execute(query, [model])
- rec = self.browse(map(itemgetter(0), self._cr.fetchall()))
+ rec = self.browse(it[0] for it in self._cr.fetchall())
return rec.with_context({'load_all_views': True})._check_xml()
@api.model
@@ -1173,7 +1181,7 @@ actual arch.
xmlid_filter = "AND md.name IN %s"
names = tuple(
name
- for (xmod, name), (model, res_id) in self.pool.model_data_reference_ids.items()
+ for (xmod, name), (model, res_id) in pycompat.items(self.pool.model_data_reference_ids)
if xmod == module and model == self._name
)
if not names:
diff --git a/odoo/addons/base/ir/ir_values.py b/odoo/addons/base/ir/ir_values.py
index 3672d7811c2..ace2998b2b3 100644
--- a/odoo/addons/base/ir/ir_values.py
+++ b/odoo/addons/base/ir/ir_values.py
@@ -5,7 +5,7 @@ from ast import literal_eval
from odoo import api, fields, models, tools, _
from odoo.exceptions import AccessError, MissingError, ValidationError
-from odoo.tools import pickle
+from odoo.tools import pickle, pycompat
import logging
_logger = logging.getLogger(__name__)
@@ -315,7 +315,7 @@ class IrValues(models.Model):
for row in self._cr.dictfetchall():
value = pickle.loads(row['value'].encode('utf-8'))
defaults.setdefault(row['name'], (row['id'], row['name'], value))
- return defaults.values()
+ return list(pycompat.values(defaults))
# use ormcache: this is called a lot by BaseModel.default_get()!
@api.model
@@ -426,4 +426,4 @@ class IrValues(models.Model):
results[name] = (id, name, action_def)
except (AccessError, MissingError):
continue
- return sorted(results.values())
+ return sorted(pycompat.values(results))
diff --git a/odoo/addons/base/module/module.py b/odoo/addons/base/module/module.py
index ae36ccd1727..c8a83a01bf1 100644
--- a/odoo/addons/base/module/module.py
+++ b/odoo/addons/base/module/module.py
@@ -10,6 +10,8 @@ import shutil
import tempfile
import zipfile
+from odoo.tools import pycompat
+
try:
from urllib import parse as urlparse
from urllib.request import urlopen
@@ -209,9 +211,9 @@ class Module(models.Model):
def format_view(v):
return '%s%s (%s)' % (v.inherit_id and '* INHERIT ' or '', v.name, v.type)
- module.views_by_module = "\n".join(sorted(map(format_view, browse('ir.ui.view'))))
- module.reports_by_module = "\n".join(sorted(map(attrgetter('name'), browse('ir.actions.report'))))
- module.menus_by_module = "\n".join(sorted(map(attrgetter('complete_name'), browse('ir.ui.menu'))))
+ module.views_by_module = "\n".join(sorted(format_view(v) for v in browse('ir.ui.view')))
+ module.reports_by_module = "\n".join(sorted(r.name for r in browse('ir.actions.report')))
+ module.menus_by_module = "\n".join(sorted(m.complete_name for m in browse('ir.ui.menu')))
@api.depends('icon')
def _get_icon_image(self):
@@ -668,7 +670,7 @@ class Module(models.Model):
_logger.debug('Install from url: %r', urls)
try:
# 1. Download & unzip missing modules
- for module_name, url in urls.iteritems():
+ for module_name, url in pycompat.items(urls):
if not url:
continue # nothing to download, local version is already the last one
@@ -687,7 +689,7 @@ class Module(models.Model):
assert os.path.isdir(os.path.join(tmp, module_name))
# 2a. Copy/Replace module source in addons path
- for module_name, url in urls.iteritems():
+ for module_name, url in pycompat.items(urls):
if module_name == OPENERP or not url:
continue # OPENERP is special case, handled below, and no URL means local module
module_path = modules.get_module_path(module_name, downloaded=True, display_warning=False)
@@ -719,11 +721,11 @@ class Module(models.Model):
self.update_list()
- with_urls = [module_name for module_name, url in urls.iteritems() if url]
+ with_urls = [module_name for module_name, url in pycompat.items(urls) if url]
downloaded = self.search([('name', 'in', with_urls)])
installed = self.search([('id', 'in', downloaded.ids), ('state', '=', 'installed')])
- to_install = self.search([('name', 'in', urls.keys()), ('state', '=', 'uninstalled')])
+ to_install = self.search([('name', 'in', list(urls)), ('state', '=', 'uninstalled')])
post_install_action = to_install.button_immediate_install()
if installed or to_install:
diff --git a/odoo/addons/base/module/report/ir_module_reference_print.py b/odoo/addons/base/module/report/ir_module_reference_print.py
index 1f2dff53243..a933cdd9430 100644
--- a/odoo/addons/base/module/report/ir_module_reference_print.py
+++ b/odoo/addons/base/module/report/ir_module_reference_print.py
@@ -2,6 +2,8 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
+from odoo.tools import pycompat
+
class IrModelReferenceReport(models.AbstractModel):
_name = 'report.base.report_irmodulereference'
@@ -21,7 +23,7 @@ class IrModelReferenceReport(models.AbstractModel):
if data:
res_ids = data.mapped('res_id')
fnames = self.env['ir.model.fields'].browse(res_ids).mapped('name')
- return sorted(self.env[model].fields_get(fnames).iteritems())
+ return sorted(pycompat.items(self.env[model].fields_get(fnames)))
return []
@api.model
diff --git a/odoo/addons/base/res/ir_property.py b/odoo/addons/base/res/ir_property.py
index 8e1b7eda860..ec086fb6c61 100644
--- a/odoo/addons/base/res/ir_property.py
+++ b/odoo/addons/base/res/ir_property.py
@@ -217,7 +217,7 @@ class Property(models.Model):
prop.write({'value': value})
# create new properties for records that do not have one yet
- for ref, id in refs.iteritems():
+ for ref, id in pycompat.items(refs):
value = clean(values[id])
if value != default_value:
self.create({
@@ -248,13 +248,13 @@ class Property(models.Model):
elif operator in ('!=', '<=', '<', '>', '>='):
value = makeref(value)
elif operator in ('in', 'not in'):
- value = map(makeref, value)
+ value = [makeref(v) for v in value]
elif operator in ('=like', '=ilike', 'like', 'not like', 'ilike', 'not ilike'):
# most probably inefficient... but correct
target = self.env[comodel]
target_names = target.name_search(value, operator=operator, limit=None)
- target_ids = map(itemgetter(0), target_names)
- operator, value = 'in', map(makeref, target_ids)
+ target_ids = [n[0] for n in target_names]
+ operator, value = 'in', [makeref(v) for v in target_ids]
elif field.type in ('integer', 'float'):
# No record is created in ir.property if the field's type is float or integer with a value
# equal to 0. Then to match with the records that are linked to a property field equal to 0,
diff --git a/odoo/addons/base/res/res_company.py b/odoo/addons/base/res/res_company.py
index 96e922f5e77..9dfdba96cba 100644
--- a/odoo/addons/base/res/res_company.py
+++ b/odoo/addons/base/res/res_company.py
@@ -6,6 +6,7 @@ import re
from odoo import api, fields, models, tools, _
from odoo.exceptions import ValidationError
+from odoo.tools import pycompat
class Company(models.Model):
@@ -146,7 +147,7 @@ class Company(models.Model):
if self.country_id:
res['domain']['state_id'] = [('country_id', '=', self.country_id.id)]
values = self.on_change_country(self.country_id.id)['value']
- for fname, value in values.iteritems():
+ for fname, value in pycompat.items(values):
setattr(self, fname, value)
return res
diff --git a/odoo/addons/base/res/res_config.py b/odoo/addons/base/res/res_config.py
index 59764fca1e8..a19c6406dcb 100644
--- a/odoo/addons/base/res/res_config.py
+++ b/odoo/addons/base/res/res_config.py
@@ -9,7 +9,7 @@ from lxml import etree
from odoo import api, models, registry, SUPERUSER_ID, _
from odoo.exceptions import AccessError, RedirectWarning, UserError
-from odoo.tools import ustr
+from odoo.tools import ustr, pycompat
_logger = logging.getLogger(__name__)
@@ -257,7 +257,7 @@ class ResConfigInstaller(models.TransientModel, ResConfigModuleInstallationMixin
installer
:rtype: [str]
"""
- return map(attrgetter('name'), self._already_installed())
+ return [m.name for m in self._already_installed()]
def _already_installed(self):
""" For each module (boolean fields in a res.config.installer),
@@ -267,7 +267,7 @@ class ResConfigInstaller(models.TransientModel, ResConfigModuleInstallationMixin
:returns: a list of all installed modules in this installer
:rtype: recordset (collection of Record)
"""
- selectable = [name for name, field in self._fields.iteritems()
+ selectable = [name for name, field in pycompat.items(self._fields)
if field.type == 'boolean']
return self.env['ir.module.module'].search([('name', 'in', selectable),
('state', 'in', ['to install', 'installed', 'to upgrade'])])
@@ -292,7 +292,7 @@ class ResConfigInstaller(models.TransientModel, ResConfigModuleInstallationMixin
"""
base = set(module_name
for installer in self.read()
- for module_name, to_install in installer.iteritems()
+ for module_name, to_install in pycompat.items(installer)
if self._fields[module_name].type == 'boolean' and to_install)
hooks_results = set()
@@ -302,7 +302,7 @@ class ResConfigInstaller(models.TransientModel, ResConfigModuleInstallationMixin
hooks_results.update(hook() or set())
additionals = set(module
- for requirements, consequences in self._install_if.iteritems()
+ for requirements, consequences in pycompat.items(self._install_if)
if base.issuperset(requirements)
for module in consequences)
@@ -462,13 +462,13 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin)
ref = self.env.ref
defaults, groups, modules, others = [], [], [], []
- for name, field in self._fields.iteritems():
+ for name, field in pycompat.items(self._fields):
if name.startswith('default_') and hasattr(field, 'default_model'):
defaults.append((name, field.default_model, name[8:]))
elif name.startswith('group_') and field.type in ('boolean', 'selection') and \
hasattr(field, 'implied_group'):
field_group_xmlids = getattr(field, 'group', 'base.group_user').split(',')
- field_groups = Groups.concat(*map(ref, field_group_xmlids))
+ field_groups = Groups.concat(*(ref(it) for it in field_group_xmlids))
groups.append((name, field_groups, ref(field.implied_group)))
elif name.startswith('module_') and field.type in ('boolean', 'selection'):
module = IrModule.sudo().search([('name', '=', name[7:])], limit=1)
diff --git a/odoo/addons/base/res/res_lang.py b/odoo/addons/base/res/res_lang.py
index 19a5b9be339..f4e2d7a1578 100644
--- a/odoo/addons/base/res/res_lang.py
+++ b/odoo/addons/base/res/res_lang.py
@@ -8,6 +8,7 @@ import re
from operator import itemgetter
from odoo import api, fields, models, tools, _
+from odoo.tools import pycompat
from odoo.tools.safe_eval import safe_eval
from odoo.exceptions import UserError, ValidationError
@@ -22,7 +23,7 @@ class Lang(models.Model):
_description = "Languages"
_order = "active desc,name"
- _disallowed_datetime_patterns = tools.DATETIME_FORMATS_MAP.keys()
+ _disallowed_datetime_patterns = list(tools.DATETIME_FORMATS_MAP)
_disallowed_datetime_patterns.remove('%y') # this one is in fact allowed, just not good practice
name = fields.Char(required=True)
@@ -125,7 +126,7 @@ class Lang(models.Model):
# For some locales, nl_langinfo returns a D_FMT/T_FMT that contains
# unsupported '%-' patterns, e.g. for cs_CZ
format = format.replace('%-', '%')
- for pattern, replacement in tools.DATETIME_FORMATS_MAP.iteritems():
+ for pattern, replacement in pycompat.items(tools.DATETIME_FORMATS_MAP):
format = format.replace(pattern, replacement)
return str(format)
@@ -318,5 +319,5 @@ def intersperse(string, counts, separator=''):
left, rest, right = intersperse_pat.match(string).groups()
def reverse(s): return s[::-1]
splits = split(reverse(rest), counts)
- res = separator.join(map(reverse, reverse(splits)))
+ res = separator.join(reverse(s) for s in reverse(splits))
return left + res + right, len(splits) > 0 and len(splits) -1 or 0
diff --git a/odoo/addons/base/res/res_partner.py b/odoo/addons/base/res/res_partner.py
index fe758d36de1..17e6507a01e 100644
--- a/odoo/addons/base/res/res_partner.py
+++ b/odoo/addons/base/res/res_partner.py
@@ -680,7 +680,7 @@ class Partner(models.Model):
query += ' limit %s'
where_clause_params.append(limit)
self.env.cr.execute(query, where_clause_params)
- partner_ids = map(lambda x: x[0], self.env.cr.fetchall())
+ partner_ids = [row[0] for row in self.env.cr.fetchall()]
if partner_ids:
return self.browse(partner_ids).name_get()
diff --git a/odoo/addons/base/res/res_users.py b/odoo/addons/base/res/res_users.py
index ae8863eee4d..106fc32d56f 100644
--- a/odoo/addons/base/res/res_users.py
+++ b/odoo/addons/base/res/res_users.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import pytz
import datetime
+import itertools
import logging
from collections import defaultdict
@@ -13,7 +14,7 @@ from odoo import api, fields, models, tools, SUPERUSER_ID, _
from odoo.exceptions import AccessDenied, AccessError, UserError, ValidationError
from odoo.osv import expression
from odoo.service.db import check_super
-from odoo.tools import partition
+from odoo.tools import partition, pycompat
_logger = logging.getLogger(__name__)
@@ -29,7 +30,7 @@ def name_boolean_group(id):
return 'in_group_' + str(id)
def name_selection_groups(ids):
- return 'sel_groups_' + '_'.join(map(str, ids))
+ return 'sel_groups_' + '_'.join(str(it) for it in ids)
def is_boolean_group(name):
return name.startswith('in_group_')
@@ -44,7 +45,7 @@ def get_boolean_group(name):
return int(name[9:])
def get_selection_groups(name):
- return map(int, name[11:].split('_'))
+ return [int(v) for v in name[11:].split('_')]
def parse_m2m(commands):
"return a list of ids corresponding to a many2many value"
@@ -92,7 +93,7 @@ class Groups(models.Model):
@api.depends('category_id.name', 'name')
def _compute_full_name(self):
# Important: value must be stored in environment of group, not group1!
- for group, group1 in zip(self, self.sudo()):
+ for group, group1 in pycompat.izip(self, self.sudo()):
if group1.category_id:
group.full_name = '%s / %s' % (group1.category_id.name, group1.name)
else:
@@ -111,7 +112,7 @@ class Groups(models.Model):
operand = [operand]
where = []
for group in operand:
- values = filter(bool, group.split('/'))
+ values = [v for v in group.split('/') if v]
group_name = values.pop().strip()
category_name = values and '/'.join(values).strip() or group_name
group_domain = [('name', operator, lst and [group_name] or group_name)]
@@ -299,13 +300,11 @@ class Users(models.Model):
canwrite = self.env['ir.model.access'].check('res.users', 'write', False)
if not canwrite:
- def override_password(vals):
- if (vals['id'] != self._uid):
+ for vals in result:
+ if vals['id'] != self._uid:
for key in USER_PRIVATE_FIELDS:
if key in vals:
vals[key] = '********'
- return vals
- result = map(override_password, result)
return result
@@ -343,7 +342,7 @@ class Users(models.Model):
raise UserError(_("You cannot deactivate the user you're currently logged in as."))
if self == self.env.user:
- for key in values.keys():
+ for key in list(values):
if not (key in self.SELF_WRITEABLE_FIELDS or key.startswith('context_')):
break
else:
@@ -624,7 +623,7 @@ class GroupsImplied(models.Model):
if values.get('users') or values.get('implied_ids'):
# add all implied groups (to all users of each group)
for group in self:
- vals = {'users': zip(repeat(4), group.with_context(active_test=False).users.ids)}
+ vals = {'users': list(pycompat.izip(repeat(4), group.with_context(active_test=False).users.ids))}
super(GroupsImplied, group.trans_implied_ids).write(vals)
return res
@@ -766,7 +765,7 @@ class GroupsView(models.Model):
# determine sequence order: a group appears after its implied groups
order = {g: len(g.trans_implied_ids & gs) for g in gs}
# check whether order is total, i.e., sequence orders are distinct
- if len(set(order.itervalues())) == len(gs):
+ if len(set(pycompat.values(order))) == len(gs):
return (app, 'selection', gs.sorted(key=order.get))
else:
return (app, 'boolean', gs)
@@ -780,7 +779,7 @@ class GroupsView(models.Model):
others += g
# build the result
res = []
- for app, gs in sorted(by_app.iteritems(), key=lambda it: it[0].sequence or 0):
+ for app, gs in sorted(pycompat.items(by_app), key=lambda it: it[0].sequence or 0):
res.append(linearize(app, gs))
if others:
res.append((self.env['ir.module.category'], 'boolean', others))
@@ -820,7 +819,7 @@ class UsersView(models.Model):
add, rem = [], []
values1 = {}
- for key, val in values.iteritems():
+ for key, val in pycompat.items(values):
if is_boolean_group(key):
(add if val else rem).append(get_boolean_group(key))
elif is_selection_groups(key):
@@ -832,7 +831,10 @@ class UsersView(models.Model):
if 'groups_id' not in values and (add or rem):
# remove group ids in `rem` and add group ids in `add`
- values1['groups_id'] = zip(repeat(3), rem) + zip(repeat(4), add)
+ values1['groups_id'] = list(itertools.chain(
+ pycompat.izip(repeat(3), rem),
+ pycompat.izip(repeat(4), add)
+ ))
return values1
@@ -847,7 +849,7 @@ class UsersView(models.Model):
@api.multi
def read(self, fields=None, load='_classic_read'):
# determine whether reified groups fields are required, and which ones
- fields1 = fields or self.fields_get().keys()
+ fields1 = fields or list(self.fields_get())
group_fields, other_fields = partition(is_reified_group, fields1)
# read regular fields (other_fields); add 'groups_id' if necessary
diff --git a/odoo/addons/base/tests/test_api.py b/odoo/addons/base/tests/test_api.py
index 8270d6086d5..55cf1ff26b5 100644
--- a/odoo/addons/base/tests/test_api.py
+++ b/odoo/addons/base/tests/test_api.py
@@ -79,11 +79,11 @@ class TestAPI(common.TransactionCase):
domain = [('name', 'ilike', 'j')]
partners = self.env['res.partner'].search(domain)
self.assertTrue(partners)
- ids = map(int, partners)
+ ids = partners.ids
# modify those partners, and check that partners has not changed
partners.write({'active': False})
- self.assertEqual(ids, map(int, partners))
+ self.assertEqual(ids, partners.ids)
# redo the search, and check that the result is now empty
partners2 = self.env['res.partner'].search(domain)
@@ -98,7 +98,7 @@ class TestAPI(common.TransactionCase):
self.assertIsRecordset(user.groups_id, 'res.groups')
partners = self.env['res.partner'].search([])
- for name, field in partners._fields.iteritems():
+ for name, field in pycompat.items(partners._fields):
if field.type == 'many2one':
for p in partners:
self.assertIsRecord(p[name], field.comodel_name)
@@ -296,7 +296,7 @@ class TestAPI(common.TransactionCase):
self.assertItemsEqual(partners.ids, country_id_cache)
# partners' countries are ready for prefetching
- country_ids = set(cid for cids in country_id_cache.itervalues() for cid in cids)
+ country_ids = set(cid for cids in pycompat.values(country_id_cache) for cid in cids)
self.assertTrue(len(country_ids) > 1)
self.assertItemsEqual(country_ids, partners._prefetch['res.country'])
diff --git a/odoo/addons/base/tests/test_expression.py b/odoo/addons/base/tests/test_expression.py
index 4dea4797b0c..87099e78754 100644
--- a/odoo/addons/base/tests/test_expression.py
+++ b/odoo/addons/base/tests/test_expression.py
@@ -5,7 +5,7 @@ import psycopg2
from odoo.models import BaseModel
from odoo.tests.common import TransactionCase
-from odoo.tools import mute_logger
+from odoo.tools import mute_logger, pycompat
import odoo.osv.expression as expression
@@ -85,10 +85,10 @@ class TestExpression(TransactionCase):
'b ab': [cids['B'], cids['AB']],
}
pids = {}
- for name, cat_ids in partners_config.iteritems():
+ for name, cat_ids in pycompat.items(partners_config):
pids[name] = partners.create({'name': name, 'category_id': [(6, 0, cat_ids)]}).id
- base_domain = [('id', 'in', pids.values())]
+ base_domain = [('id', 'in', list(pycompat.values(pids)))]
def test(op, value, expected):
found_ids = partners.search(base_domain + [('category_id', op, value)]).ids
diff --git a/odoo/addons/base/tests/test_float.py b/odoo/addons/base/tests/test_float.py
index 8c4ee380bd2..725ba48365b 100644
--- a/odoo/addons/base/tests/test_float.py
+++ b/odoo/addons/base/tests/test_float.py
@@ -98,10 +98,10 @@ class TestFloatPrecision(TransactionCase):
precisions = [2, 2, 2, 2, 2, 2, 3, 4]
# Note: max precision for double floats is 53 bits of precision or
# 17 significant decimal digits
- for magnitude in pycompat.range(7):
- for frac, exp, prec in zip(fractions, expecteds, precisions):
+ for magnitude in range(7):
+ for frac, exp, prec in pycompat.izip(fractions, expecteds, precisions):
for sign in [-1,1]:
- for x in pycompat.range(0, 10000, 97):
+ for x in range(0, 10000, 97):
n = x * 10 ** magnitude
f = sign * (n + frac)
f_exp = ('-' if f != 0 and sign == -1 else '') + str(n) + exp
diff --git a/odoo/addons/base/tests/test_ir_filters.py b/odoo/addons/base/tests/test_ir_filters.py
index d932f16be11..b8de7590ad2 100644
--- a/odoo/addons/base/tests/test_ir_filters.py
+++ b/odoo/addons/base/tests/test_ir_filters.py
@@ -4,11 +4,12 @@
from odoo import exceptions
from odoo.tests.common import TransactionCase, ADMIN_USER_ID
-def noid(d):
+def noid(seq):
""" Removes values that are not relevant for the test comparisons """
- d.pop('id', None)
- d.pop('action_id', None)
- return d
+ for d in seq:
+ d.pop('id', None)
+ d.pop('action_id', None)
+ return seq
class FiltersCase(TransactionCase):
@@ -34,7 +35,7 @@ class TestGetFilters(FiltersCase):
filters = self.env['ir.filters'].sudo(self.USER_ID).get_filters('ir.filters')
- self.assertItemsEqual(map(noid, filters), [
+ self.assertItemsEqual(noid(filters), [
dict(name='a', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'),
dict(name='b', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'),
dict(name='c', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'),
@@ -52,7 +53,7 @@ class TestGetFilters(FiltersCase):
filters = self.env['ir.filters'].sudo(self.USER_ID).get_filters('ir.filters')
- self.assertItemsEqual(map(noid, filters), [
+ self.assertItemsEqual(noid(filters), [
dict(name='a', is_default=False, user_id=False, domain='[]', context='{}', sort='[]'),
dict(name='b', is_default=False, user_id=False, domain='[]', context='{}', sort='[]'),
dict(name='c', is_default=False, user_id=False, domain='[]', context='{}', sort='[]'),
@@ -69,7 +70,7 @@ class TestGetFilters(FiltersCase):
filters = self.env['ir.filters'].sudo(self.USER_ID).get_filters('ir.filters')
- self.assertItemsEqual(map(noid, filters), [
+ self.assertItemsEqual(noid(filters), [
dict(name='a', is_default=False, user_id=False, domain='[]', context='{}', sort='[]'),
dict(name='c', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'),
])
@@ -95,7 +96,7 @@ class TestOwnDefaults(FiltersCase):
})
filters = Filters.get_filters('ir.filters')
- self.assertItemsEqual(map(noid, filters), [
+ self.assertItemsEqual(noid(filters), [
dict(name='a', user_id=self.USER_NG, is_default=True,
domain='[]', context='{}', sort='[]')
])
@@ -120,7 +121,7 @@ class TestOwnDefaults(FiltersCase):
})
filters = Filters.get_filters('ir.filters')
- self.assertItemsEqual(map(noid, filters), [
+ self.assertItemsEqual(noid(filters), [
dict(name='a', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'),
dict(name='b', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'),
dict(name='c', user_id=self.USER_NG, is_default=True, domain='[]', context='{}', sort='[]'),
@@ -146,7 +147,7 @@ class TestOwnDefaults(FiltersCase):
})
filters = Filters.get_filters('ir.filters')
- self.assertItemsEqual(map(noid, filters), [
+ self.assertItemsEqual(noid(filters), [
dict(name='a', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'),
dict(name='b', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'),
dict(name='c', user_id=self.USER_NG, is_default=True, domain='[]', context='{}', sort='[]'),
@@ -172,7 +173,7 @@ class TestOwnDefaults(FiltersCase):
})
filters = Filters.get_filters('ir.filters')
- self.assertItemsEqual(map(noid, filters), [
+ self.assertItemsEqual(noid(filters), [
dict(name='a', user_id=self.USER_NG, is_default=True, domain='[]', context='{}', sort='[]'),
dict(name='b', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'),
])
@@ -204,7 +205,7 @@ class TestGlobalDefaults(FiltersCase):
})
filters = Filters.get_filters('ir.filters')
- self.assertItemsEqual(map(noid, filters), [
+ self.assertItemsEqual(noid(filters), [
dict(name='a', user_id=False, is_default=False, domain='[]', context='{}', sort='[]'),
dict(name='b', user_id=False, is_default=False, domain='[]', context='{}', sort='[]'),
dict(name='c', user_id=False, is_default=True, domain='[]', context='{}', sort='[]'),
@@ -271,7 +272,7 @@ class TestGlobalDefaults(FiltersCase):
})
filters = Filters.get_filters('ir.filters')
- self.assertItemsEqual(map(noid, filters), [
+ self.assertItemsEqual(noid(filters), [
dict(name='a', user_id=False, is_default=False, domain='[]', context='{}', sort='[]'),
dict(name='b', user_id=False, is_default=True, domain='[]', context=context_value, sort='[]'),
])
diff --git a/odoo/addons/base/tests/test_orm.py b/odoo/addons/base/tests/test_orm.py
index 0da71a84ad5..bf95bee08f7 100644
--- a/odoo/addons/base/tests/test_orm.py
+++ b/odoo/addons/base/tests/test_orm.py
@@ -141,7 +141,7 @@ class TestORM(TransactionCase):
partner_ids_by_year = defaultdict(list)
partners = self.env['res.partner']
- for name, date in partners_data.items():
+ for name, date in pycompat.items(partners_data):
p = partners.create(dict(name=name, date=date))
partner_ids.append(p.id)
partner_ids_by_day[date].append(p.id)
@@ -297,7 +297,7 @@ class TestO2MSerialization(TransactionCase):
def test_CREATE_commands(self):
" returns the VALUES dict as-is "
values = [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}]
- results = self.env['res.partner'].resolve_2many_commands('child_ids', map(CREATE, values))
+ results = self.env['res.partner'].resolve_2many_commands('child_ids', [CREATE(v) for v in values])
self.assertEqual(results, values)
def test_LINK_TO_command(self):
@@ -307,7 +307,7 @@ class TestO2MSerialization(TransactionCase):
self.env['res.partner'].create({'name': 'bar'}).id,
self.env['res.partner'].create({'name': 'baz'}).id,
]
- commands = map(LINK_TO, ids)
+ commands = [LINK_TO(v) for v in ids]
results = self.env['res.partner'].resolve_2many_commands('child_ids', commands, ['name'])
self.assertItemsEqual(results, [
@@ -356,7 +356,7 @@ class TestO2MSerialization(TransactionCase):
self.env['res.partner'].create({'name': 'bar'}).id,
self.env['res.partner'].create({'name': 'baz'}).id,
]
- commands = map(DELETE, ids)
+ commands = [DELETE(v) for v in ids]
results = self.env['res.partner'].resolve_2many_commands('child_ids', commands, ['name'])
self.assertEqual(results, [])
diff --git a/odoo/addons/base/tests/test_qweb.py b/odoo/addons/base/tests/test_qweb.py
index dcf41b092d6..cd459fe7669 100644
--- a/odoo/addons/base/tests/test_qweb.py
+++ b/odoo/addons/base/tests/test_qweb.py
@@ -13,6 +13,7 @@ from itertools import chain
from odoo.modules import get_module_resource
from odoo.tests.common import TransactionCase
from odoo.addons.base.ir.ir_qweb import QWebException
+from odoo.tools import pycompat
def dedent_and_strip(string):
@@ -411,7 +412,7 @@ class TestQWebNS(TransactionCase):
'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2',
'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2'
}
- self.assertSetEqual(set(expected_ns.items()) - set(result_etree.nsmap.items()), set())
+ self.assertSetEqual(set(pycompat.items(expected_ns)) - set(pycompat.items(result_etree.nsmap)), set())
# check that the t-call did its work
cac_lines = result_etree.findall('.//cac:line', namespaces={'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2'})
diff --git a/odoo/addons/base/tests/test_search.py b/odoo/addons/base/tests/test_search.py
index d52cd9db9c4..97670fdd805 100644
--- a/odoo/addons/base/tests/test_search.py
+++ b/odoo/addons/base/tests/test_search.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests.common import TransactionCase
+from odoo.tools import pycompat
class test_search(TransactionCase):
@@ -107,7 +108,7 @@ class test_search(TransactionCase):
user_ids[u] = Users.create({'name': u, 'login': u}).id
cron_ids[u] = Cron.create({'name': u, 'model_id': self.env.ref('base.model_res_partner').id, 'user_id': user_ids[u]}).id
- ids = Cron.search([('id', 'in', cron_ids.values())], order='user_id').ids
+ ids = Cron.search([('id', 'in', list(pycompat.values(cron_ids)))], order='user_id').ids
expected_ids = [cron_ids[l] for l in 'ABC']
self.assertEqual(ids, expected_ids)
@@ -127,7 +128,7 @@ class test_search(TransactionCase):
create('F', parent_id=cat_ids['D'])
expected_ids = [cat_ids[x] for x in 'ADEFBC']
- found_ids = Cats.search([('id', 'in', cat_ids.values())]).ids
+ found_ids = Cats.search([('id', 'in', list(pycompat.values(cat_ids)))]).ids
self.assertEqual(found_ids, expected_ids)
def test_13_m2o_order_loop_multi(self):
diff --git a/odoo/addons/base/tests/test_views.py b/odoo/addons/base/tests/test_views.py
index bd462932e5f..40cd922340c 100644
--- a/odoo/addons/base/tests/test_views.py
+++ b/odoo/addons/base/tests/test_views.py
@@ -638,7 +638,7 @@ class TestViews(ViewCase):
kw.setdefault('mode', 'extension' if kw.get('inherit_id') else 'primary')
kw.setdefault('active', True)
- keys = sorted(kw.keys())
+ keys = sorted(kw)
fields = ','.join('"%s"' % (k.replace('"', r'\"'),) for k in keys)
params = ','.join('%%(%s)s' % (k,) for k in keys)
diff --git a/odoo/addons/test_impex/tests/test_export.py b/odoo/addons/test_impex/tests/test_export.py
index 226ba9943f6..72a3b2a6f75 100644
--- a/odoo/addons/test_impex/tests/test_export.py
+++ b/odoo/addons/test_impex/tests/test_export.py
@@ -4,6 +4,7 @@
import itertools
from odoo.tests import common
+from odoo.tools import pycompat
class CreatorCase(common.TransactionCase):
@@ -494,9 +495,9 @@ class test_o2m_multiple(CreatorCase):
"""
fields = ['const', 'child1/value', 'child2/value']
child1 = [(0, False, {'value': v, 'str': 'record%.02d' % index})
- for index, v in zip(itertools.count(), [4, 42, 36, 4, 13])]
+ for index, v in pycompat.izip(itertools.count(), [4, 42, 36, 4, 13])]
child2 = [(0, False, {'value': v, 'str': 'record%.02d' % index})
- for index, v in zip(itertools.count(10), [8, 12, 8, 55, 33, 13])]
+ for index, v in pycompat.izip(itertools.count(10), [8, 12, 8, 55, 33, 13])]
self.assertEqual(
self.export(child1=child1, child2=False, fields=fields),
diff --git a/odoo/addons/test_limits/models.py b/odoo/addons/test_limits/models.py
index 506578a811b..957e3c3d6f9 100644
--- a/odoo/addons/test_limits/models.py
+++ b/odoo/addons/test_limits/models.py
@@ -5,7 +5,6 @@ import time
import sys
from odoo import models, api
-from odoo.tools import pycompat
class m(models.Model):
""" This model exposes a few methods that will consume between 'almost no
@@ -39,7 +38,7 @@ class m(models.Model):
t0 = time.clock()
t1 = time.clock()
while t1 - t0 < seconds:
- for i in pycompat.range(10000000):
+ for i in range(10000000):
x = i * i
t1 = time.clock()
return True
diff --git a/odoo/addons/test_new_api/models.py b/odoo/addons/test_new_api/models.py
index c84827d47ce..2f288e89f74 100644
--- a/odoo/addons/test_new_api/models.py
+++ b/odoo/addons/test_new_api/models.py
@@ -5,6 +5,7 @@ import datetime
from odoo import models, fields, api, _
from odoo.exceptions import AccessError, ValidationError
+from odoo.tools import pycompat
class Category(models.Model):
@@ -45,7 +46,7 @@ class Category(models.Model):
categories.append(category[0])
categories.append(self)
# assign parents following sequence
- for parent, child in zip(categories, categories[1:]):
+ for parent, child in pycompat.izip(categories, categories[1:]):
if parent and child:
child.parent = parent
# assign name of last category, and reassign display_name (to normalize it)
@@ -364,4 +365,4 @@ class ComputeRecursive(models.Model):
if rec.parent:
rec.display_name = rec.parent.display_name + " / " + rec.name
else:
- rec.display_name = rec.name
\ No newline at end of file
+ rec.display_name = rec.name
diff --git a/odoo/addons/test_new_api/tests/test_one2many.py b/odoo/addons/test_new_api/tests/test_one2many.py
index 9ade2c0fe19..e1ad547e95e 100644
--- a/odoo/addons/test_new_api/tests/test_one2many.py
+++ b/odoo/addons/test_new_api/tests/test_one2many.py
@@ -45,7 +45,7 @@ class One2manyCase(TransactionCase):
# Check the lines first
self.assertItemsEqual(
self.multi.lines.mapped('name'),
- map(str, range(10)))
+ [str(i) for i in range(10)])
# Modify the first line and drop the last one
self.multi.lines[0].name = "hello"
self.multi.lines = self.multi.lines[:-1]
diff --git a/odoo/addons/test_pylint/tests/test_pylint.py b/odoo/addons/test_pylint/tests/test_pylint.py
index 1ae544e56a6..f98dcf88b3c 100644
--- a/odoo/addons/test_pylint/tests/test_pylint.py
+++ b/odoo/addons/test_pylint/tests/test_pylint.py
@@ -30,22 +30,33 @@ class TestPyLint(TransactionCase):
'relative-import',
'deprecated-module',
'import-star-module-level',
- # 'bad-python3-import', # TODO: more stuff used in report
+
+ 'bad-builtin',
+
+ 'dict-iter-method',
+ 'dict-view-method',
'long-suffix',
- 'apply-builtin',
- 'cmp-builtin',
- 'coerce-builtin',
- 'execfile-builtin',
- 'input-builtin',
- 'intern-builtin',
- 'long-builtin',
- 'raw_input-builtin',
- 'reload-builtin',
- 'xrange-builtin',
+ ]
+
+ BAD_FUNCTIONS = [
+ 'apply',
+ 'cmp',
+ 'coerce',
+ 'execfile',
+ 'input',
+ 'intern',
+ 'long',
+ 'raw_input',
+ 'reload',
+ 'xrange',
+ 'long',
+ 'map',
+ 'filter',
+ 'zip',
# TODO: enable once report has been removed
- # 'file-builtin',
- # 'reduce-builtin',
+ # 'file',
+ # 'reduce',
]
def _skip_test(self, reason):
@@ -69,6 +80,8 @@ class TestPyLint(TransactionCase):
'--enable=%s' % ','.join(self.ENABLED_CODES),
'--reports=n',
"--msg-template='{msg} ({msg_id}) at {path}:{line}'",
+ '--load-plugins=pylint.extensions.bad_builtin',
+ '--bad-functions=%s' % ','.join(self.BAD_FUNCTIONS),
]
try:
diff --git a/odoo/api.py b/odoo/api.py
index 1adbd08f6e5..dc51870f20a 100644
--- a/odoo/api.py
+++ b/odoo/api.py
@@ -55,7 +55,7 @@ from weakref import WeakSet
from decorator import decorator
from werkzeug.local import Local, release_local
-from odoo.tools import frozendict, classproperty
+from odoo.tools import frozendict, classproperty, pycompat
_logger = logging.getLogger(__name__)
@@ -85,7 +85,7 @@ class Params(object):
params = []
for arg in self.args:
params.append(repr(arg))
- for item in sorted(self.kwargs.iteritems()):
+ for item in sorted(pycompat.items(self.kwargs)):
params.append("%s=%r" % item)
return ', '.join(params)
@@ -100,7 +100,7 @@ class Meta(type):
# dummy parent class to catch overridden methods decorated with 'returns'
parent = type.__new__(meta, name, bases, {})
- for key, value in attrs.items():
+ for key, value in list(pycompat.items(attrs)):
if not key.startswith('__') and callable(value):
# make the method inherit from decorators
value = propagate(getattr(parent, key, None), value)
@@ -916,7 +916,7 @@ class Environment(Mapping):
def remove_todo(self, field, records):
""" Mark ``field`` as recomputed on ``records``. """
recs_list = [recs - records for recs in self.all.todo.pop(field, [])]
- recs_list = filter(None, recs_list)
+ recs_list = [r for r in recs_list if r]
if recs_list:
self.all.todo[field] = recs_list
@@ -938,15 +938,14 @@ class Environment(Mapping):
# make a full copy of the cache, and invalidate it
cache_dump = dict(
(field, dict(field_cache))
- for field, field_cache in self.cache.iteritems()
+ for field, field_cache in pycompat.items(self.cache)
)
self.invalidate_all()
# re-fetch the records, and compare with their former cache
invalids = []
- for field, field_dump in cache_dump.iteritems():
- ids = filter(None, field_dump)
- records = self[field.model_name].browse(ids)
+ for field, field_dump in pycompat.items(cache_dump):
+ records = self[field.model_name].browse(f for f in field_dump if f)
for record in records:
try:
cached = field_dump[record.id]
diff --git a/odoo/cli/command.py b/odoo/cli/command.py
index 2ab042f58e8..503ffcb360a 100644
--- a/odoo/cli/command.py
+++ b/odoo/cli/command.py
@@ -28,7 +28,7 @@ class Help(Command):
"""Display the list of available commands"""
def run(self, args):
print("Available commands:\n")
- names = commands.keys()
+ names = list(commands)
padding = max([len(k) for k in names]) + 2
for k in sorted(names):
name = k.ljust(padding, ' ')
diff --git a/odoo/fields.py b/odoo/fields.py
index e200d7b32dc..8d5d865968b 100644
--- a/odoo/fields.py
+++ b/odoo/fields.py
@@ -335,7 +335,7 @@ class Field(object):
def __init__(self, string=Default, **kwargs):
kwargs['string'] = string
- args = {key: val for key, val in kwargs.iteritems() if val is not Default}
+ args = {key: val for key, val in pycompat.items(kwargs) if val is not Default}
self.args = args or EMPTY_DICT
self._setup_done = None
@@ -364,7 +364,7 @@ class Field(object):
""" Set all field attributes at once (with slot defaults). """
# optimization: we assign slots only
assign = object.__setattr__
- for key, val in self._slots.iteritems():
+ for key, val in pycompat.items(self._slots):
assign(self, key, attrs.pop(key, val))
if attrs:
assign(self, '_attrs', attrs)
@@ -542,7 +542,7 @@ class Field(object):
if not getattr(self, attr):
setattr(self, attr, getattr(field, prop))
- for attr, value in field._attrs.iteritems():
+ for attr, value in pycompat.items(field._attrs):
if attr not in self._attrs:
setattr(self, attr, value)
@@ -565,7 +565,7 @@ class Field(object):
""" Compute the related field ``self`` on ``records``. """
# when related_sudo, bypass access rights checks when reading values
others = records.sudo() if self.related_sudo else records
- for record, other in zip(records, others):
+ for record, other in pycompat.izip(records, others):
if not record.id and record.env != other.env:
# draft records: copy record's cache to other's cache first
copy_cache(record, other.env)
@@ -650,7 +650,7 @@ class Field(object):
model = model0.env.get(field.comodel_name)
# add self's model dependencies
- for mname, fnames in model0._depends.iteritems():
+ for mname, fnames in pycompat.items(model0._depends):
model = model0.env[mname]
for fname in fnames:
field = model._fields[fname]
@@ -1002,7 +1002,7 @@ class Field(object):
# HACK: if result is in the wrong cache, copy values
if recs.env != env:
computed = record._field_computed[self]
- for source, target in zip(recs, recs.with_env(env)):
+ for source, target in pycompat.izip(recs, recs.with_env(env)):
try:
values = target._convert_to_cache({
f.name: source[f.name] for f in computed
@@ -1071,8 +1071,8 @@ class Field(object):
for field, path in records._field_triggers[self]:
bymodel[field.model_name][path].append(field)
- for model_name, bypath in bymodel.iteritems():
- for path, fields in bypath.iteritems():
+ for model_name, bypath in pycompat.items(bymodel):
+ for path, fields in pycompat.items(bypath):
if path and any(field.compute and field.store for field in fields):
# process stored fields
stored = set(field for field in fields if field.compute and field.store)
@@ -1755,7 +1755,7 @@ class Selection(Field):
if 'selection_add' in field.args:
# use an OrderedDict to update existing values
selection_add = field.args['selection_add']
- self.selection = OrderedDict(self.selection + selection_add).items()
+ self.selection = list(pycompat.items(OrderedDict(self.selection + selection_add)))
def _description_selection(self, env):
""" return the selection list (pairs (value, label)); labels are
@@ -1934,7 +1934,7 @@ class Many2one(_Relational):
super(Many2one, self)._setup_attrs(model, name)
# determine self.delegate
if not self.delegate:
- self.delegate = name in model._inherits.values()
+ self.delegate = name in pycompat.values(model._inherits)
def update_db(self, model, columns):
comodel = model.env[self.comodel_name]
diff --git a/odoo/http.py b/odoo/http.py
index 0b3ad105e39..fa5b44d1226 100644
--- a/odoo/http.py
+++ b/odoo/http.py
@@ -52,7 +52,7 @@ import odoo
from odoo.service.server import memory_info
from odoo.service import security, model as service_model
from odoo.tools.func import lazy_property
-from odoo.tools import ustr, consteq, frozendict
+from odoo.tools import ustr, consteq, frozendict, pycompat, unique
from odoo.modules.module import module_manifest
@@ -288,8 +288,8 @@ class WebRequest(object):
def set_handler(self, endpoint, arguments, auth):
# is this needed ?
- arguments = dict((k, v) for k, v in arguments.iteritems()
- if not k.startswith("_ignored_"))
+ arguments ={k: v for k, v in pycompat.items(arguments)
+ if not k.startswith("_ignored_")}
self.endpoint_arguments = arguments
self.endpoint = endpoint
self.auth_method = auth
@@ -837,7 +837,7 @@ more details.
"""
response = Response(data, headers=headers)
if cookies:
- for k, v in cookies.iteritems():
+ for k, v in pycompat.items(cookies):
response.set_cookie(k, v)
return response
@@ -878,7 +878,7 @@ class ControllerType(type):
super(ControllerType, cls).__init__(name, bases, attrs)
# flag old-style methods with req as first argument
- for k, v in attrs.items():
+ for k, v in pycompat.items(attrs):
if inspect.isfunction(v) and hasattr(v, 'original_func'):
# Set routing type on original functions
routing_type = v.routing.get('type')
@@ -941,14 +941,12 @@ def routing_map(modules, nodb_only, converters=None):
result = [klass]
return result
- uniq = lambda it: collections.OrderedDict((id(x), x) for x in it).values()
-
for module in modules:
if module not in controllers_per_module:
continue
for _, cls in controllers_per_module[module]:
- subclasses = uniq(c for c in get_subclasses(cls) if c is not cls)
+ subclasses = list(unique(c for c in get_subclasses(cls) if c is not cls))
if subclasses:
name = "%s (extended by %s)" % (cls.__name__, ', '.join(sub.__name__ for sub in subclasses))
cls = type(name, tuple(reversed(subclasses)), {})
@@ -1050,7 +1048,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
security.check(self.db, self.uid, self.password)
def logout(self, keep_db=False):
- for k in self.keys():
+ for k in list(self):
if not (keep_db and k == 'db'):
del self[k]
self._default_values()
@@ -1137,7 +1135,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
# NOTE we do not store files in the session itself to avoid loading them in memory.
# By storing them in the session store, we ensure every worker (even ones on other
# servers) can access them. It also allow stale files to be deleted by `session_gc`.
- for f in req.files.values():
+ for f in pycompat.values(req.files):
storename = 'werkzeug_%s_%s.file' % (self.sid, uuid.uuid4().hex)
path = os.path.join(root.session_store.path, storename)
with open(path, 'w') as fp:
@@ -1155,7 +1153,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
try:
if data:
# regenerate files filenames with the current session store
- for name, (storename, filename, content_type) in data['files'].iteritems():
+ for name, (storename, filename, content_type) in pycompat.items(data['files']):
path = os.path.join(root.session_store.path, storename)
files.add(name, (path, filename, content_type))
yield werkzeug.datastructures.CombinedMultiDict([data['form'], files])
@@ -1163,7 +1161,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
yield None
finally:
# cleanup files
- for f, _, _ in files.values():
+ for f, _, _ in pycompat.values(files):
try:
os.unlink(f)
except IOError:
diff --git a/odoo/models.py b/odoo/models.py
index a1535123c9d..a9448b3ca05 100644
--- a/odoo/models.py
+++ b/odoo/models.py
@@ -108,9 +108,6 @@ def check_method_name(name):
if regex_private.match(name):
raise AccessError(_('Private methods (such as %s) cannot be called remotely.') % (name,))
-def intersect(la, lb):
- return filter(lambda x: x in lb, la)
-
def same_name(f, g):
""" Test whether functions ``f`` and ``g`` are identical or have the same name """
return f == g or getattr(f, '__name__', 0) == getattr(g, '__name__', 1)
@@ -150,7 +147,7 @@ class MetaModel(api.Meta):
self.module_to_models[self._module].append(self)
# check for new-api conversion error: leave comma after field definition
- for key, val in attrs.iteritems():
+ for key, val in pycompat.items(attrs):
if type(val) is tuple and len(val) == 1 and isinstance(val[0], Field):
_logger.error("Trailing comma after field definition: %s.%s", self, key)
if isinstance(val, Field):
@@ -515,7 +512,7 @@ class BaseModel(object):
cls._inherits.update(base._inherits)
- for mname, fnames in base._depends.iteritems():
+ for mname, fnames in pycompat.items(base._depends):
cls._depends[mname] = cls._depends.get(mname, []) + fnames
for cons in base._constraints:
@@ -525,7 +522,7 @@ class BaseModel(object):
cls._sql_constraints += base._sql_constraints
cls._sequence = cls._sequence or (cls._table + '_id_seq')
- cls._constraints = cls._constraints.values()
+ cls._constraints = list(pycompat.values(cls._constraints))
# update _inherits_children of parent models
for parent_name in cls._inherits:
@@ -707,7 +704,7 @@ class BaseModel(object):
This method is used when exporting data via client menu
"""
- fields_to_export = map(fix_import_export_id_paths, fields_to_export)
+ fields_to_export = [fix_import_export_id_paths(f) for f in fields_to_export]
if raw_data:
self = self.with_context(export_raw_data=True)
return {'datas': self._export_rows(fields_to_export)}
@@ -740,7 +737,7 @@ class BaseModel(object):
cr = self._cr
cr.execute('SAVEPOINT model_load')
- fields = map(fix_import_export_id_paths, fields)
+ fields = [fix_import_export_id_paths(f) for f in fields]
fg = self.fields_get()
ids = []
@@ -835,7 +832,7 @@ class BaseModel(object):
# copy non-relational fields to record dict
record = {fnames[0]: value
- for fnames, value in itertools.izip(fields_, row)
+ for fnames, value in pycompat.izip(fields_, row)
if not is_relational(fnames[0])}
# Get all following rows which have relational values attached to
@@ -849,13 +846,13 @@ class BaseModel(object):
# get only cells for this sub-field, should be strictly
# non-empty, field path [None] is for name_get field
- indices, subfields = zip(*((index, fnames[1:] or [None])
+ indices, subfields = pycompat.izip(*((index, fnames[1:] or [None])
for index, fnames in enumerate(fields_)
if fnames[0] == relfield))
# return all rows which have at least one value for the
# subfields of relfield
- relfield_data = filter(any, map(itemgetter_tuple(indices), record_span))
+ relfield_data = [it for it in pycompat.imap(itemgetter_tuple(indices), record_span) if any(it)]
record[relfield] = [
subrecord
for subrecord, _subinfo in comodel._extract_records(subfields, relfield_data, log=log)
@@ -876,7 +873,7 @@ class BaseModel(object):
:returns: a list of triplets of (id, xid, record)
:rtype: list((int|None, str|None, dict))
"""
- field_names = {name: field.string for name, field in self._fields.iteritems()}
+ field_names = {name: field.string for name, field in pycompat.items(self._fields)}
if self.env.lang:
field_names.update(self.env['ir.translation'].get_field_string(self._name))
@@ -1007,7 +1004,7 @@ class BaseModel(object):
defaults = self._convert_to_write(defaults)
# add default values for inherited fields
- for model, names in parent_fields.iteritems():
+ for model, names in pycompat.items(parent_fields):
defaults.update(self.env[model].default_get(names))
return defaults
@@ -1083,7 +1080,7 @@ class BaseModel(object):
:rtype: etree._Element
"""
group = E.group(col="4")
- for fname, field in self._fields.iteritems():
+ for fname, field in pycompat.items(self._fields):
if field.automatic:
continue
elif field.type in ('one2many', 'many2many', 'text', 'html'):
@@ -1500,14 +1497,14 @@ class BaseModel(object):
# avoid overriding inherited values when parent is set
avoid_models = {
parent_model
- for parent_model, parent_field in self._inherits.iteritems()
+ for parent_model, parent_field in pycompat.items(self._inherits)
if parent_field in values
}
# compute missing fields
missing_defaults = {
name
- for name, field in self._fields.iteritems()
+ for name, field in pycompat.items(self._fields)
if name not in values
if name not in MAGIC_COLUMNS
if not (field.inherited and field.related_field.model_name in avoid_models)
@@ -1518,7 +1515,7 @@ class BaseModel(object):
# override defaults with the provided values, never allow the other way around
defaults = self.default_get(list(missing_defaults))
- for name, value in defaults.iteritems():
+ for name, value in pycompat.items(defaults):
if self._fields[name].type == 'many2many' and value and isinstance(value[0], pycompat.integer_types):
# convert a list of ids into a list of commands
defaults[name] = [(6, 0, value)]
@@ -1603,10 +1600,10 @@ class BaseModel(object):
if field.relational and groups._fold_name in groups._fields:
fold = {group.id: group[groups._fold_name]
for group in groups.browse([key for key in result if key])}
- for key, line in result.iteritems():
+ for key, line in pycompat.items(result):
line['__fold'] = fold.get(key, False)
- return result.values()
+ return list(pycompat.values(result))
@api.model
def _read_group_prepare(self, orderby, aggregated_fields, annotated_groupbys, query):
@@ -1840,7 +1837,7 @@ class BaseModel(object):
def _read_group_raw(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
self.check_access_rights('read')
query = self._where_calc(domain)
- fields = fields or [f.name for f in self._fields.itervalues() if f.store]
+ fields = fields or [f.name for f in pycompat.values(self._fields) if f.store]
groupby = [groupby] if isinstance(groupby, basestring) else list(OrderedSet(groupby))
groupby_list = groupby[:1] if lazy else groupby
@@ -1914,7 +1911,7 @@ class BaseModel(object):
self._read_group_resolve_many2one_fields(fetched_data, annotated_groupbys)
- data = map(lambda r: {k: self._read_group_prepare_data(k,v, groupby_dict) for k,v in r.iteritems()}, fetched_data)
+ data = ({k: self._read_group_prepare_data(k,v, groupby_dict) for k,v in pycompat.items(r)} for r in fetched_data)
result = [self._read_group_format_result(d, annotated_groupbys, groupby, domain) for d in data]
if lazy:
# Right now, read_group only fill results in lazy mode (by default).
@@ -2020,7 +2017,7 @@ class BaseModel(object):
# fields which were required but have been removed (or will be added by
# another module)
cr = self._cr
- cols = [name for name, field in self._fields.iteritems()
+ cols = [name for name, field in pycompat.items(self._fields)
if field.store and field.column_type]
cr.execute("SELECT a.attname, a.attnotnull"
" FROM pg_class c, pg_attribute a"
@@ -2120,7 +2117,7 @@ class BaseModel(object):
recs = self.with_context(active_test=False).search([])
recs._recompute_todo(field)
- for field in self._fields.itervalues():
+ for field in pycompat.values(self._fields):
if not field.store:
continue
@@ -2208,9 +2205,9 @@ class BaseModel(object):
""" Determine inherited fields. """
# determine candidate inherited fields
fields = {}
- for parent_model, parent_field in self._inherits.iteritems():
+ for parent_model, parent_field in pycompat.items(self._inherits):
parent = self.env[parent_model]
- for name, field in parent._fields.iteritems():
+ for name, field in pycompat.items(parent._fields):
# inherited fields are implemented as related fields, with the
# following specific properties:
# - reading inherited fields should not bypass access rights
@@ -2223,13 +2220,13 @@ class BaseModel(object):
)
# add inherited fields that are not redefined locally
- for name, field in fields.iteritems():
+ for name, field in pycompat.items(fields):
if name not in self._fields:
self._add_field(name, field)
@api.model
def _inherits_check(self):
- for table, field_name in self._inherits.items():
+ for table, field_name in pycompat.items(self._inherits):
field = self._fields.get(field_name)
if not field:
_logger.info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.', field_name, self._name)
@@ -2242,7 +2239,7 @@ class BaseModel(object):
field.ondelete = "cascade"
# reflect fields with delegate=True in dictionary self._inherits
- for field in self._fields.itervalues():
+ for field in pycompat.values(self._fields):
if field.type == 'many2one' and not field.related and field.delegate:
if not field.required:
_logger.warning("Field %s with delegate=True must be required.", field)
@@ -2330,7 +2327,7 @@ class BaseModel(object):
# set up fields
bad_fields = []
- for name, field in cls._fields.iteritems():
+ for name, field in pycompat.items(cls._fields):
try:
field.setup_full(self)
except Exception:
@@ -2349,11 +2346,11 @@ class BaseModel(object):
# map each field to the fields computed with the same method
groups = defaultdict(list)
- for field in cls._fields.itervalues():
+ for field in pycompat.values(cls._fields):
if field.compute:
cls._field_computed[field] = group = groups[field.compute]
group.append(field)
- for fields in groups.itervalues():
+ for fields in pycompat.values(groups):
compute_sudo = fields[0].compute_sudo
if not all(field.compute_sudo == compute_sudo for field in fields):
_logger.warning("%s: inconsistent 'compute_sudo' for computed fields: %s",
@@ -2366,7 +2363,7 @@ class BaseModel(object):
if isinstance(self, Model):
# set up field triggers (on database-persisted models only)
- for field in cls._fields.itervalues():
+ for field in pycompat.values(cls._fields):
# dependencies of custom fields may not exist; ignore that case
exceptions = (Exception,) if field.manual else ()
with tools.ignore(*exceptions):
@@ -2405,7 +2402,7 @@ class BaseModel(object):
readonly = not (has_access('write') or has_access('create'))
res = {}
- for fname, field in self._fields.iteritems():
+ for fname, field in pycompat.items(self._fields):
if allfields and fname not in allfields:
continue
if field.groups and not self.user_has_groups(field.groups):
@@ -2417,7 +2414,7 @@ class BaseModel(object):
description['states'] = {}
if attributes:
description = {key: val
- for key, val in description.iteritems()
+ for key, val in pycompat.items(description)
if key in attributes}
res[fname] = description
@@ -2452,9 +2449,9 @@ class BaseModel(object):
return True
if not fields:
- fields = filter(valid, self._fields)
+ fields = [name for name in self._fields if valid(name)]
else:
- invalid_fields = set(filter(lambda name: not valid(name), fields))
+ invalid_fields = {name for name in fields if not valid(name)}
if invalid_fields:
_logger.info('Access Denied by ACLs for operation: %s, uid: %s, model: %s, fields: %s',
operation, self._uid, self._name, ', '.join(invalid_fields))
@@ -2528,7 +2525,7 @@ class BaseModel(object):
if self._context.get('prefetch_fields', True) and field.prefetch:
fs.update(
f
- for f in self._fields.itervalues()
+ for f in pycompat.values(self._fields)
# select fields that can be prefetched
if f.prefetch
# discard fields with groups that the user may not access
@@ -2591,7 +2588,7 @@ class BaseModel(object):
order_str = self._generate_order_by(None, query)
# determine the fields that are stored as columns in tables;
- fields = map(self._fields.get, field_names + inherited_field_names)
+ fields = [self._fields[n] for n in (field_names + inherited_field_names)]
fields_pre = [
field
for field in fields
@@ -2608,7 +2605,7 @@ class BaseModel(object):
res = 'pg_size_pretty(length(%s)::bigint)' % res
return '%s as "%s"' % (res, col)
- qual_names = map(qualify, set(fields_pre + [self._fields['id']]))
+ qual_names = [qualify(name) for name in set(fields_pre + [self._fields['id']])]
# determine the actual query to execute
from_clause, where_clause, params = query.get_sql()
@@ -2666,8 +2663,7 @@ class BaseModel(object):
if extras:
raise AccessError(
_("Database fetch misses ids ({}) and has extra ids ({}), may be caused by a type incoherence in a previous request").format(
- ', '.join(map(repr, missing._ids)),
- ', '.join(map(repr, extras._ids)),
+ missing._ids, extras._ids,
))
# mark non-existing records in missing
forbidden = missing.exists()
@@ -2990,7 +2986,7 @@ class BaseModel(object):
# split up fields into old-style and pure new-style ones
old_vals, new_vals, unknown = {}, {}, []
- for key, val in vals.iteritems():
+ for key, val in pycompat.items(vals):
field = self._fields.get(key)
if field:
if field.store or field.inherited:
@@ -3003,7 +2999,7 @@ class BaseModel(object):
if unknown:
_logger.warning("%s.write() with unknown fields: %s", self._name, ', '.join(sorted(unknown)))
- protected_fields = map(self._fields.get, new_vals)
+ protected_fields = [self._fields[n] for n in new_vals]
with self.env.protecting(protected_fields, self):
# write old-style fields with (low-level) method _write
if old_vals:
@@ -3054,7 +3050,7 @@ class BaseModel(object):
query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL) ORDER BY %s" % \
(self._table, self._parent_name, self._parent_order)
cr.execute(query, (tuple(self.ids),))
- parents_changed = map(operator.itemgetter(0), cr.fetchall())
+ parents_changed = [x[0] for x in cr.fetchall()]
updates = [] # list of (column, expr) or (column, pattern, value)
upd_todo = [] # list of column names to set explicitly
@@ -3062,7 +3058,7 @@ class BaseModel(object):
direct = [] # list of direcly updated columns
has_trans = self.env.lang and self.env.lang != 'en_US'
single_lang = len(self.env['res.lang'].get_installed()) <= 1
- for name, val in vals.iteritems():
+ for name, val in pycompat.items(vals):
field = self._fields[name]
if field and field.deprecated:
_logger.warning('Field %s.%s is deprecated: %s', self._name, name, field.deprecated)
@@ -3124,7 +3120,7 @@ class BaseModel(object):
# defaults in context must be removed when call a one2many or many2many
rel_context = {key: val
- for key, val in self._context.iteritems()
+ for key, val in pycompat.items(self._context)
if not key.startswith('default_')}
# call the 'write' method of fields which are not columns
@@ -3137,7 +3133,7 @@ class BaseModel(object):
# write inherited fields on the corresponding parent records
unknown_fields = set(updend)
- for parent_model, parent_field in self._inherits.iteritems():
+ for parent_model, parent_field in pycompat.items(self._inherits):
parent_ids = []
for sub_ids in cr.split_for_in_conditions(self.ids):
query = "SELECT DISTINCT %s FROM %s WHERE id IN %%s" % (parent_field, self._table)
@@ -3258,7 +3254,7 @@ class BaseModel(object):
# split up fields into old-style and pure new-style ones
old_vals, new_vals, unknown = {}, {}, []
- for key, val in vals.iteritems():
+ for key, val in pycompat.items(vals):
field = self._fields.get(key)
if field:
if field.store or field.inherited:
@@ -3274,7 +3270,7 @@ class BaseModel(object):
# create record with old-style fields
record = self.browse(self._create(old_vals))
- protected_fields = map(self._fields.get, new_vals)
+ protected_fields = [self._fields[n] for n in new_vals]
with self.env.protecting(protected_fields, record):
# put the values of pure new-style fields into cache, and inverse them
record.modified(set(new_vals) - set(old_vals))
@@ -3299,7 +3295,7 @@ class BaseModel(object):
# data of parent records to create or update, by model
tocreate = {
parent_model: {'id': vals.pop(parent_field, None)}
- for parent_model, parent_field in self._inherits.iteritems()
+ for parent_model, parent_field in pycompat.items(self._inherits)
}
# list of column assignments defined as tuples like:
@@ -3314,7 +3310,7 @@ class BaseModel(object):
upd_todo = []
unknown_fields = []
protected_fields = []
- for name, val in vals.items():
+ for name, val in list(pycompat.items(vals)):
field = self._fields.get(name)
if not field:
unknown_fields.append(name)
@@ -3330,7 +3326,7 @@ class BaseModel(object):
_logger.warning('No such field(s) in model %s: %s.', self._name, ', '.join(unknown_fields))
# create or update parent records
- for parent_model, parent_vals in tocreate.iteritems():
+ for parent_model, parent_vals in pycompat.items(tocreate):
parent_id = parent_vals.pop('id')
if not parent_id:
parent_id = self.env[parent_model].create(parent_vals).id
@@ -3339,12 +3335,12 @@ class BaseModel(object):
updates.append((self._inherits[parent_model], '%s', parent_id))
# set boolean fields to False by default (to make search more powerful)
- for name, field in self._fields.iteritems():
+ for name, field in pycompat.items(self._fields):
if field.type == 'boolean' and field.store and name not in vals:
vals[name] = False
# determine SQL values
- for name, val in vals.iteritems():
+ for name, val in pycompat.items(vals):
field = self._fields[name]
if field.store and field.column_type:
updates.append((name, field.column_format, field.convert_to_column(val, self)))
@@ -3375,7 +3371,7 @@ class BaseModel(object):
if self.env.lang and self.env.lang != 'en_US':
# add translations for self.env.lang
- for name, val in vals.iteritems():
+ for name, val in pycompat.items(vals):
field = self._fields[name]
if field.store and field.column_type and field.translate is True:
tname = "%s,%s" % (self._name, name)
@@ -3423,7 +3419,7 @@ class BaseModel(object):
# defaults in context must be removed when call a one2many or many2many
rel_context = {key: val
- for key, val in self._context.iteritems()
+ for key, val in pycompat.items(self._context)
if not key.startswith('default_')}
# call the 'write' method of fields which are not columns
@@ -3728,11 +3724,11 @@ class BaseModel(object):
# build a black list of fields that should not be copied
blacklist = set(MAGIC_COLUMNS + ['parent_left', 'parent_right'])
- whitelist = set(name for name, field in self._fields.iteritems() if not field.inherited)
+ whitelist = set(name for name, field in pycompat.items(self._fields) if not field.inherited)
def blacklist_given_fields(model):
# blacklist the fields that are given by inheritance
- for parent_model, parent_field in model._inherits.items():
+ for parent_model, parent_field in pycompat.items(model._inherits):
blacklist.add(parent_field)
if parent_field in default:
# all the fields of 'parent_model' are given by the record:
@@ -3741,17 +3737,17 @@ class BaseModel(object):
else:
blacklist_given_fields(self.env[parent_model])
# blacklist deprecated fields
- for name, field in model._fields.iteritems():
+ for name, field in pycompat.items(model._fields):
if field.deprecated:
blacklist.add(name)
blacklist_given_fields(self)
fields_to_copy = {name: field
- for name, field in self._fields.iteritems()
+ for name, field in pycompat.items(self._fields)
if field.copy and name not in default and name not in blacklist}
- for name, field in fields_to_copy.iteritems():
+ for name, field in pycompat.items(fields_to_copy):
if field.type == 'one2many':
# duplicate following the order of the ids because we'll rely on
# it later for copying translations in copy_translation()!
@@ -3789,7 +3785,7 @@ class BaseModel(object):
old_wo_lang, new_wo_lang = (old + new).with_context(lang=None)
Translation = old.env['ir.translation']
- for name, field in old._fields.iteritems():
+ for name, field in pycompat.items(old._fields):
if not field.copy:
continue
@@ -3799,7 +3795,7 @@ class BaseModel(object):
# foreseen in copy_data()
old_lines = old[name].sorted(key='id')
new_lines = new[name].sorted(key='id')
- for (old_line, new_line) in zip(old_lines, new_lines):
+ for (old_line, new_line) in pycompat.izip(old_lines, new_lines):
old_line.copy_translations(new_line)
elif field.translate:
@@ -3969,7 +3965,7 @@ class BaseModel(object):
"""
results = self._get_external_ids()
return {key: val[0] if val else ''
- for key, val in results.iteritems()}
+ for key, val in pycompat.items(results)}
# backwards compatibility
get_xml_id = get_external_id
@@ -4236,7 +4232,7 @@ class BaseModel(object):
""" List of actual record ids in this recordset (ignores placeholder
ids for records to create)
"""
- return filter(None, list(self._ids))
+ return [it for it in self._ids if it]
# backward-compatibility with former browse records
_cr = property(lambda self: self.env.cr)
@@ -4342,7 +4338,7 @@ class BaseModel(object):
target = self if update else self.browse([], self._prefetch)
return {
name: fields[name].convert_to_cache(value, target, validate=validate)
- for name, value in values.iteritems()
+ for name, value in pycompat.items(values)
if name in fields
}
@@ -4352,14 +4348,14 @@ class BaseModel(object):
"""
return {
name: self._fields[name].convert_to_record(value, self)
- for name, value in values.iteritems()
+ for name, value in pycompat.items(values)
}
def _convert_to_write(self, values):
""" Convert the ``values`` dictionary into the format of :meth:`write`. """
fields = self._fields
result = {}
- for name, value in values.iteritems():
+ for name, value in pycompat.items(values):
if name in fields:
field = fields[name]
value = field.convert_to_cache(value, self, validate=False)
@@ -4423,7 +4419,7 @@ class BaseModel(object):
"""
if isinstance(func, basestring):
name = func
- func = lambda rec: filter(None, rec.mapped(name))
+ func = lambda rec: any(rec.mapped(name))
return self.browse([rec.id for rec in self if func(rec)])
def sorted(self, key=None, reverse=False):
@@ -4440,13 +4436,13 @@ class BaseModel(object):
return self.browse(reversed(recs._ids)) if reverse else recs
if isinstance(key, basestring):
key = itemgetter(key)
- return self.browse(map(attrgetter('id'), sorted(self, key=key, reverse=reverse)))
+ return self.browse(item.id for item in sorted(self, key=key, reverse=reverse))
@api.multi
def update(self, values):
""" Update the records in ``self`` with ``values``. """
for record in self:
- for name, value in values.iteritems():
+ for name, value in pycompat.items(values):
record[name] = value
#
@@ -4667,7 +4663,7 @@ class BaseModel(object):
the records of model ``self`` in cache that have no value for ``field``
(:class:`Field` instance).
"""
- ids = filter(None, self._prefetch[self._name] - set(self.env.cache[field]))
+ ids = [it for it in self._prefetch[self._name] - set(self.env.cache[field]) if it]
return self.browse(ids)
@api.model
@@ -4690,9 +4686,9 @@ class BaseModel(object):
if fnames is None:
if ids is None:
return self.env.invalidate_all()
- fields = self._fields.values()
+ fields = list(pycompat.values(self._fields))
else:
- fields = map(self._fields.__getitem__, fnames)
+ fields = [self._fields[n] for n in fnames]
# invalidate fields and inverse fields, too
spec = [(f, ids) for f in fields] + \
@@ -4746,10 +4742,11 @@ class BaseModel(object):
updates[frozendict(vals)].add(rec.id)
# update records in batch when possible
with recs.env.norecompute():
- for vals, ids in updates.iteritems():
+ for vals, ids in pycompat.items(updates):
recs.browse(ids)._write(dict(vals))
# mark computed fields as done
- map(recs._recompute_done, fs)
+ for f in fs:
+ recs._recompute_done(f)
#
# Generic onchange method
@@ -4779,7 +4776,7 @@ class BaseModel(object):
if not result.get(names):
result[names] = node.attrib.get('on_change')
# traverse the subviews included in relational fields
- for subinfo in info['fields'][name].get('views', {}).itervalues():
+ for subinfo in pycompat.values(info['fields'][name].get('views', {})):
process(etree.fromstring(subinfo['arch']), subinfo, names)
else:
for child in node:
@@ -4802,19 +4799,19 @@ class BaseModel(object):
return
if res.get('value'):
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 res.get('domain'):
result.setdefault('domain', {}).update(res['domain'])
if res.get('warning'):
if result.get('warning'):
# Concatenate multiple warnings
warning = result['warning']
- warning['message'] = '\n\n'.join(filter(None, [
+ warning['message'] = '\n\n'.join(s for s in [
warning.get('title'),
warning.get('message'),
res['warning'].get('title'),
res['warning'].get('message'),
- ]))
+ ] if s)
warning['title'] = _('Warnings')
else:
result['warning'] = res['warning']
@@ -4931,7 +4928,7 @@ class BaseModel(object):
record.mapped(field_seq)
# determine which fields have been modified
- for name, oldval in values.iteritems():
+ for name, oldval in pycompat.items(values):
field = self._fields[name]
newval = record[name]
if newval != oldval or (
@@ -5005,7 +5002,7 @@ class RecordCache(MutableMapping):
"""
if args and isinstance(args[0], SpecialValue):
values = dict.fromkeys(self._recs._ids, args[0])
- for name, field in self._recs._fields.iteritems():
+ for name, field in pycompat.items(self._recs._fields):
if name != 'id':
self._recs.env.cache[field].update(values)
else:
@@ -5023,7 +5020,7 @@ class RecordCache(MutableMapping):
""" Iterate over the field names with a regular value in cache. """
cache, id = self._recs.env.cache, self._recs.id
dummy = SpecialValue(None)
- for name, field in self._recs._fields.iteritems():
+ for name, field in pycompat.items(self._recs._fields):
if name != 'id' and not isinstance(cache[field].get(id, dummy), SpecialValue):
yield name
diff --git a/odoo/modules/db.py b/odoo/modules/db.py
index 7106ff82b69..bde759843cf 100644
--- a/odoo/modules/db.py
+++ b/odoo/modules/db.py
@@ -99,7 +99,7 @@ def create_categories(cr, categories):
category = []
while categories:
category.append(categories[0])
- xml_id = 'module_category_' + ('_'.join(map(lambda x: x.lower(), category))).replace('&', 'and').replace(' ', '_')
+ xml_id = 'module_category_' + ('_'.join(x.lower() for x in category)).replace('&', 'and').replace(' ', '_')
# search via xml_id (because some categories are renamed)
cr.execute("SELECT res_id FROM ir_model_data WHERE name=%s AND module=%s AND model=%s",
(xml_id, "base", "ir.module.category"))
diff --git a/odoo/modules/graph.py b/odoo/modules/graph.py
index d962ea78bdc..731b6f6557a 100644
--- a/odoo/modules/graph.py
+++ b/odoo/modules/graph.py
@@ -8,7 +8,7 @@ import logging
import odoo
import odoo.tools as tools
-
+from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -36,7 +36,7 @@ class Graph(dict):
return
# update the graph with values from the database (if exist)
## First, we set the default values for each package in graph
- additional_data = dict((key, {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None}) for key in self.keys())
+ additional_data = {key: {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None} for key in pycompat.keys(self)}
## Then we get the values from the database
cr.execute('SELECT name, id, state, demo AS dbdemo, latest_version AS installed_version'
' FROM ir_module_module'
@@ -46,8 +46,8 @@ class Graph(dict):
## and we update the default values with values from the database
additional_data.update((x['name'], x) for x in cr.dictfetchall())
- for package in self.values():
- for k, v in additional_data[package.name].items():
+ for package in pycompat.values(self):
+ for k, v in pycompat.items(additional_data[package.name]):
setattr(package, k, v)
def add_module(self, cr, module, force=None):
@@ -94,7 +94,7 @@ class Graph(dict):
self.update_from_db(cr)
for package in later:
- unmet_deps = filter(lambda p: p not in self, dependencies[package])
+ unmet_deps = [p for p in dependencies[package] if p not in self]
_logger.error('module %s: Unmet dependencies: %s', package, ', '.join(unmet_deps))
return len(self) - len_graph
@@ -102,9 +102,9 @@ class Graph(dict):
def __iter__(self):
level = 0
- done = set(self.keys())
+ done = set(pycompat.keys(self))
while done:
- level_modules = sorted((name, module) for name, module in self.items() if module.depth==level)
+ level_modules = sorted((name, module) for name, module in pycompat.items(self) if module.depth==level)
for name, module in level_modules:
done.remove(name)
yield module
@@ -164,7 +164,10 @@ class Node(object):
setattr(child, name, value + 1)
def __iter__(self):
- return itertools.chain(iter(self.children), *map(iter, self.children))
+ return itertools.chain(
+ self.children,
+ itertools.chain.from_iterable(self.children)
+ )
def __str__(self):
return self._pprint()
diff --git a/odoo/modules/loading.py b/odoo/modules/loading.py
index f8c65d40177..bd58376a7b9 100644
--- a/odoo/modules/loading.py
+++ b/odoo/modules/loading.py
@@ -20,6 +20,7 @@ import odoo.tools as tools
from odoo import api, SUPERUSER_ID
from odoo.modules.module import adapt_version, initialize_sys_path, load_openerp_module
+from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
_test_logger = logging.getLogger('odoo.tests')
@@ -295,15 +296,15 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
_logger.info('updating modules list')
Module.update_list()
- _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
+ _check_module_names(cr, itertools.chain(tools.config['init'], tools.config['update']))
- module_names = [k for k, v in tools.config['init'].items() if v]
+ module_names = [k for k, v in pycompat.items(tools.config['init']) if v]
if module_names:
modules = Module.search([('state', '=', 'uninstalled'), ('name', 'in', module_names)])
if modules:
modules.button_install()
- module_names = [k for k, v in tools.config['update'].items() if v]
+ module_names = [k for k, v in pycompat.items(tools.config['update']) if v]
if module_names:
modules = Module.search([('state', '=', 'installed'), ('name', 'in', module_names)])
if modules:
@@ -391,7 +392,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
getattr(py_module, uninstall_hook)(cr, registry)
Module = env['ir.module.module']
- Module.browse(modules_to_remove.values()).module_uninstall()
+ Module.browse(pycompat.values(modules_to_remove)).module_uninstall()
# Recursive reload, should only happen once, because there should be no
# modules to remove next time
cr.commit()
@@ -414,7 +415,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
_logger.info('Modules loaded.')
# STEP 8: call _register_hook on every model
- for model in env.values():
+ for model in pycompat.values(env):
model._register_hook()
# STEP 9: save installed/updated modules for post-install tests
diff --git a/odoo/modules/migration.py b/odoo/modules/migration.py
index 5b1dcf09ab2..0fa6699cf24 100644
--- a/odoo/modules/migration.py
+++ b/odoo/modules/migration.py
@@ -13,6 +13,7 @@ from os.path import join as opj
from odoo.modules.module import get_resource_path
import odoo.release as release
import odoo.tools as tools
+from odoo.tools import pycompat
from odoo.tools.parse_version import parse_version
@@ -87,13 +88,12 @@ class MigrationManager(object):
return "%s.%s" % (release.major_version, version)
def _get_migration_versions(pkg):
- versions = list(set(
+ versions = sorted({
ver
- for lv in self.migrations[pkg.name].values()
- for ver, lf in lv.items()
+ for lv in pycompat.values(self.migrations[pkg.name])
+ for ver, lf in pycompat.items(lv)
if lf
- ))
- versions.sort(key=lambda k: parse_version(convert_version(k)))
+ }, key=lambda k: parse_version(convert_version(k)))
return versions
def _get_migration_files(pkg, version, stage):
@@ -107,7 +107,7 @@ class MigrationManager(object):
'maintenance': opj('base', 'maintenance', 'migrations', pkg.name),
}
- for x in mapping.keys():
+ for x in mapping:
if version in m.get(x):
for f in m[x][version]:
if not f.startswith(stage + '-'):
diff --git a/odoo/modules/module.py b/odoo/modules/module.py
index dd1485e1d8a..e6194a5fb10 100644
--- a/odoo/modules/module.py
+++ b/odoo/modules/module.py
@@ -23,6 +23,7 @@ import odoo
import odoo.tools as tools
import odoo.release as release
from odoo import SUPERUSER_ID, api
+from odoo.tools import pycompat
MANIFEST_NAMES = ('__manifest__.py', '__openerp__.py')
README = ['README.rst', 'README.md', 'README.txt']
@@ -323,7 +324,7 @@ def load_information_from_description_file(module, mod_path=None):
'sequence': 100,
'summary': '',
}
- info.update(itertools.izip(
+ info.update(pycompat.izip(
'depends data demo test init_xml update_xml demo_xml'.split(),
iter(list, None)))
@@ -395,7 +396,11 @@ def get_modules():
for mname in MANIFEST_NAMES:
if os.path.isfile(opj(dir, name, mname)):
return True
- return map(clean, filter(is_really_module, os.listdir(dir)))
+ return [
+ clean(it)
+ for it in os.listdir(dir)
+ if is_really_module(it)
+ ]
plist = []
initialize_sys_path()
@@ -492,7 +497,7 @@ def run_unit_tests(module_name, dbname, position=runs_at_install):
r = True
for m in mods:
tests = unwrap_suite(unittest.TestLoader().loadTestsFromModule(m))
- suite = unittest.TestSuite(itertools.ifilter(position, tests))
+ suite = unittest.TestSuite(t for t in tests if position(t))
if suite.countTestCases():
t0 = time.time()
@@ -532,5 +537,5 @@ def unwrap_suite(test):
return
for item in itertools.chain.from_iterable(
- itertools.imap(unwrap_suite, subtests)):
+ unwrap_suite(t) for t in subtests):
yield item
diff --git a/odoo/modules/registry.py b/odoo/modules/registry.py
index e0cfb532fd1..71d7b5d9fbe 100644
--- a/odoo/modules/registry.py
+++ b/odoo/modules/registry.py
@@ -16,7 +16,8 @@ import threading
import odoo
from .. import SUPERUSER_ID
from odoo.tools import (assertion_report, lazy_classproperty, config,
- lazy_property, table_exists, topological_sort, OrderedSet)
+ lazy_property, table_exists, topological_sort,
+ OrderedSet, pycompat)
from odoo.tools.lru import LRU
_logger = logging.getLogger(__name__)
@@ -156,7 +157,7 @@ class Registry(Mapping):
def delete_all(cls):
""" Delete all the registries. """
with cls._lock:
- for db_name in cls.registries.keys():
+ for db_name in list(pycompat.keys(cls.registries)):
cls.delete(db_name)
#
@@ -192,8 +193,8 @@ class Registry(Mapping):
# map fields on their dependents
dependents = {
field: set(dep for dep, _ in model._field_triggers[field] if dep != field)
- for model in self.itervalues()
- for field in model._fields.itervalues()
+ for model in pycompat.values(self)
+ for field in pycompat.values(model._fields)
}
# sort them topologically, and associate a sequence number to each field
mapping = {
@@ -261,7 +262,7 @@ class Registry(Mapping):
env['ir.model']._add_manual_models()
# prepare the setup on all models
- models = env.values()
+ models = list(pycompat.values(env))
for model in models:
model._prepare_setup()
@@ -310,18 +311,18 @@ class Registry(Mapping):
# make sure all tables are present
missing = [name
- for name, model in env.items()
+ for name, model in pycompat.items(env)
if not model._abstract and not table_exists(cr, model._table)]
if missing:
_logger.warning("Models have no table: %s.", ", ".join(missing))
# recreate missing tables following model dependencies
- deps = {name: model._depends for name, model in env.items()}
+ deps = {name: model._depends for name, model in pycompat.items(env)}
for name in topological_sort(deps):
if name in missing:
_logger.info("Recreate table of model %s.", name)
env[name].init()
# check again, and log errors if tables are still missing
- for name, model in env.items():
+ for name, model in pycompat.items(env):
if not model._abstract and not table_exists(cr, model._table):
_logger.error("Model %s has no table.", name)
@@ -340,7 +341,7 @@ class Registry(Mapping):
""" Clear the caches associated to methods decorated with
``tools.ormcache`` or ``tools.ormcache_multi`` for all the models.
"""
- for model in self.models.itervalues():
+ for model in pycompat.values(self.models):
model.clear_caches()
def setup_signaling(self):
diff --git a/odoo/osv/expression.py b/odoo/osv/expression.py
index b98fca44b39..8df300a2bd4 100644
--- a/odoo/osv/expression.py
+++ b/odoo/osv/expression.py
@@ -1018,7 +1018,7 @@ class expression(object):
subop = 'not inselect' if operator in NEGATIVE_TERM_OPERATORS else 'inselect'
subquery = 'SELECT "%s" FROM "%s" WHERE "%s" IN %%s' % (rel_id1, rel_table, rel_id2)
# avoid flattening of argument in to_sql()
- subquery = cr.mogrify(subquery, [tuple(filter(None, res_ids))])
+ subquery = cr.mogrify(subquery, [tuple(it for it in res_ids if it)])
push(create_substitution_leaf(leaf, ('id', subop, (subquery, [])), internal=True))
if call_null_m2m:
@@ -1200,7 +1200,7 @@ class expression(object):
else:
field = model._fields[left]
instr = ','.join([field.column_format] * len(params))
- params = map(partial(field.convert_to_column, record=model), params)
+ params = [field.convert_to_column(p, record=model) for p in params]
query = '(%s."%s" %s (%s))' % (table_alias, left, operator, instr)
else:
# The case for (left, 'in', []) or (left, 'not in', []).
diff --git a/odoo/osv/orm.py b/odoo/osv/orm.py
index 6a815c9577a..c06c4e66496 100644
--- a/odoo/osv/orm.py
+++ b/odoo/osv/orm.py
@@ -1,6 +1,7 @@
import json
from lxml import etree
+from odoo.tools import pycompat
from ..exceptions import except_orm
from ..models import (
MetaModel,
@@ -35,12 +36,12 @@ def transfer_field_to_modifiers(field, modifiers):
for attr in ('invisible', 'readonly', 'required'):
state_exceptions[attr] = []
default_values[attr] = bool(field.get(attr))
- for state, modifs in (field.get("states",{})).items():
+ for state, modifs in pycompat.items(field.get("states",{})):
for modif in modifs:
if default_values[modif[0]] != modif[1]:
state_exceptions[modif[0]].append(state)
- for attr, default_value in default_values.items():
+ for attr, default_value in pycompat.items(default_values):
if state_exceptions[attr]:
modifiers[attr] = [("state", "not in" if default_value else "in", state_exceptions[attr])]
else:
diff --git a/odoo/release.py b/odoo/release.py
index 51385a7a02b..c563d9ff4cd 100644
--- a/odoo/release.py
+++ b/odoo/release.py
@@ -13,8 +13,8 @@ RELEASE_LEVELS_DISPLAY = {ALPHA: ALPHA,
# (6,1,0,'beta',0) < (6,1,0,'candidate',1) < (6,1,0,'candidate',2)
# (6,1,0,'candidate',2) < (6,1,0,'final',0) < (6,1,2,'final',0)
version_info = (11, 0, 0, ALPHA, 1, '')
-version = '.'.join(map(str, version_info[:2])) + RELEASE_LEVELS_DISPLAY[version_info[3]] + str(version_info[4] or '') + version_info[5]
-series = serie = major_version = '.'.join(map(str, version_info[:2]))
+version = '.'.join(str(s) for s in version_info[:2]) + RELEASE_LEVELS_DISPLAY[version_info[3]] + str(version_info[4] or '') + version_info[5]
+series = serie = major_version = '.'.join(str(s) for s in version_info[:2])
product_name = 'Odoo'
description = 'Odoo Server'
diff --git a/odoo/service/db.py b/odoo/service/db.py
index e20dcb7442a..e451b151d31 100644
--- a/odoo/service/db.py
+++ b/odoo/service/db.py
@@ -351,7 +351,7 @@ def list_db_incompatible(databases):
:return: A list of databases that are incompatible
"""
incompatible_databases = []
- server_version = '.'.join(map(str, version_info[:2]))
+ server_version = '.'.join(str(v) for v in version_info[:2])
for database_name in databases:
with closing(db_connect(database_name).cursor()) as cr:
if odoo.tools.table_exists(cr, 'ir_module_module'):
diff --git a/odoo/service/model.py b/odoo/service/model.py
index ba243bf432c..78a07460a38 100644
--- a/odoo/service/model.py
+++ b/odoo/service/model.py
@@ -137,7 +137,7 @@ def check(f):
time.sleep(wait_time)
except IntegrityError as inst:
registry = odoo.registry(dbname)
- for key in registry._sql_error.keys():
+ for key in pycompat.keys(registry._sql_error):
if key in inst[0]:
raise ValidationError(tr(registry._sql_error[key], 'sql_constraint') or inst[0])
if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION):
diff --git a/odoo/service/server.py b/odoo/service/server.py
index 5de54d8188e..fa6ed422f81 100644
--- a/odoo/service/server.py
+++ b/odoo/service/server.py
@@ -40,7 +40,7 @@ from odoo.modules.module import run_unit_tests, runs_post_install
from odoo.modules.registry import Registry
from odoo.release import nt_service_name
import odoo.tools.config as config
-from odoo.tools import stripped_sys_argv, dumpstacks, log_ormcache_stats
+from odoo.tools import stripped_sys_argv, dumpstacks, log_ormcache_stats, pycompat
_logger = logging.getLogger(__name__)
@@ -216,7 +216,7 @@ class ThreadedServer(CommonServer):
time.sleep(SLEEP_INTERVAL + number) # Steve Reich timing style
registries = odoo.modules.registry.Registry.registries
_logger.debug('cron%d polling for jobs', number)
- for db_name, registry in registries.iteritems():
+ for db_name, registry in pycompat.items(registries):
while registry.ready:
try:
acquired = odoo.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name)
@@ -513,7 +513,7 @@ class PreforkServer(CommonServer):
def process_timeout(self):
now = time.time()
- for (pid, worker) in self.workers.items():
+ for (pid, worker) in pycompat.items(self.workers):
if worker.watchdog_timeout is not None and \
(now - worker.watchdog_time) >= worker.watchdog_timeout:
_logger.error("%s (%s) timeout after %ss",
@@ -534,8 +534,8 @@ class PreforkServer(CommonServer):
def sleep(self):
try:
# map of fd -> worker
- fds = dict([(w.watchdog_pipe[0], w) for k, w in self.workers.items()])
- fd_in = fds.keys() + [self.pipe[0]]
+ fds = {w.watchdog_pipe[0]: w for k, w in pycompat.items(self.workers)}
+ fd_in = list(pycompat.keys(fds)) + [self.pipe[0]]
# check for ping or internal wakeups
ready = select.select(fd_in, [], [], self.beat)
# update worker watchdogs
@@ -584,7 +584,7 @@ class PreforkServer(CommonServer):
if graceful:
_logger.info("Stopping gracefully")
limit = time.time() + self.timeout
- for pid in self.workers.keys():
+ for pid in self.workers:
self.worker_kill(pid, signal.SIGINT)
while self.workers and time.time() < limit:
try:
@@ -596,7 +596,7 @@ class PreforkServer(CommonServer):
time.sleep(0.1)
else:
_logger.info("Stopping forcefully")
- for pid in self.workers.keys():
+ for pid in self.workers:
self.worker_kill(pid, signal.SIGTERM)
if self.socket:
self.socket.close()
@@ -874,7 +874,7 @@ def load_test_file_yml(registry, test_file):
def load_test_file_py(registry, test_file):
# Locate python module based on its filename and run the tests
test_path, _ = os.path.splitext(os.path.abspath(test_file))
- for mod_name, mod_mod in sys.modules.items():
+ for mod_name, mod_mod in pycompat.items(sys.modules):
if mod_mod:
mod_path, _ = os.path.splitext(getattr(mod_mod, '__file__', ''))
if test_path == mod_path:
diff --git a/odoo/sql_db.py b/odoo/sql_db.py
index 9a438d387de..a722c3ea41b 100644
--- a/odoo/sql_db.py
+++ b/odoo/sql_db.py
@@ -47,7 +47,7 @@ def undecimalize(symb, cr):
return None
return float(symb)
-for name, typeoid in types_mapping.items():
+for name, typeoid in pycompat.items(types_mapping):
psycopg2.extensions.register_type(psycopg2.extensions.new_type(typeoid, name, lambda x, cr: x))
psycopg2.extensions.register_type(psycopg2.extensions.new_type((700, 701, 1700,), 'float', undecimalize))
@@ -190,9 +190,9 @@ class Cursor(object):
row = self._obj.fetchone()
return row and self.__build_dict(row)
def dictfetchmany(self, size):
- return map(self.__build_dict, self._obj.fetchmany(size))
+ return [self.__build_dict(row) for row in self._obj.fetchmany(size)]
def dictfetchall(self):
- return map(self.__build_dict, self._obj.fetchall())
+ return [self.__build_dict(row) for row in self._obj.fetchall()]
def __del__(self):
if not self._closed and not self._cnx.closed:
@@ -260,7 +260,7 @@ class Cursor(object):
sqllogs = {'from': self.sql_from_log, 'into': self.sql_into_log}
sum = 0
if sqllogs[type]:
- sqllogitems = sqllogs[type].items()
+ sqllogitems = pycompat.items(sqllogs[type])
_logger.debug("SQL LOG %s:", type)
for r in sorted(sqllogitems, key=lambda k: k[1]):
delay = timedelta(microseconds=r[1][1])
@@ -473,7 +473,7 @@ class LazyCursor(object):
if cr is None:
from odoo import registry
cr = self._cursor = registry(self.dbname).cursor()
- for _ in pycompat.range(self._depth):
+ for _ in range(self._depth):
cr.__enter__()
return getattr(cr, name)
diff --git a/odoo/tests/common.py b/odoo/tests/common.py
index 78cfbfbdbf9..a491c23ef76 100644
--- a/odoo/tests/common.py
+++ b/odoo/tests/common.py
@@ -135,7 +135,7 @@ class BaseCase(unittest.TestCase):
def shortDescription(self):
doc = self._testMethodDoc
- return doc and ' '.join(filter(None, map(str.strip, doc.splitlines()))) or None
+ return doc and ' '.join(l.strip() for l in doc.splitlines() if not l.isspace()) or None
class TransactionCase(BaseCase):
diff --git a/odoo/tools/amount_to_text_en.py b/odoo/tools/amount_to_text_en.py
index 6a8e7020b4a..b1854b1c4d6 100644
--- a/odoo/tools/amount_to_text_en.py
+++ b/odoo/tools/amount_to_text_en.py
@@ -71,7 +71,7 @@ def amount_to_text(number, currency):
cents_number = int(list[1])
cents_name = (cents_number > 1) and 'Cents' or 'Cent'
- return ' '.join(filter(None, [start_word, units_name, (start_word or units_name) and (end_word or cents_name) and 'and', end_word, cents_name]))
+ return ' '.join(w for w in [start_word, units_name, (start_word or units_name) and (end_word or cents_name) and 'and', end_word, cents_name] if w)
#-------------------------------------------------------------
diff --git a/odoo/tools/appdirs.py b/odoo/tools/appdirs.py
index f98fa1f8ae3..8617dabfa4f 100644
--- a/odoo/tools/appdirs.py
+++ b/odoo/tools/appdirs.py
@@ -15,7 +15,7 @@ from __future__ import print_function
# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
__version_info__ = (1, 3, 0)
-__version__ = '.'.join(map(str, __version_info__))
+__version__ = '.'.join(str(v) for v in __version_info__)
import sys
diff --git a/odoo/tools/cache.py b/odoo/tools/cache.py
index 48dc42ba04c..acc01895a48 100644
--- a/odoo/tools/cache.py
+++ b/odoo/tools/cache.py
@@ -8,6 +8,8 @@ from decorator import decorator
from inspect import formatargspec, getargspec
import logging
+from odoo.tools import pycompat
+
unsafe_eval = eval
_logger = logging.getLogger(__name__)
@@ -112,7 +114,7 @@ class ormcache_context(ormcache):
spec = getargspec(self.method)
args = formatargspec(*spec)[1:-1]
cont_expr = "(context or {})" if 'context' in spec.args else "self._context"
- keys_expr = "tuple(map(%s.get, %r))" % (cont_expr, self.keys)
+ keys_expr = "tuple(%s.get(k) for k in %r)" % (cont_expr, self.keys)
if self.args:
code = "lambda %s: (%s, %s)" % (args, ", ".join(self.args), keys_expr)
else:
@@ -200,10 +202,10 @@ def log_ormcache_stats(sig=None, frame=None):
me = threading.currentThread()
me_dbname = me.dbname
entries = defaultdict(int)
- for dbname, reg in Registry.registries.iteritems():
- for key in reg.cache.iterkeys():
+ for dbname, reg in pycompat.items(Registry.registries):
+ for key in reg.cache:
entries[(dbname,) + key[:2]] += 1
- for key, count in sorted(entries.items()):
+ for key, count in sorted(pycompat.items(entries)):
dbname, model_name, method = key
me.dbname = dbname
stat = STAT[key]
diff --git a/odoo/tools/config.py b/odoo/tools/config.py
index 3a227fb5142..0f51fbcb269 100644
--- a/odoo/tools/config.py
+++ b/odoo/tools/config.py
@@ -13,7 +13,8 @@ import odoo.conf
import odoo.loglevels as loglevels
import logging
import odoo.release as release
-from . import appdirs
+from . import appdirs, pycompat
+
class MyOption (optparse.Option, object):
""" optparse Option with two additional attributes.
@@ -55,7 +56,7 @@ def _deduplicate_loggers(loggers):
# there are no duplicates within the output sequence
return (
'{}:{}'.format(logger, level)
- for logger, level in dict(it.split(':') for it in loggers).iteritems()
+ for logger, level in pycompat.items(dict(it.split(':') for it in loggers))
)
@@ -453,10 +454,10 @@ class configmanager(object):
self.options['demo'] = (dict(self.options['init'])
if not self.options['without_demo'] else {})
self.options['update'] = opt.update and dict.fromkeys(opt.update.split(','), 1) or {}
- self.options['translate_modules'] = opt.translate_modules and map(lambda m: m.strip(), opt.translate_modules.split(',')) or ['all']
+ self.options['translate_modules'] = opt.translate_modules and [m.strip() for m in opt.translate_modules.split(',')] or ['all']
self.options['translate_modules'].sort()
- dev_split = opt.dev_mode and map(str.strip, opt.dev_mode.split(',')) or []
+ dev_split = opt.dev_mode and [s.strip() for s in opt.dev_mode.split(',')] or []
self.options['dev_mode'] = 'all' in dev_split and dev_split + ['pdb', 'reload', 'qweb', 'werkzeug', 'xml'] or dev_split
if opt.pg_path:
@@ -527,9 +528,9 @@ class configmanager(object):
def save(self):
p = ConfigParser.ConfigParser()
- loglevelnames = dict(zip(self._LOGLEVELS.values(), self._LOGLEVELS.keys()))
+ loglevelnames = dict(pycompat.izip(pycompat.values(self._LOGLEVELS), pycompat.keys(self._LOGLEVELS)))
p.add_section('options')
- for opt in sorted(self.options.keys()):
+ for opt in sorted(pycompat.keys(self.options)):
if opt in ('version', 'language', 'translate_out', 'translate_in', 'overwrite_existing_translations', 'init', 'update'):
continue
if opt in self.blacklist_for_save:
@@ -541,9 +542,9 @@ class configmanager(object):
else:
p.set('options', opt, self.options[opt])
- for sec in sorted(self.misc.keys()):
+ for sec in sorted(pycompat.keys(self.misc)):
p.add_section(sec)
- for opt in sorted(self.misc[sec].keys()):
+ for opt in sorted(pycompat.keys(self.misc[sec])):
p.set(sec,opt,self.misc[sec][opt])
# try to create the directories and write the file
diff --git a/odoo/tools/convert.py b/odoo/tools/convert.py
index 815ee9a64f1..7e8e9f6e3ce 100644
--- a/odoo/tools/convert.py
+++ b/odoo/tools/convert.py
@@ -98,7 +98,7 @@ def _eval_xml(self, node, env):
q = safe_eval(f_search, idref2)
ids = env[f_model].search(q).ids
if f_use != 'id':
- ids = map(lambda x: x[f_use], env[f_model].browse(ids).read([f_use]))
+ ids = [x[f_use] for x in env[f_model].browse(ids).read([f_use])]
_fields = env[f_model]._fields
if (f_name in _fields) and _fields[f_name].type == 'many2many':
return ids
@@ -659,7 +659,7 @@ form: module.record_id""" % (xml_id,)
_fields = self.env[rec_model]._fields
# if the current field is many2many
if (f_name in _fields) and _fields[f_name].type == 'many2many':
- f_val = [(6, 0, map(lambda x: x[f_use], s))]
+ f_val = [(6, 0, [x[f_use] for x in s])]
elif len(s):
# otherwise (we are probably in a many2one field),
# take the first element of the search
@@ -707,7 +707,7 @@ form: module.record_id""" % (xml_id,)
'model': 'ir.ui.view',
}
for att in ['forcecreate', 'context']:
- if att in el.keys():
+ if att in el.attrib:
record_attrs[att] = el.attrib.pop(att)
Field = builder.E.field
@@ -733,7 +733,7 @@ form: module.record_id""" % (xml_id,)
record.append(Field(name='customize_show', eval=el.get('customize_show')))
groups = el.attrib.pop('groups', None)
if groups:
- grp_lst = map(lambda x: "ref('%s')" % x, groups.split(','))
+ grp_lst = [("ref('%s')" % x) for x in groups.split(',')]
record.append(Field(name="groups_id", eval="[(6, 0, ["+', '.join(grp_lst)+"])]"))
if el.attrib.pop('page', None) == 'True':
record.append(Field(name="page", eval="True"))
@@ -854,7 +854,7 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
if not (line and any(line)):
continue
try:
- datas.append(map(ustr, line))
+ datas.append([ustr(v) for v in line])
except Exception:
_logger.error("Cannot import the line: %s", line)
diff --git a/odoo/tools/float_utils.py b/odoo/tools/float_utils.py
index 8c71d7a29b2..fa150a1d902 100644
--- a/odoo/tools/float_utils.py
+++ b/odoo/tools/float_utils.py
@@ -217,10 +217,10 @@ if __name__ == "__main__":
fractions = [.0, .015, .01499, .675, .67499, .4555, .4555, .45555]
expecteds = ['.00', '.02', '.01', '.68', '.67', '.46', '.456', '.4556']
precisions = [2, 2, 2, 2, 2, 2, 3, 4]
- for magnitude in pycompat.range(7):
- for frac, exp, prec in zip(fractions, expecteds, precisions):
+ for magnitude in range(7):
+ for frac, exp, prec in pycompat.izip(fractions, expecteds, precisions):
for sign in [-1,1]:
- for x in pycompat.range(0, 10000, 97):
+ for x in range(0, 10000, 97):
n = x * 10**magnitude
f = sign * (n + frac)
f_exp = ('-' if f != 0 and sign == -1 else '') + str(n) + exp
diff --git a/odoo/tools/func.py b/odoo/tools/func.py
index 8f49864eda2..2e204206f04 100644
--- a/odoo/tools/func.py
+++ b/odoo/tools/func.py
@@ -33,7 +33,7 @@ class lazy_property(object):
""" Reset all lazy properties on the instance `obj`. """
cls = type(obj)
obj_dict = vars(obj)
- for name in obj_dict.keys():
+ for name in list(obj_dict):
if isinstance(getattr(cls, name, None), lazy_property):
obj_dict.pop(name)
diff --git a/odoo/tools/graph.py b/odoo/tools/graph.py
index 82fc0e0978a..5e3edabf05a 100755
--- a/odoo/tools/graph.py
+++ b/odoo/tools/graph.py
@@ -5,6 +5,9 @@
import operator
import math
+from odoo.tools import pycompat
+
+
class graph(object):
def __init__(self, nodes, transitions, no_ancester=None):
"""Initialize graph's object
@@ -256,7 +259,7 @@ class graph(object):
"""The ranks are normalized by setting the least rank to zero.
"""
- least_rank = min(map(lambda x: x['x'], self.result.values()))
+ least_rank = min(x['x'] for x in pycompat.values(self.result.values))
if least_rank!=0:
for node in self.result:
@@ -380,7 +383,7 @@ class graph(object):
"""Finds actual-order of the nodes with respect to maximum number of nodes in a rank in component
"""
mid_pos = 0.0
- max_level = max(map(lambda x: len(x), self.levels.values()))
+ max_level = max(len(x) for x in pycompat.values(self.levels.values))
for level in self.levels:
if level:
@@ -471,7 +474,7 @@ class graph(object):
"""
if self.Is_Cyclic:
- max_level = max(map(lambda x: len(x), self.levels.values()))
+ max_level = max(len(x) for x in pycompat.values(self.levels.values))
if max_level%2:
self.result[self.start]['y'] = (max_level+1)/2 + self.max_order + (self.max_order and 1)
@@ -483,7 +486,7 @@ class graph(object):
else:
self.result[self.start]['y'] = 0
self.tree_order(self.start, 0)
- min_order = math.fabs(min(map(lambda x: x['y'], self.result.values())))
+ min_order = math.fabs(min(x['y'] for x in pycompat.values(self.result.values)))
index = self.start_nodes.index(self.start)
same = False
@@ -537,7 +540,7 @@ class graph(object):
self.result[start]['y'] = base + factor
factor += 1
- self.max_order = max(map(lambda x: x['y'], self.result.values()))
+ self.max_order = max(x['y'] for x in pycompat.values(self.result.values))
def find_starts(self):
"""Finds other start nodes of the graph in the case when graph is disconneted
@@ -626,7 +629,7 @@ class graph(object):
self.make_chain()
self.preprocess_order()
self.order = {}
- max_rank = max(map(lambda x: x, self.levels.keys()))
+ max_rank = max(x for x in self.levels)
for i in range(max_rank+1):
self.order[i] = 0
@@ -657,7 +660,7 @@ class graph(object):
for node in self.no_ancester:
for sec_node in self.transitions.get(node, []):
- if sec_node in self.partial_order.keys():
+ if sec_node in pycompat.keys(self.partial_order):
self.transitions[self.start_nodes[0]].append(node)
break
@@ -743,7 +746,7 @@ if __name__=='__main__':
for node in nodes:
node_res[node] = result[node]
- for name,node in node_res.items():
+ for name,node in pycompat.items(node_res):
draw.arc( (int(node['y']-radius), int(node['x']-radius),int(node['y']+radius), int(node['x']+radius) ), 0, 360, (128,128,128))
draw.text( (int(node['y']), int(node['x'])), str(name), (128,128,128))
diff --git a/odoo/tools/lru.py b/odoo/tools/lru.py
index 7c485a039d3..a9e4a8b8f18 100644
--- a/odoo/tools/lru.py
+++ b/odoo/tools/lru.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
# taken from http://code.activestate.com/recipes/252524-length-limited-o1-lru-cache-implementation/
import threading
+
+from odoo.tools import pycompat
from .func import synchronized
__all__ = ['LRU']
@@ -92,6 +94,7 @@ class LRU(object):
def __len__(self):
return len(self.d)
+ # FIXME: should this have a P2 and a P3 version or something?
@synchronized()
def iteritems(self):
cur = self.first
@@ -106,12 +109,12 @@ class LRU(object):
@synchronized()
def itervalues(self):
- for i,j in self.iteritems():
+ for i,j in pycompat.items(self):
yield j
@synchronized()
def keys(self):
- return self.d.keys()
+ return list(pycompat.keys(self.d))
@synchronized()
def pop(self,key):
diff --git a/odoo/tools/mail.py b/odoo/tools/mail.py
index 490006082d5..9689512df00 100644
--- a/odoo/tools/mail.py
+++ b/odoo/tools/mail.py
@@ -16,6 +16,7 @@ from lxml import etree
import odoo
from odoo.loglevels import ustr
+from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -84,7 +85,7 @@ class _Cleaner(clean.Cleaner):
new_node.text = text
new_node.tail = tail
if attrs:
- for key, val in attrs.iteritems():
+ for key, val in pycompat.items(attrs):
new_node.set(key, val)
return new_node
@@ -153,7 +154,7 @@ class _Cleaner(clean.Cleaner):
if style[0].lower() in self._style_whitelist:
valid_styles[style[0].lower()] = style[1]
if valid_styles:
- el.attrib['style'] = '; '.join('%s: %s' % (key, val) for (key, val) in valid_styles.iteritems())
+ el.attrib['style'] = '; '.join('%s: %s' % (key, val) for (key, val) in pycompat.items(valid_styles))
else:
del el.attrib['style']
@@ -530,4 +531,4 @@ def decode_smtp_header(smtp_header):
# was mail_thread.decode_header()
def decode_message_header(message, header, separator=' '):
- return separator.join(map(decode_smtp_header, filter(None, message.get_all(header, []))))
+ return separator.join(decode_smtp_header(h) for h in message.get_all(header, []) if h)
diff --git a/odoo/tools/mimetypes.py b/odoo/tools/mimetypes.py
index 370c4b2e85b..3abdc22963d 100644
--- a/odoo/tools/mimetypes.py
+++ b/odoo/tools/mimetypes.py
@@ -10,6 +10,8 @@ import logging
import re
import zipfile
+from odoo.tools import pycompat
+
__all__ = ['guess_mimetype']
_logger = logging.getLogger(__name__)
@@ -33,7 +35,7 @@ def _check_ooxml(data):
# then there is a directory whose name denotes the type of the file:
# word, pt (powerpoint) or xl (excel)
- for dirname, mime in _ooxml_dirs.iteritems():
+ for dirname, mime in pycompat.items(_ooxml_dirs):
if any(entry.startswith(dirname) for entry in filenames):
return mime
diff --git a/odoo/tools/misc.py b/odoo/tools/misc.py
index 492be9951e4..81c27105ff1 100644
--- a/odoo/tools/misc.py
+++ b/odoo/tools/misc.py
@@ -22,7 +22,7 @@ import time
import werkzeug.utils
import zipfile
from collections import defaultdict, Iterable, Mapping, MutableSet, OrderedDict
-from itertools import islice, izip, groupby, repeat
+from itertools import islice, groupby, repeat
from lxml import etree
from .which import which
@@ -282,7 +282,7 @@ def flatten(list):
r = []
for e in list:
if isiterable(e):
- map(r.append, flatten(e))
+ r.extend(flatten(e))
else:
r.append(e)
return r
@@ -304,7 +304,7 @@ def reverse_enumerate(l):
File "", line 1, in
StopIteration
"""
- return izip(pycompat.range(len(l)-1, -1, -1), reversed(l))
+ return pycompat.izip(range(len(l)-1, -1, -1), reversed(l))
def partition(pred, elems):
""" Return a pair equivalent to:
@@ -337,10 +337,12 @@ def topological_sort(elems):
visited.add(n)
if n in elems:
# first visit all dependencies of n, then append n to result
- map(visit, elems[n])
+ for it in elems[n]:
+ visit(it)
result.append(n)
- map(visit, elems)
+ for el in elems:
+ visit(el)
return result
@@ -458,7 +460,7 @@ def logged(f):
vector = ['Call -> function: %r' % f]
for i, arg in enumerate(args):
vector.append(' arg %02d: %s' % (i, pformat(arg)))
- for key, value in kwargs.items():
+ for key, value in pycompat.items(kwargs):
vector.append(' kwarg %10s: %s' % (key, pformat(value)))
timeb4 = time.time()
@@ -523,9 +525,9 @@ def detect_ip_addr():
# try 32 bit kernel:
if ip_addr is None:
- ifaces = filter(None, [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)])
+ ifaces = [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)]
- for ifname in [iface for iface in ifaces if iface != 'lo']:
+ for ifname in [iface for iface in ifaces if iface if iface != 'lo']:
ip_addr = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, pack('256s', ifname[:15]))[20:24])
break
@@ -909,7 +911,7 @@ def dumpstacks(sig=None, frame=None):
'uid': getattr(th, 'uid', 'n/a'),
'dbname': getattr(th, 'dbname', 'n/a')}
for th in threading.enumerate()}
- for threadId, stack in sys._current_frames().items():
+ for threadId, stack in pycompat.items(sys._current_frames()):
thread_info = threads_info.get(threadId, {})
code.append("\n# Thread: %s (id:%s) (db:%s) (uid:%s)" %
(thread_info.get('name', 'n/a'),
@@ -939,7 +941,7 @@ def freehash(arg):
if isinstance(arg, Mapping):
return hash(frozendict(arg))
elif isinstance(arg, Iterable):
- return hash(frozenset(map(freehash, arg)))
+ return hash(frozenset(freehash(item) for item in arg))
else:
return id(arg)
@@ -960,7 +962,7 @@ class frozendict(dict):
def update(self, *args, **kwargs):
raise NotImplementedError("'update' not supported on frozendict")
def __hash__(self):
- return hash(frozenset((key, freehash(val)) for key, val in self.iteritems()))
+ return hash(frozenset((key, freehash(val)) for key, val in pycompat.items(self)))
class Collector(Mapping):
""" A mapping from keys to lists. This is essentially a space optimization
@@ -1122,7 +1124,7 @@ def format_date(env, value, lang_code=False, date_format=False):
def _consteq(str1, str2):
""" Constant-time string comparison. Suitable to compare bytestrings of fixed,
known length only, because length difference is optimized. """
- return len(str1) == len(str2) and sum(ord(x)^ord(y) for x, y in zip(str1, str2)) == 0
+ return len(str1) == len(str2) and sum(ord(x)^ord(y) for x, y in pycompat.izip(str1, str2)) == 0
consteq = getattr(passlib.utils, 'consteq', _consteq)
diff --git a/odoo/tools/osutil.py b/odoo/tools/osutil.py
index af12c6558b2..2bb180a1d17 100644
--- a/odoo/tools/osutil.py
+++ b/odoo/tools/osutil.py
@@ -39,7 +39,7 @@ def walksymlinks(top, topdown=True, onerror=None):
if topdown:
yield dirpath, dirnames, filenames
- symlinks = filter(lambda dirname: os.path.islink(os.path.join(dirpath, dirname)), dirnames)
+ symlinks = (dirname for dirname in dirnames if os.path.islink(os.path.join(dirpath, dirname)))
for s in symlinks:
for x in walksymlinks(os.path.join(dirpath, s), topdown, onerror):
yield x
diff --git a/odoo/tools/parse_version.py b/odoo/tools/parse_version.py
index 348fb3664a6..e9ff071c0b6 100644
--- a/odoo/tools/parse_version.py
+++ b/odoo/tools/parse_version.py
@@ -7,6 +7,8 @@
from __future__ import print_function
import re
+from odoo.tools import pycompat
+
component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE)
replace = {'pre':'c', 'preview':'c','-':'final-','_':'final-','rc':'c','dev':'@','saas':'','~':''}.get
@@ -72,7 +74,7 @@ if __name__ == '__main__':
if verbose:
print(v, pv)
- for a, b in zip(pvs, pvs[1:]):
+ for a, b in pycompat.izip(pvs, pvs[1:]):
assert a < b, '%s < %s == %s' % (a, b, a < b)
chk(('0', '4.2', '4.2.3.4', '5.0.0-alpha', '5.0.0-rc1', '5.0.0-rc1.1', '5.0.0_rc2', '5.0.0_rc3', '5.0.0'), False)
diff --git a/odoo/tools/pycompat.py b/odoo/tools/pycompat.py
index 50b6f492a59..3fbb847a9fa 100644
--- a/odoo/tools/pycompat.py
+++ b/odoo/tools/pycompat.py
@@ -6,10 +6,12 @@ import sys
PY2 = sys.version_info[0] == 2
if PY2:
- # pylint: disable=long-builtin,xrange-builtin
+ # pylint: disable=long-builtin,dict-iter-method
integer_types = (int, long)
- range = xrange
+ keys = lambda d: iter(d.iterkeys())
+ values = lambda d: iter(d.itervalues())
+ items = lambda d: iter(d.iteritems())
# noinspection PyUnresolvedReferences
from itertools import imap, izip, ifilter
@@ -19,9 +21,12 @@ if PY2:
del cls.__next__
return cls
else:
+ # pylint: disable=bad-functions
integer_types = (int,)
- range = range
+ keys = lambda d: iter(d.keys())
+ values = lambda d: iter(d.values())
+ items = lambda d: iter(d.items())
imap = map
izip = zip
diff --git a/odoo/tools/safe_eval.py b/odoo/tools/safe_eval.py
index ebe97619e35..1897486eb82 100644
--- a/odoo/tools/safe_eval.py
+++ b/odoo/tools/safe_eval.py
@@ -23,7 +23,6 @@ from types import CodeType
import logging
import werkzeug
-from odoo.tools import pycompat
from .misc import ustr
import odoo
@@ -251,7 +250,7 @@ _BUILTINS = {
'divmod': divmod,
'isinstance': isinstance,
'range': range,
- 'xrange': pycompat.range,
+ 'xrange': range,
'zip': zip,
'Exception': Exception,
}
diff --git a/odoo/tools/test_reports.py b/odoo/tools/test_reports.py
index 17065848129..cad44431c1d 100644
--- a/odoo/tools/test_reports.py
+++ b/odoo/tools/test_reports.py
@@ -174,7 +174,7 @@ def try_report_action(cr, uid, action_id, active_model=None, active_ids=None,
view_data.update(wiz_data)
_logger.debug("View data is: %r", view_data)
- for fk, field in view_res.get('fields',{}).items():
+ for fk, field in pycompat.items(view_res.get('fields',{})):
# Default fields returns list of int, while at create()
# we need to send a [(6,0,[int,..])]
if field['type'] in ('one2many', 'many2many') \
diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py
index dcdb95d0955..b2996af6ae3 100644
--- a/odoo/tools/translate.py
+++ b/odoo/tools/translate.py
@@ -255,7 +255,7 @@ def translate_xml_node(node, callback, method, parser=None):
append_content(result, translate_content(todo) if todo_has_text else todo)
# translate the required attributes
- for name, value in result.items():
+ for name, value in pycompat.items(result.attrib):
if name in TRANSLATED_ATTRS:
result.set(name, translate_text(value) or value)
@@ -660,7 +660,7 @@ def trans_export(lang, modules, buffer, format, cr):
row.setdefault('tnrs', []).append((type, name, res_id))
row.setdefault('comments', set()).update(comments)
- for src, row in sorted(grouped_rows.items()):
+ for src, row in sorted(pycompat.items(grouped_rows)):
if not lang:
# translation template, so no translation value
row['translation'] = ''
@@ -674,7 +674,7 @@ def trans_export(lang, modules, buffer, format, cr):
module = row[0]
rows_by_module.setdefault(module, []).append(row)
tmpdir = tempfile.mkdtemp()
- for mod, modrows in rows_by_module.items():
+ for mod, modrows in pycompat.items(rows_by_module):
tmpmoddir = join(tmpdir, mod, 'i18n')
os.makedirs(tmpmoddir)
pofilename = (lang if lang else mod) + ".po" + ('t' if not lang else '')
@@ -1073,7 +1073,7 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
dic = dict.fromkeys(('type', 'name', 'res_id', 'src', 'value',
'comments', 'imd_model', 'imd_name', 'module'))
dic['lang'] = lang
- dic.update(zip(fields, row))
+ dic.update(pycompat.izip(fields, row))
# discard the target from the POT targets.
src = dic['src']
@@ -1111,7 +1111,7 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
# Then process the entries implied by the POT file (which is more
# correct w.r.t. the targets) if some of them remain.
pot_rows = []
- for src, target in pot_targets.iteritems():
+ for src, target in pycompat.items(pot_targets):
if target.value:
for type, name, res_id in target.targets:
pot_rows.append((type, name, res_id, src, target.value, target.comments))
diff --git a/odoo/tools/yaml_import.py b/odoo/tools/yaml_import.py
index 36c6c248249..4cb2e7ea184 100644
--- a/odoo/tools/yaml_import.py
+++ b/odoo/tools/yaml_import.py
@@ -13,7 +13,7 @@ import yaml
import odoo
from . import assertion_report
-from . import yaml_tag
+from . import yaml_tag, pycompat
from .config import config
from .misc import file_open, DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
from odoo import SUPERUSER_ID
@@ -36,8 +36,8 @@ class YamlImportAbortion(Exception):
def _is_yaml_mapping(node, tag_constructor):
value = isinstance(node, dict) \
- and len(node.keys()) == 1 \
- and isinstance(node.keys()[0], tag_constructor)
+ and len(node) == 1 \
+ and isinstance(next(iter(node)), tag_constructor)
return value
def is_comment(node):
@@ -202,7 +202,7 @@ class YamlInterpreter(object):
def process_assert(self, node):
if isinstance(node, dict):
- assertion, expressions = node.items()[0]
+ assertion, expressions = next(pycompat.items(node))
else:
assertion, expressions = node, []
@@ -275,7 +275,7 @@ class YamlInterpreter(object):
return record_dict
def process_record(self, node):
- record, fields = node.items()[0]
+ record, fields = next(pycompat.items(node))
model = self.env[record.model]
view_id = record.view
if view_id and (view_id is not True) and isinstance(view_id, basestring):
@@ -377,7 +377,7 @@ class YamlInterpreter(object):
def process_vals(fg, vals):
""" sanitize the given field values """
result = {}
- for field_name, field_value in vals.iteritems():
+ for field_name, field_value in pycompat.items(vals):
if field_name not in fg:
continue
if fg[field_name]['type'] == 'many2one' and isinstance(field_value, (tuple, list)):
@@ -391,14 +391,14 @@ class YamlInterpreter(object):
elif isinstance(command, dict):
return process_vals(sub_fg, command)
return command
- field_value = map(process, field_value or [])
+ field_value = [process(v) for v in (field_value or [])]
result[field_name] = field_value
return result
def post_process(fg, elems, vals):
""" filter out readonly fields from vals """
result = {}
- for field_name, field_value in vals.iteritems():
+ for field_name, field_value in pycompat.items(vals):
if is_readonly(elems[field_name]):
continue
if fg[field_name]['type'] in ('one2many', 'many2many'):
@@ -412,7 +412,7 @@ class YamlInterpreter(object):
elif isinstance(command, dict):
return (0, 0, post_process(sub_fg, sub_elems, command))
return command
- field_value = map(process, field_value or [])
+ field_value = [process(v) for v in (field_value or [])]
result[field_name] = field_value
return result
@@ -444,7 +444,7 @@ class YamlInterpreter(object):
record_dict.update(process_vals(fg, result.get('value', {})))
# fill in fields, and execute onchange where necessary
- for field_name, field_elem in elems.iteritems():
+ for field_name, field_elem in pycompat.items(elems):
assert field_name in fg, "The field '%s' is defined in the form view but not on the object '%s'!" % (field_name, model._name)
if is_readonly(field_elem):
# skip readonly fields
@@ -474,7 +474,7 @@ class YamlInterpreter(object):
result = recs.onchange(dict(record_dict, **parent_values), field_name, onchange_spec)
record_dict.update(process_vals(fg, {
key: val
- for key, val in result.get('value', {}).iteritems()
+ for key, val in pycompat.items(result.get('value', {}))
if key not in fields # do not shadow values explicitly set in yaml
}))
@@ -483,7 +483,7 @@ class YamlInterpreter(object):
else:
record_dict = {}
- for field_name, expression in fields.iteritems():
+ for field_name, expression in pycompat.items(fields):
if record_dict.get(field_name):
continue
field_value = self._eval_field(model, field_name, expression, parent=record_dict, default=False, context=context)
@@ -494,7 +494,7 @@ class YamlInterpreter(object):
# should not be sent to create. This bug appears with not stored function fields in the new API.
return {
key: val
- for key, val in record_dict.iteritems()
+ for key, val in pycompat.items(record_dict)
for field in [model._fields[key].base_field]
if field.store or field.inverse
}
@@ -580,7 +580,7 @@ class YamlInterpreter(object):
self.sudo_env = self.env(user=SUPERUSER_ID)
def process_python(self, node):
- python, statements = node.items()[0]
+ python, statements = next(pycompat.items(node))
assert python.model or python.id, "!python node must have attribute `model` or `id`"
if python.id is None:
record = self.env[python.model]
@@ -640,7 +640,7 @@ class YamlInterpreter(object):
return args
def process_function(self, node):
- function, params = node.items()[0]
+ function, params = next(pycompat.items(node))
if self.isnoupdate(function) and self.mode != 'init':
return
model = self.env[function.model]
@@ -770,9 +770,9 @@ class YamlInterpreter(object):
def process_ir_set(self, node):
if not self.mode == 'init':
return False
- _, fields = node.items()[0]
+ _, fields = next(pycompat.items(node))
res = {}
- for fieldname, expression in fields.items():
+ for fieldname, expression in pycompat.items(fields):
if is_eval(expression):
value = safe_eval(expression.expression, self.eval_context)
else:
@@ -878,7 +878,7 @@ class YamlInterpreter(object):
elif not is_preceded_by_comment:
if isinstance(node, dict):
msg = "Creating %s\n with %s"
- args = node.items()[0]
+ args = next(pycompat.items(node))
self._log(msg, *args)
else:
self._log(node)
diff --git a/odoo/tools/yaml_tag.py b/odoo/tools/yaml_tag.py
index f921c598f30..2b7141b6693 100644
--- a/odoo/tools/yaml_tag.py
+++ b/odoo/tools/yaml_tag.py
@@ -1,6 +1,9 @@
import yaml
import logging
+from . import pycompat
+
+
class YamlTag(object):
"""
Superclass for constructors of custom tags defined in yaml file.
@@ -13,7 +16,7 @@ class YamlTag(object):
def __getattr__(self, attr):
return None
def __repr__(self):
- return "<%s %s>" % (self.__class__.__name__, sorted(self.__dict__.items()))
+ return "<%s %s>" % (self.__class__.__name__, sorted(pycompat.items(self.__dict__)))
class Assert(YamlTag):
def __init__(self, model, id=None, severity=logging.WARNING, string="NONAME", **kwargs):
diff --git a/setup.py b/setup.py
index 2fce5faa39d..465f40b1f0a 100644
--- a/setup.py
+++ b/setup.py
@@ -24,9 +24,9 @@ def py2exe_datafiles():
import babel
data_files['babel/localedata'] = glob(join(dirname(babel.__file__), 'localedata', '*'))
others = ['global.dat', 'numbers.py', 'support.py', 'plural.py']
- data_files['babel'] = map(lambda f: join(dirname(babel.__file__), f), others)
+ data_files['babel'] = [join(dirname(babel.__file__), f) for f in others]
others = ['frontend.py', 'mofile.py']
- data_files['babel/messages'] = map(lambda f: join(dirname(babel.__file__), 'messages', f), others)
+ data_files['babel/messages'] = [join(dirname(babel.__file__), 'messages', f) for f in others]
import pytz
tzdir = dirname(pytz.__file__)
@@ -51,7 +51,7 @@ def py2exe_datafiles():
for f in filenames
if not f.endswith(('.py', '.pyc', '.pyo'))]
- return data_files.items()
+ return list(data_files.items())
def py2exe_options():
@@ -128,7 +128,7 @@ setup(
url=url,
author=author,
author_email=author_email,
- classifiers=filter(None, classifiers.split('\n')),
+ classifiers=[c for c in classifiers.split('\n') if c],
license=license,
scripts=['setup/odoo'],
packages=find_packages(),
diff --git a/setup/setup_dev.py b/setup/setup_dev.py
index 0038ad3fd8a..1ad6fe2f5ff 100755
--- a/setup/setup_dev.py
+++ b/setup/setup_dev.py
@@ -153,7 +153,7 @@ def main():
elif len(sys.argv) == 2 and sys.argv[1] in cmds:
cmds[sys.argv[1]]()
else:
- sys.exit('Unknow command. Command available: %r' % (cmds.keys(),))
+ sys.exit('Unknow command. Command available: %r' % (list(cmds,))
if __name__ == "__main__":
main()