diff options
Diffstat (limited to 'addons')
159 files changed, 595 insertions, 486 deletions
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 = '<span>%s</span>' % message_description - for name, values in tracked_values.iteritems(): + for name, values in pycompat.items(tracked_values): message += '<div> • <b>%s</b>: ' % name message += '%s</div>' % 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="<provider>"' attribute, then it required if record.provider is <provider>. """ for acquirer in self: - if any(getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k] for k, f in self._fields.items()): + if any(getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k] for k, f in pycompat.items(self._fields)): return False return True @@ -686,7 +686,7 @@ class PaymentToken(models.Model): if hasattr(self, custom_method_name): values.update(getattr(self, custom_method_name)(values)) # remove all non-model fields used by (provider)_create method to avoid warning - fields_wl = set(self._fields.keys()) & set(values.keys()) + fields_wl = set(self._fields) & set(values) values = {field: values[field] for field in fields_wl} return super(PaymentToken, self).create(values) 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/<string:xmlid>', @@ -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/<model("event.event"):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/<model("event.event"):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: <id>, name: <product code> <template_name> <attributes>) - 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/<model("product.template"):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'))]) |
