From fffaf735f58f1ac57781c095eb552de6d1fcdc47 Mon Sep 17 00:00:00 2001 From: xmo-odoo <xmo@odoo.com> Date: Wed, 10 May 2017 09:39:55 +0200 Subject: [PATCH] [FIX] P3: list -> iterable builtins (#16811) In Python 3: * various builtins and dict methods were changed to return view/iterable objects rather than lists * and the separate Python 2 view/iterable builtins and methods were removed altogether This is problematic when using these items as list (which the happens repeatedly in Odoo), but more viciously when iterating *multiple times* over them (which also happens, which I've messed up multiple times while writing this, and which is a pain to debug even when you've just created the issue). Convert all code using these to semantics-matching cross-version helper functions to get the LCD behaviour between P2 and P3, and forbid the builtins via lint. issue #8530 --- addons/account/models/account.py | 4 +- .../account/models/account_bank_statement.py | 4 +- addons/account/models/account_invoice.py | 24 +-- .../models/account_journal_dashboard.py | 2 +- addons/account/models/account_move.py | 2 +- addons/account/models/chart_template.py | 5 +- addons/account/models/partner.py | 8 +- addons/account/report/account_balance.py | 2 +- .../account/report/account_general_ledger.py | 2 +- addons/account/report/account_journal.py | 2 +- .../account/report/account_overdue_report.py | 2 +- .../report/account_report_financial.py | 19 +- addons/account/tests/test_reconciliation.py | 9 +- addons/account_asset/models/account_asset.py | 6 +- .../asset_depreciation_confirmation_wizard.py | 2 +- .../report/report_account_test.py | 4 +- .../account_voucher/models/account_voucher.py | 3 +- addons/anonymization/models/anonymization.py | 2 +- .../anonymization/wizard/anonymize_wizard.py | 2 +- addons/auth_signup/controllers/main.py | 5 +- addons/auth_signup/models/res_partner.py | 2 +- .../models/base_address_extended.py | 4 +- .../base_automation/models/base_automation.py | 3 +- .../wizard/base_gengo_translations.py | 3 +- addons/base_geolocalize/models/res_partner.py | 8 +- addons/base_import/models/base_import.py | 18 +- addons/base_import/models/odf_ods_reader.py | 4 +- addons/base_import/tests/test_base_import.py | 9 +- addons/base_import_module/models/ir_module.py | 4 +- .../base_import_module/models/ir_ui_view.py | 2 +- addons/base_vat/models/res_partner.py | 2 +- addons/calendar/models/calendar.py | 24 +-- addons/calendar/models/mail_message.py | 4 +- addons/crm/models/crm_lead.py | 5 +- addons/crm/wizard/base_partner_merge.py | 7 +- addons/event/models/event.py | 3 +- addons/event/models/event_mail.py | 8 +- addons/fleet/models/fleet_vehicle_cost.py | 4 +- addons/gamification/models/goal.py | 8 +- addons/google_calendar/models/calendar.py | 2 +- .../google_calendar/models/google_calendar.py | 26 ++- addons/hr/models/hr.py | 4 +- addons/hr_expense/models/hr_expense.py | 6 +- addons/hr_holidays/models/hr_holidays.py | 2 +- addons/hr_payroll/models/hr_payslip.py | 10 +- .../report/report_payslip_details.py | 9 +- .../hr_recruitment/models/hr_recruitment.py | 3 +- addons/hw_blackbox_be/controllers/main.py | 2 +- addons/hw_escpos/escpos/escpos.py | 4 +- .../im_livechat/models/im_livechat_channel.py | 3 +- addons/im_livechat/models/mail_channel.py | 3 +- .../report/report_hr_yearly_salary_detail.py | 3 +- addons/l10n_lu/scripts/tax2csv.py | 28 +-- addons/link_tracker/models/link_tracker.py | 4 +- addons/lunch/models/lunch.py | 10 +- addons/mail/models/html2text.py | 11 +- addons/mail/models/mail_channel.py | 6 +- addons/mail/models/mail_followers.py | 19 +- addons/mail/models/mail_mail.py | 3 +- addons/mail/models/mail_message.py | 26 +-- addons/mail/models/mail_template.py | 18 +- addons/mail/models/mail_thread.py | 40 ++--- addons/mail/models/res_partner.py | 3 +- addons/mail/tests/test_mail_followers.py | 19 +- addons/mail/wizard/mail_compose_message.py | 8 +- .../models/marketing_campaign.py | 5 +- addons/mass_mailing/models/mass_mailing.py | 10 +- addons/mrp/models/mrp_workcenter.py | 3 +- addons/mrp/models/stock_picking.py | 2 +- addons/mrp/wizard/mrp_product_produce.py | 2 +- .../wizard/mrp_repair_make_invoice.py | 3 +- addons/pad/models/pad.py | 8 +- addons/payment/models/payment_acquirer.py | 6 +- addons/payment_adyen/models/payment.py | 10 +- .../models/authorize_request.py | 2 +- .../payment_authorize/tests/test_authorize.py | 4 +- addons/payment_buckaroo/controllers/main.py | 3 +- addons/payment_buckaroo/models/payment.py | 8 +- addons/payment_ogone/models/payment.py | 2 +- addons/payment_paypal/controllers/main.py | 2 +- addons/point_of_sale/models/pos_order.py | 12 +- addons/point_of_sale/report/pos_invoice.py | 2 +- addons/product/models/product_pricelist.py | 22 ++- addons/product/report/product_pricelist.py | 2 +- .../product_expiry/models/production_lot.py | 9 +- .../product_margin/models/product_product.py | 8 +- addons/project/models/project.py | 8 +- addons/project_issue/models/project_issue.py | 8 +- addons/purchase/models/purchase.py | 2 +- addons/rating/models/rating.py | 3 +- addons/rating_project/models/project.py | 5 +- .../models/project_issue.py | 7 +- addons/resource/models/resource.py | 2 +- addons/resource/models/resource_mixin.py | 2 +- addons/sale/models/account_invoice.py | 3 +- addons/sale/models/res_partner.py | 4 +- addons/sale/models/sale.py | 10 +- addons/sale/models/sale_analytic.py | 4 +- addons/sale/tests/test_sale_order.py | 11 +- .../sale_expense/tests/test_sale_expense.py | 4 +- addons/sale_mrp/models/procurement.py | 3 +- addons/sale_mrp/sale_mrp.py | 2 +- addons/sale_stock/tests/test_sale_stock.py | 5 +- addons/stock/models/procurement.py | 5 +- addons/stock/models/stock_inventory.py | 4 +- addons/stock/models/stock_move.py | 14 +- addons/stock/models/stock_picking.py | 17 +- addons/stock/models/stock_quant.py | 4 +- addons/stock/models/stock_warehouse.py | 16 +- addons/stock/wizard/make_procurement.py | 3 +- addons/stock_account/models/stock.py | 4 +- .../models/stock_landed_cost.py | 7 +- addons/survey/models/survey.py | 15 +- addons/survey/tests/test_survey.py | 12 +- addons/web/controllers/main.py | 20 +-- addons/web/tests/test_serving_base.py | 2 +- addons/web_diagram/controllers/main.py | 12 +- addons/web_editor/models/ir_qweb.py | 4 +- addons/web_editor/models/ir_ui_view.py | 4 +- addons/web_tour/models/ir_ui_menu.py | 3 +- addons/website/controllers/main.py | 8 +- addons/website/models/ir_http.py | 4 +- addons/website/models/ir_qweb.py | 5 +- addons/website/models/website.py | 13 +- addons/website/tests/test_converter.py | 4 +- addons/website/tests/test_crawl.py | 2 +- addons/website/tests/test_views.py | 3 +- addons/website_blog/controllers/main.py | 4 +- addons/website_blog/models/website_blog.py | 7 +- .../controllers/main.py | 6 +- .../models/crm_lead.py | 5 +- .../wizard/crm_forward_to_partner.py | 3 +- addons/website_customer/controllers/main.py | 2 +- addons/website_event/controllers/main.py | 11 +- .../controllers/main.py | 4 +- addons/website_event_sale/controllers/main.py | 5 +- .../website_event_track/controllers/main.py | 6 +- addons/website_form/controllers/main.py | 6 +- addons/website_form/models/models.py | 9 +- addons/website_forum/controllers/main.py | 4 +- addons/website_forum/models/forum.py | 7 +- addons/website_gengo/controllers/main.py | 2 +- .../controllers/main.py | 2 +- addons/website_livechat/controllers/main.py | 3 +- addons/website_membership/controllers/main.py | 4 +- addons/website_portal/controllers/main.py | 5 +- .../controllers/website_portal.py | 3 +- addons/website_project/controllers/main.py | 3 +- .../website_project_issue/controllers/main.py | 3 +- addons/website_quote/controllers/main.py | 2 +- addons/website_sale/controllers/main.py | 35 ++-- addons/website_sale/models/product.py | 4 +- addons/website_sale/models/sale_order.py | 3 +- .../tests/test_website_sale_pricelist.py | 9 +- .../website_sale_digital/controllers/main.py | 7 +- .../models/account_invoice.py | 2 +- .../website_sale_options/controllers/main.py | 7 +- addons/website_slides/controllers/main.py | 4 +- addons/website_slides/models/slides.py | 8 +- doc/_extensions/odoo_ext/translator.py | 6 +- doc/python3.rst | 50 ++++-- odoo/addons/base/ir/ir_actions.py | 25 +-- odoo/addons/base/ir/ir_attachment.py | 10 +- odoo/addons/base/ir/ir_config_parameter.py | 4 +- odoo/addons/base/ir/ir_fields.py | 16 +- odoo/addons/base/ir/ir_http.py | 5 +- odoo/addons/base/ir/ir_mail_server.py | 17 +- odoo/addons/base/ir/ir_model.py | 30 ++-- odoo/addons/base/ir/ir_qweb/assetsbundle.py | 8 +- odoo/addons/base/ir/ir_qweb/ir_qweb.py | 8 +- odoo/addons/base/ir/ir_qweb/qweb.py | 55 +++--- odoo/addons/base/ir/ir_sequence.py | 3 +- odoo/addons/base/ir/ir_translation.py | 6 +- odoo/addons/base/ir/ir_ui_view.py | 38 ++-- odoo/addons/base/ir/ir_values.py | 6 +- odoo/addons/base/module/module.py | 16 +- .../report/ir_module_reference_print.py | 4 +- odoo/addons/base/res/ir_property.py | 8 +- odoo/addons/base/res/res_company.py | 3 +- odoo/addons/base/res/res_config.py | 14 +- odoo/addons/base/res/res_lang.py | 7 +- odoo/addons/base/res/res_partner.py | 2 +- odoo/addons/base/res/res_users.py | 34 ++-- odoo/addons/base/tests/test_api.py | 8 +- odoo/addons/base/tests/test_expression.py | 6 +- odoo/addons/base/tests/test_float.py | 6 +- odoo/addons/base/tests/test_ir_filters.py | 27 +-- odoo/addons/base/tests/test_orm.py | 8 +- odoo/addons/base/tests/test_qweb.py | 3 +- odoo/addons/base/tests/test_search.py | 5 +- odoo/addons/base/tests/test_views.py | 2 +- odoo/addons/test_impex/tests/test_export.py | 5 +- odoo/addons/test_limits/models.py | 3 +- odoo/addons/test_new_api/models.py | 5 +- .../test_new_api/tests/test_one2many.py | 2 +- odoo/addons/test_pylint/tests/test_pylint.py | 39 +++-- odoo/api.py | 15 +- odoo/cli/command.py | 2 +- odoo/fields.py | 20 +-- odoo/http.py | 22 ++- odoo/models.py | 163 +++++++++--------- odoo/modules/db.py | 2 +- odoo/modules/graph.py | 19 +- odoo/modules/loading.py | 11 +- odoo/modules/migration.py | 12 +- odoo/modules/module.py | 13 +- odoo/modules/registry.py | 19 +- odoo/osv/expression.py | 4 +- odoo/osv/orm.py | 5 +- odoo/release.py | 4 +- odoo/service/db.py | 2 +- odoo/service/model.py | 2 +- odoo/service/server.py | 16 +- odoo/sql_db.py | 10 +- odoo/tests/common.py | 2 +- odoo/tools/amount_to_text_en.py | 2 +- odoo/tools/appdirs.py | 2 +- odoo/tools/cache.py | 10 +- odoo/tools/config.py | 17 +- odoo/tools/convert.py | 10 +- odoo/tools/float_utils.py | 6 +- odoo/tools/func.py | 2 +- odoo/tools/graph.py | 19 +- odoo/tools/lru.py | 7 +- odoo/tools/mail.py | 7 +- odoo/tools/mimetypes.py | 4 +- odoo/tools/misc.py | 26 +-- odoo/tools/osutil.py | 2 +- odoo/tools/parse_version.py | 4 +- odoo/tools/pycompat.py | 11 +- odoo/tools/safe_eval.py | 3 +- odoo/tools/test_reports.py | 2 +- odoo/tools/translate.py | 10 +- odoo/tools/yaml_import.py | 36 ++-- odoo/tools/yaml_tag.py | 5 +- setup.py | 8 +- setup/setup_dev.py | 2 +- 237 files changed, 1160 insertions(+), 953 deletions(-) diff --git a/addons/account/models/account.py b/addons/account/models/account.py index 0f1ea103ecb9..b9f1cf4dd91f 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 0a510fe0c9cf..73a4102311e3 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 eb744bf5c115..79a093d22032 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 bf060df73448..89b2f8ef327d 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 99c1b61d6361..c7207a9e87b6 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 83ecd07ea898..c8245752e4f5 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 e826524eb57f..72976451e8c9 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 dc717c2829b9..c4b2568bfa5c 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 2dde8c979bcf..3f08209b129f 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 401ce8856d51..4dfe7d970e58 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 2fa1df2ff6c3..dbfd933c99aa 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 53981c783644..bc6f9b53f1fc 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 ad316e2b0a20..50012321db5d 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 6a49e33f547f..103f0595524c 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 00b3fd3e36e8..4ba3245871ff 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 d2ba59edf2fa..ac64c1faea0b 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 0f120e000d82..5cf492c76544 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 65928db4b0ad..2d4fe1b17d26 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 a0b355d4330e..b9c35483e189 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 6c1b0facad3d..db636797d12a 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 99e0b98c5ea3..8527ad6e33d5 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 0fcfceca3990..f8ec7979ab80 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 c9c0a1503643..d99d8c58a130 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 d6e5ce9176ad..98df93565765 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 2cc4ac92ab83..916f6c0d9e02 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 996b91b03499..b09056f2ff16 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 0f0142f3f043..c2613c6baa54 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 0502af6d0abc..2d1d850d3a92 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 9a703b40978c..5219bc240d5f 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 871efe42c963..adaa6651f5b8 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 1d051bcfd82c..8c57a0cf3c4d 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 98f5e5c95d92..740f6281e9ab 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 df6810ebbf3e..421b070dcb77 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 a8145e302eff..c50b2ed9b1f1 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 27f3f3e002dc..ccfa6d3e692d 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 c5c3e50a1579..ccec10601b11 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 4db265d0091f..d87c24dd6634 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 827863e3adc3..12c6a7d7f2ec 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 5a306ae9a33a..89f037d55d32 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 bb87f2f7e48c..ce8e50f6852d 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 169368b9bd5d..d8e136faf533 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 efbcb03a5c32..7bd91ccfa954 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 facef9e2f6ff..4c442c6f09ca 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 ca6e4fa93621..ce3b7d1a8273 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 894dfa91a35b..fd30b7a1b236 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 5789e07d1584..23043d2edb98 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 9302b9a5ce51..3080f328457b 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 616f70346ef6..89d14af6153b 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 b0294d0f8152..74c324a303ea 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 c2f0e8cb3c2e..85ca245c32c2 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 3f6c47ba5cec..5333b7ed4cbf 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 cdc82c238529..0464f8249edb 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 710ebea9d88f..9993272573f9 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 9d6881214239..12dcd46c1401 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 a3ec3e7f51b8..c94dfd21d847 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 11844ef77206..5e2fb25150fa 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 6012dc9c22b6..fac6edfff982 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 ce169c911279..a6bd60f82c92 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 3ac2285fab37..4d8f0ca09258 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 ac2183d767ae..18d73ecb4dd2 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 45e562889f7b..ca426189b388 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 6d3b1f0fcc32..b07adf60608e 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 4958c043bb74..02cb12c07c74 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 85e122bdb487..7e98bbea2c0b 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 92822154ff30..76412ce041c9 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 a6a3acf303e9..70a7b0c07afa 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 cc3519ef737e..8a683be0994d 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 0dc9cdc96afc..49d87c2fe3c1 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 4bd3c1b00832..627859d1a8e5 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 0bb35fe9c90c..70089f812c6f 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 000e77171dd1..a659e45e9623 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 dfa1dbf859ba..1c8f08e79bd3 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 bb98284f9f2d..ae990799b38a 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 b54adc58a9c1..8b2d8944813e 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 6eef3ae80cc6..75a54d935266 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 50e4e95fdc4f..c16113cd52b3 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 e5e61f6e8f30..5af6a454b470 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 2235226769fb..e46f078c1348 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 dbd17233e581..34cf982c82ed 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 00359c4d0387..768cb8062a3c 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 f4916a42ca8d..ebcddbfcfdf6 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 aeafe819fe54..554821254446 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 c4dc828b540e..a5e5d92fd2be 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 85555085df58..91f72d6cadda 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 cfaf7984d284..64366c901c48 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 fa65e79b38ec..482d9b688f53 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 5c8447344f86..2d243e7de78e 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 5e4a8757d689..5513e4393eed 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 ffd1c1fa559e..2003cb7bbfd9 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 3bc5491b6ef3..8126cc5bb230 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 2a8336addafd..7fd98913b8d2 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 028d1a3af14f..47704a347fd8 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 32da153366d3..de4e5617a533 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 de5a9b5ecb41..8b922dd5ff80 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 dcd930d3eb15..d0b11a6ac026 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 27a70360d45e..cb80ca6cf35e 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 45bf0617b33d..9582cc7579f1 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 ef58a8e44aa4..cad770f842db 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 4e3cbabc9fb0..a201e66185eb 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 2dd9dba77264..bc8d314206e9 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 a00176ddfef9..36f814c9385c 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 8aa4074c8bb3..846c6e656f6e 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 6b71bb07975a..0d4b11489d16 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 7ce3bb2abede..8cf07b02c367 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 5c5822a83d71..0771fdaaaec8 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 591842e42250..7a806be4478f 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 629015a4a10e..1b3f3d34c907 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 1d144695f520..e100d32f3ee8 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 bb4916e30de6..ea6fb20b9622 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 3ae8f6d6bf87..369995e7a15b 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 fa373b6c9eb2..df6d8d7ecf94 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 12134cc0f0f5..def612f5d436 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 3d367b0364b7..a35ae5ab84e1 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 322289d4e33a..01786368b722 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 b0763580fefd..f00c6ff51224 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 ebbc4dfac624..3974e798461e 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 ba23c839e677..56b6c76eefeb 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 751767800d69..e3bd9ec94e2b 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 65464fa7493d..4c065a5b634c 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 94a90e3a2ffb..1cfb14a8d0a5 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 ccdd44e1c4d6..1d0e0a85dd8b 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 b6902b17c41a..a4648e7d99d6 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 4ecee90fd5e9..e6cedfe07d20 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 bd7621c767dc..9968ad8e6366 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 0c3dfe0434a0..9267826c6893 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 704cd62a41b2..a9f1e9b1f845 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 a90c14f31931..415e1668ce18 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 6ae960fc9c9f..3a5ef95c3d4e 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 eee9acc3c328..7e9c3acaa8ed 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 5e69b2a28522..3c18c7cf4706 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 99a39ac9c918..f501a64eefae 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 5aa8073192ff..cb2fbf3ba033 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 b69ffaf3e290..4946c5b25de8 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 9a9ef605118f..00e51627c0c7 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 99c4fabe7837..e709e0b3d065 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 edb473d90e01..26ce63962c75 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 3463ec0b3be6..3fb372105895 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 6a93f387ec16..cf70e0dd62a1 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 3c118176ec62..75a2ce33fb2f 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 ca536bc28bd3..0c8535c9b309 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 d780e803e40a..f303f7642936 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 c36d5aef7183..d3e274a24307 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 71968b3d850a..2ed3eafc2f62 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 753e4eef1ae7..675b56e561f0 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 1784f30b9494..3097ac0437cd 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 3f89ed2b80d3..e9385d68e725 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 4d8762e43469..874c311f92a2 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 8c1241f7ac87..4eb7cb68fa7b 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 847ad64b3031..1ce21cff8e85 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 f36c7c6111f2..9561280c424c 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 d00377d73224..9ff71a4f1a01 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 d5a8f77a72dc..05ed4c785000 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 fc2d69de53e4..baf78e4c01b7 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 20f4147f0bd6..ec04a71d6ab8 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 089f2b97f352..162daa5c0c80 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 798faca55a0d..a47ffcd244bc 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 f55374eda98d..bf9970e9beb8 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 4b2cee0b4115..6385c84069a1 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 bd2a544a599a..d391246b9cd6 100644 --- a/addons/website_slides/models/slides.py +++ b/addons/website_slides/models/slides.py @@ -12,7 +12,7 @@ import re import urllib2 from odoo import api, fields, models, SUPERUSER_ID, _ -from odoo.tools import image +from odoo.tools import image, pycompat from odoo.tools.translate import html_translate from odoo.exceptions import Warning from odoo.addons.website.models.website import slug @@ -323,7 +323,7 @@ class Slide(models.Model): values = res['values'] if not values.get('document_id'): raise Warning(_('Please enter valid Youtube or Google Doc URL')) - for key, value in values.iteritems(): + for key, value in pycompat.items(values): setattr(self, key, value) # website @@ -386,7 +386,7 @@ class Slide(models.Model): values['date_published'] = datetime.datetime.now() if values.get('url'): doc_data = self._parse_document_url(values['url']).get('values', dict()) - for key, value in doc_data.iteritems(): + for key, value in pycompat.items(doc_data): values.setdefault(key, value) # Do not publish slide if user has not publisher rights if not self.user_has_groups('website.group_website_publisher'): @@ -400,7 +400,7 @@ class Slide(models.Model): def write(self, values): if values.get('url'): doc_data = self._parse_document_url(values['url']).get('values', dict()) - for key, value in doc_data.iteritems(): + for key, value in pycompat.items(doc_data): values.setdefault(key, value) if values.get('channel_id'): custom_channels = self.env['slide.channel'].search([('custom_slide_id', '=', self.id), ('id', '!=', values.get('channel_id'))]) diff --git a/doc/_extensions/odoo_ext/translator.py b/doc/_extensions/odoo_ext/translator.py index 48c5e901f281..1f7fba93772d 100644 --- a/doc/_extensions/odoo_ext/translator.py +++ b/doc/_extensions/odoo_ext/translator.py @@ -71,9 +71,9 @@ class BootstrapTranslator(nodes.NodeVisitor, object): tagname = unicode(tagname).lower() # extract generic attributes - attrs = {name.lower(): value for name, value in attributes.iteritems()} + attrs = {name.lower(): value for name, value in attributes.items()} attrs.update( - (name, value) for name, value in node.attributes.iteritems() + (name, value) for name, value in node.attributes.items() if name.startswith('data-') ) @@ -97,7 +97,7 @@ class BootstrapTranslator(nodes.NodeVisitor, object): prefix=u''.join(prefix), tag=tagname, attrs=u' '.join(u'{}="{}"'.format(name, self.attval(value)) - for name, value in attrs.iteritems()), + for name, value in attrs.items()), postfix=u''.join(postfix), ) # only "space characters" SPACE, CHARACTER TABULATION, LINE FEED, diff --git a/doc/python3.rst b/doc/python3.rst index df86107c35b7..ba368572c130 100644 --- a/doc/python3.rst +++ b/doc/python3.rst @@ -172,6 +172,37 @@ statement to the following cross-language forms:: exec(source, globals) exec(source, globals, locals) +List/iteration builtins and methods +----------------------------------- + +In Python 3, a number of builtins and methods formerly returning *lists* were +converted to return *iterators* or *views*, with the corresponding redundant +methods or functions having been *removed entirely*: + +* In Python 3, ``map``, ``filter`` and ``zip`` return iterators, + ``itertools.imap``, ``itertools.ifilter`` and ``itertools.izip`` have been + removed. + + .. important:: + + When possible, use comprehensions (list, generator, ...) rather than + ``map`` or ``filter``, otherwise use the cross-version ``pycompat`` + versions (``pycompat.imap``, ``pycompat.ifilter`` and + ``pycompat.izip``). The ``pycompat`` versions all return *iterators* and + may need to be wrapped in a ``list()`` call to yield a list. + +* In Python 3, ``dict.keys``, ``dict.values`` and ``dict.items`` return + *views* rather than lists, and the ``iter*`` and ``view*`` methods have + been removed. + + .. important:: + + Prefer using :func:`odoo.tools.pycompat.keys`, + :func:`odoo.tools.pycompat.values` and :func:`odoo.tools.pycompat.items` + return cross-version iterators. When needing actual lists (e.g. to + modify a dictionary during iteration), wrap one of the calls above in a + ``list()``. + builtins -------- @@ -258,21 +289,12 @@ code by replacing it with some other method altogether. ``xrange`` ########## -In Python 3, ``range()`` behaves the same as Python 3's ``xrange``. For -cross-versions code you can: - -* just use ``range()`` everywhere and ignore the allocation cost of a list in - Python 2 (often not an issue) -* conditionally alias ``xrange`` to ``range`` in Python 3 and use that -* use a combination of ``itertools.count`` and ``takewhile`` for a - cross-compatible lazy increasing sequence of numbers - -.. warning:: +In Python 3, ``range()`` behaves the same as Python 3's ``xrange``. - In the *rare* cases where you need conditional code (code which applies - for one version of python and not the other), use ``sys.version_info`` - e.g. ``sys.version_info() >= (3,)`` for Python3+ code or - ``sys.version_info() < (3,)`` for Python 2 code. +For cross-version code, you can just use ``range()`` everywhere: while this +will incur a slight allocation cost on Python 2, Python 3's ``range`` supports +the entire Sequence protocol and thus behaves very much like a regular +list or tuple. Removed/renamed methods ----------------------- diff --git a/odoo/addons/base/ir/ir_actions.py b/odoo/addons/base/ir/ir_actions.py index 918e5d908806..4979c898e08a 100644 --- a/odoo/addons/base/ir/ir_actions.py +++ b/odoo/addons/base/ir/ir_actions.py @@ -5,6 +5,7 @@ import odoo from odoo import api, fields, models, tools, SUPERUSER_ID, _ from odoo.exceptions import MissingError, UserError, ValidationError, AccessError from odoo.tools.safe_eval import safe_eval, test_python_expr +from odoo.tools import pycompat from odoo.http import request import datetime @@ -649,17 +650,19 @@ class IrActionsTodo(models.Model): """ user_groups = self.env.user.groups_id - def groups_match(todo): - """ Checks if the todo's groups match those of the current user """ - return not todo.groups_id or bool(todo.groups_id & user_groups) - - done = filter(groups_match, self.browse(self.search([('state', '!=', 'open')]))) - total = filter(groups_match, self.browse(self.search([]))) - + done_count = self.search_count([ + ('state', '!=', open), + '|', ('groups_id', '=', False), + ('groups_id', 'in', user_groups.ids), + ]) + total_count = self.search_count([ + '|', ('groups_id', '=', False), + ('groups_id', 'in', user_groups.ids), + ]) return { - 'done': len(done), - 'total': len(total), - 'todo': len(total) - len(done), + 'done': done_count, + 'total': total_count, + 'todo': total_count - done_count, } @@ -688,7 +691,7 @@ class IrActionsActClient(models.Model): @api.depends('params_store') def _compute_params(self): self_bin = self.with_context(bin_size=False, bin_size_params_store=False) - for record, record_bin in zip(self, self_bin): + for record, record_bin in pycompat.izip(self, self_bin): record.params = record_bin.params_store and safe_eval(record_bin.params_store, {'uid': self._uid}) def _inverse_params(self): diff --git a/odoo/addons/base/ir/ir_attachment.py b/odoo/addons/base/ir/ir_attachment.py index b6bd9a08ee77..9d19d0764388 100644 --- a/odoo/addons/base/ir/ir_attachment.py +++ b/odoo/addons/base/ir/ir_attachment.py @@ -11,7 +11,7 @@ from collections import defaultdict from odoo import api, fields, models, tools, SUPERUSER_ID, _ from odoo.exceptions import AccessError -from odoo.tools import config, human_size, ustr, html_escape +from odoo.tools import config, human_size, ustr, html_escape, pycompat from odoo.tools.mimetypes import guess_mimetype _logger = logging.getLogger(__name__) @@ -167,7 +167,7 @@ class IrAttachment(models.Model): # remove garbage files, and clean up checklist removed = 0 - for fname, filepath in checklist.iteritems(): + for fname, filepath in pycompat.items(checklist): if fname not in whitelist: try: os.unlink(self._full_path(fname)) @@ -318,7 +318,7 @@ class IrAttachment(models.Model): model_ids[values['res_model']].add(values['res_id']) # check access rights on the records - for res_model, res_ids in model_ids.iteritems(): + for res_model, res_ids in pycompat.items(model_ids): # ignore attachments that are not attached to a resource anymore # when checking access rights (resource was deleted but attachment # was not) @@ -374,12 +374,12 @@ class IrAttachment(models.Model): # To avoid multiple queries for each attachment found, checks are # performed in batch as much as possible. - for res_model, targets in model_attachments.iteritems(): + for res_model, targets in pycompat.items(model_attachments): if res_model not in self.env: continue if not self.env[res_model].check_access_rights('read', False): # remove all corresponding attachment ids - ids.difference_update(itertools.chain(*targets.itervalues())) + ids.difference_update(itertools.chain(*pycompat.values(targets))) continue # filter ids according to what access rules permit target_ids = list(targets) diff --git a/odoo/addons/base/ir/ir_config_parameter.py b/odoo/addons/base/ir/ir_config_parameter.py index cbb44ca64572..8e4ea20b5a5d 100644 --- a/odoo/addons/base/ir/ir_config_parameter.py +++ b/odoo/addons/base/ir/ir_config_parameter.py @@ -8,7 +8,7 @@ import uuid import logging from odoo import api, fields, models -from odoo.tools import config, ormcache, mute_logger +from odoo.tools import config, ormcache, mute_logger, pycompat _logger = logging.getLogger(__name__) @@ -42,7 +42,7 @@ class IrConfigParameter(models.Model): Initializes the parameters listed in _default_parameters. It overrides existing parameters if force is ``True``. """ - for key, func in _default_parameters.iteritems(): + for key, func in pycompat.items(_default_parameters): # force=True skips search and always performs the 'if' body (because ids=False) params = self.sudo().search([('key', '=', key)]) if force or not params: diff --git a/odoo/addons/base/ir/ir_fields.py b/odoo/addons/base/ir/ir_fields.py index 26c351d6fa82..204d45994252 100644 --- a/odoo/addons/base/ir/ir_fields.py +++ b/odoo/addons/base/ir/ir_fields.py @@ -8,13 +8,13 @@ import psycopg2 import pytz from odoo import api, fields, models, _ -from odoo.tools import ustr +from odoo.tools import ustr, pycompat REFERENCING_FIELDS = {None, 'id', '.id'} def only_ref_fields(record): - return {k: v for k, v in record.iteritems() if k in REFERENCING_FIELDS} + return {k: v for k, v in pycompat.items(record) if k in REFERENCING_FIELDS} def exclude_ref_fields(record): - return {k: v for k, v in record.iteritems() if k not in REFERENCING_FIELDS} + return {k: v for k, v in pycompat.items(record) if k not in REFERENCING_FIELDS} CREATE = lambda values: (0, False, values) UPDATE = lambda id, values: (1, id, values) @@ -43,9 +43,9 @@ class IrFieldsConverter(models.AbstractModel): if isinstance(error_params, basestring): error_params = sanitize(error_params) elif isinstance(error_params, dict): - error_params = {k: sanitize(v) for k, v in error_params.iteritems()} + error_params = {k: sanitize(v) for k, v in pycompat.items(error_params)} elif isinstance(error_params, tuple): - error_params = tuple(map(sanitize, error_params)) + error_params = tuple(sanitize(v) for v in error_params) return error_type(error_msg % error_params, error_args) @api.model @@ -64,12 +64,12 @@ class IrFieldsConverter(models.AbstractModel): converters = { name: self.to_field(model, field, fromtype) - for name, field in model._fields.iteritems() + for name, field in pycompat.items(model._fields) } def fn(record, log): converted = {} - for field, value in record.iteritems(): + for field, value in pycompat.items(record): if field in REFERENCING_FIELDS: continue if not value: @@ -369,7 +369,7 @@ class IrFieldsConverter(models.AbstractModel): :rtype: str, list """ # Can import by name_get, external id or database id - fieldset = set(record.iterkeys()) + fieldset = set(record) if fieldset - REFERENCING_FIELDS: raise ValueError( _(u"Can not create Many-To-One records indirectly, import the field separately")) diff --git a/odoo/addons/base/ir/ir_http.py b/odoo/addons/base/ir/ir_http.py index 3d9c72aec60a..aa23531055ee 100644 --- a/odoo/addons/base/ir/ir_http.py +++ b/odoo/addons/base/ir/ir_http.py @@ -21,6 +21,7 @@ import odoo from odoo import api, http, models, tools, SUPERUSER_ID from odoo.exceptions import AccessDenied, AccessError from odoo.http import request, STATIC_CACHE, content_disposition +from odoo.tools import pycompat from odoo.tools.mimetypes import guess_mimetype from odoo.modules.module import get_resource_path, get_module_path @@ -54,7 +55,7 @@ class ModelsConverter(werkzeug.routing.BaseConverter): def to_python(self, value): env = api.Environment(request.cr, UID_PLACEHOLDER, request.context) - return env[self.model].browse(map(int, value.split(','))) + return env[self.model].browse(int(v) for v in value.split(',')) def to_url(self, value): return ",".join(value.ids) @@ -202,7 +203,7 @@ class IrHttp(models.AbstractModel): @classmethod def _postprocess_args(cls, arguments, rule): """ post process arg to set uid on browse records """ - for name, arg in arguments.items(): + for name, arg in list(pycompat.items(arguments)): if isinstance(arg, models.BaseModel) and arg._uid is UID_PLACEHOLDER: arguments[name] = arg.sudo(request.uid) if not arg.exists(): diff --git a/odoo/addons/base/ir/ir_mail_server.py b/odoo/addons/base/ir/ir_mail_server.py index 22f78da6c18c..737e9806d68a 100644 --- a/odoo/addons/base/ir/ir_mail_server.py +++ b/odoo/addons/base/ir/ir_mail_server.py @@ -13,9 +13,11 @@ import re import smtplib import threading +import itertools + from odoo import api, fields, models, tools, _ from odoo.exceptions import except_orm, UserError -from odoo.tools import html2text, ustr +from odoo.tools import html2text, ustr, pycompat _logger = logging.getLogger(__name__) _test_logger = logging.getLogger('odoo.tests') @@ -110,7 +112,7 @@ def extract_rfc2822_addresses(text): if not text: return [] candidates = address_pattern.findall(ustr(text).encode('utf-8')) - return filter(try_coerce_ascii, candidates) + return [c for c in candidates if try_coerce_ascii(c)] def encode_rfc2822_address_header(header_text): @@ -127,7 +129,7 @@ def encode_rfc2822_address_header(header_text): return formataddr((name, email)) addresses = getaddresses([ustr(header_text).encode('utf-8')]) - return COMMASPACE.join(map(encode_addr, addresses)) + return COMMASPACE.join(encode_addr(a) for a in addresses) class IrMailServer(models.Model): @@ -333,7 +335,7 @@ class IrMailServer(models.Model): msg['Bcc'] = encode_rfc2822_address_header(COMMASPACE.join(email_bcc)) msg['Date'] = formatdate() # Custom headers may override normal headers or provide additional ones - for key, value in headers.iteritems(): + for key, value in pycompat.items(headers): msg[ustr(key).encode('utf-8')] = encode_header(value) if subtype == 'html' and not body_alternative and html2text: @@ -442,7 +444,12 @@ class IrMailServer(models.Model): email_cc = message['Cc'] email_bcc = message['Bcc'] - smtp_to_list = filter(None, tools.flatten(map(extract_rfc2822_addresses, [email_to, email_cc, email_bcc]))) + smtp_to_list = [ + address + for base in [email_to, email_cc, email_bcc] + for address in extract_rfc2822_addresses(base) + if address + ] assert smtp_to_list, self.NO_VALID_RECIPIENT x_forge_to = message['X-Forge-To'] diff --git a/odoo/addons/base/ir/ir_model.py b/odoo/addons/base/ir/ir_model.py index ad72694d4102..9272a790fbc8 100644 --- a/odoo/addons/base/ir/ir_model.py +++ b/odoo/addons/base/ir/ir_model.py @@ -45,7 +45,7 @@ def query_insert(cr, table, values): query = INSERT_QUERY.format( table=table, cols=",".join(values), - vals=",".join(map("%({0})s".format, values)), + vals=",".join("%({0})s".format(v) for v in values), ) cr.execute(query, values) @@ -53,8 +53,8 @@ def query_update(cr, table, values, selectors): setters = set(values) - set(selectors) query = UPDATE_QUERY.format( table=table, - assignment=",".join(map("{0}=%({0})s".format, setters)), - condition=" AND ".join(map("{0}=%({0})s".format, selectors)), + assignment=",".join("{0}=%({0})s".format(s) for s in setters), + condition=" AND ".join("{0}=%({0})s".format(s) for s in selectors), ) cr.execute(query, values) @@ -188,7 +188,7 @@ class IrModel(models.Model): @api.multi def write(self, vals): if '__last_update' in self._context: - self = self.with_context({k: v for k, v in self._context.iteritems() if k != '__last_update'}) + self = self.with_context({k: v for k, v in pycompat.items(self._context) if k != '__last_update'}) if 'model' in vals and any(rec.model != vals['model'] for rec in self): raise UserError(_('Field "Model" cannot be modified on models.')) if 'state' in vals and any(rec.state != vals['state'] for rec in self): @@ -559,8 +559,8 @@ class IrModelFields(models.Model): except Exception: raise UserError("\n".join([ _("Cannot rename/delete fields that are still present in views:"), - _("Fields:") + " " + ", ".join(map(str, fields)), - _("View:") + " " + view.name, + _("Fields: %s") % ", ".join(str(f) for f in fields), + _("View: %s") % view.name, ])) finally: # the registry has been modified, restore it @@ -760,7 +760,7 @@ class IrModelFields(models.Model): fields_data[field.name] = dict(params, id=record.id) return record - diff = {key for key, val in params.items() if field_data[key] != val} + diff = {key for key, val in pycompat.items(params) if field_data[key] != val} if diff: cr = self.env.cr # update the entry in this table @@ -778,7 +778,7 @@ class IrModelFields(models.Model): def _reflect_model(self, model): """ Reflect the given model's fields. """ self.clear_caches() - for field in model._fields.itervalues(): + for field in pycompat.values(model._fields): self._reflect_field(field) if not self.pool._init: @@ -1228,7 +1228,7 @@ class IrModelData(models.Model): @api.depends('module', 'name') def _compute_complete_name(self): for res in self: - res.complete_name = ".".join(filter(None, [res.module, res.name])) + res.complete_name = ".".join(n for n in [res.module, res.name] if n) @api.depends('model', 'res_id') def _compute_reference(self): @@ -1259,7 +1259,7 @@ class IrModelData(models.Model): model_id_name[xid.model][xid.res_id] = None # fill in model_id_name with name_get() of corresponding records - for model, id_name in model_id_name.iteritems(): + for model, id_name in pycompat.items(model_id_name): try: ng = self.env[model].browse(id_name).name_get() id_name.update(ng) @@ -1356,7 +1356,7 @@ class IrModelData(models.Model): record = self.get_object(module, xml_id) if record: self.loads[(module, xml_id)] = (model, record.id) - for parent_model, parent_field in self.env[model]._inherits.iteritems(): + for parent_model, parent_field in pycompat.items(self.env[model]._inherits): parent = record[parent_field] parent_xid = '%s_%s' % (xml_id, parent_model.replace('.', '_')) self.loads[(module, parent_xid)] = (parent_model, parent.id) @@ -1411,7 +1411,7 @@ class IrModelData(models.Model): elif record: record.write(values) if xml_id: - for parent_model, parent_field in record._inherits.iteritems(): + for parent_model, parent_field in pycompat.items(record._inherits): self.sudo().create({ 'name': xml_id + '_' + parent_model.replace('.', '_'), 'model': parent_model, @@ -1430,7 +1430,7 @@ class IrModelData(models.Model): elif mode == 'init' or (mode == 'update' and xml_id): existing_parents = set() # {parent_model, ...} if xml_id: - for parent_model, parent_field in record._inherits.iteritems(): + for parent_model, parent_field in pycompat.items(record._inherits): xid = self.sudo().search([ ('module', '=', module), ('name', '=', xml_id + '_' + parent_model.replace('.', '_')), @@ -1450,7 +1450,7 @@ class IrModelData(models.Model): inherit_models = [record] while inherit_models: current_model = inherit_models.pop() - for parent_model_name, parent_field in current_model._inherits.iteritems(): + for parent_model_name, parent_field in pycompat.items(current_model._inherits): inherit_models.append(self.env[parent_model_name]) if parent_model_name in existing_parents: continue @@ -1476,7 +1476,7 @@ class IrModelData(models.Model): if xml_id and record: self.loads[(module, xml_id)] = (model, record.id) - for parent_model, parent_field in record._inherits.iteritems(): + for parent_model, parent_field in pycompat.items(record._inherits): parent_xml_id = xml_id + '_' + parent_model.replace('.', '_') self.loads[(module, parent_xml_id)] = (parent_model, record[parent_field].id) diff --git a/odoo/addons/base/ir/ir_qweb/assetsbundle.py b/odoo/addons/base/ir/ir_qweb/assetsbundle.py index 5bc2dec8a9bc..19902e78d1b8 100644 --- a/odoo/addons/base/ir/ir_qweb/assetsbundle.py +++ b/odoo/addons/base/ir/ir_qweb/assetsbundle.py @@ -13,7 +13,7 @@ from odoo.http import request from odoo.modules.module import get_resource_path import psycopg2 import werkzeug -from odoo.tools import func, misc +from odoo.tools import func, misc, pycompat import logging _logger = logging.getLogger(__name__) @@ -308,7 +308,7 @@ class AssetsBundle(object): outdated = False assets = dict((asset.html_url, asset) for asset in self.stylesheets if isinstance(asset, atype)) if assets: - assets_domain = [('url', 'in', assets.keys())] + assets_domain = [('url', 'in', list(assets))] attachments = self.env['ir.attachment'].sudo().search(assets_domain) for attachment in attachments: asset = assets[attachment.url] @@ -320,7 +320,7 @@ class AssetsBundle(object): if not asset._content and attachment.file_size > 0: asset._content = None # file missing, force recompile - if any(asset._content is None for asset in assets.itervalues()): + if any(asset._content is None for asset in pycompat.values(assets)): outdated = True if outdated: @@ -452,7 +452,7 @@ class WebAsset(object): def stat(self): if not (self.inline or self._filename or self._ir_attach): - path = filter(None, self.url.split('/')) + path = (segment for segment in self.url.split('/') if segment) self._filename = get_resource_path(*path) if self._filename: return diff --git a/odoo/addons/base/ir/ir_qweb/ir_qweb.py b/odoo/addons/base/ir/ir_qweb/ir_qweb.py index 68c4bf048d7a..5548fcd47d8d 100644 --- a/odoo/addons/base/ir/ir_qweb/ir_qweb.py +++ b/odoo/addons/base/ir/ir_qweb/ir_qweb.py @@ -75,7 +75,7 @@ class IrQWeb(models.AbstractModel, QWeb): # apply ormcache_context decorator unless in dev mode... @tools.conditional( 'xml' not in tools.config['dev_mode'], - tools.ormcache('id_or_xml_id', 'tuple(map(options.get, self._get_template_cache_keys()))'), + tools.ormcache('id_or_xml_id', 'tuple(options.get(k) for k in self._get_template_cache_keys())'), ) def compile(self, id_or_xml_id, options): return super(IrQWeb, self).compile(id_or_xml_id, options=options) @@ -173,7 +173,7 @@ class IrQWeb(models.AbstractModel, QWeb): if field_options and 'monetary' in field_options: try: options = "{'widget': 'monetary'" - for k, v in json.loads(field_options).iteritems(): + for k, v in pycompat.items(json.loads(field_options)): if k in ('display_currency', 'from_currency'): options = "%s, '%s': %s" % (options, k, v) else: @@ -238,12 +238,12 @@ class IrQWeb(models.AbstractModel, QWeb): atype = 'text/less' if atype not in ('text/less', 'text/sass'): atype = 'text/css' - path = filter(None, href.split('/')) + path = [segment for segment in href.split('/') if segment] filename = get_resource_path(*path) if path else None files.append({'atype': atype, 'url': href, 'filename': filename, 'content': el.text, 'media': media}) elif el.tag == 'script': atype = 'text/javascript' - path = filter(None, src.split('/')) + path = [segment for segment in src.split('/') if segment] filename = get_resource_path(*path) if path else None files.append({'atype': atype, 'url': src, 'filename': filename, 'content': el.text, 'media': media}) else: diff --git a/odoo/addons/base/ir/ir_qweb/qweb.py b/odoo/addons/base/ir/ir_qweb/qweb.py index 14a0a54fbd9d..06aba8ccdb42 100644 --- a/odoo/addons/base/ir/ir_qweb/qweb.py +++ b/odoo/addons/base/ir/ir_qweb/qweb.py @@ -6,14 +6,15 @@ import traceback from collections import OrderedDict, Sized, Mapping, defaultdict from functools import reduce -from itertools import chain, izip, tee, count +from itertools import tee, count from textwrap import dedent +import itertools from lxml import etree, html import werkzeug from werkzeug.utils import escape as _escape -from odoo.tools import pycompat +from odoo.tools import pycompat, freehash try: import builtins @@ -82,7 +83,7 @@ class Contextifier(ast.NodeTransformer): return ast.copy_location(ast.Lambda( args=ast.arguments( args=args.args, - defaults=map(self.visit, args.defaults), + defaults=[self.visit(default) for default in args.defaults], vararg=args.vararg, kwarg=args.kwarg, ), @@ -109,7 +110,7 @@ class Contextifier(ast.NodeTransformer): for field, value in ast.iter_fields(node): # map transformation of comprehensions if isinstance(value, list): - setattr(newnode, field, map(transformer.visit, value)) + setattr(newnode, field, [transformer.visit(v) for v in value]) else: # set transformation of key/value/expr fields setattr(newnode, field, transformer.visit(value)) return newnode @@ -168,14 +169,14 @@ def foreach_iterator(base_ctx, enum, name): if not enum: return if isinstance(enum, int): - enum = pycompat.range(enum) + enum = range(enum) size = None if isinstance(enum, Sized): ctx["%s_size" % name] = size = len(enum) if isinstance(enum, Mapping): - enum = enum.iteritems() + enum = pycompat.items(enum) else: - enum = izip(*tee(enum)) + enum = pycompat.izip(*tee(enum)) value_key = '%s_value' % name index_key = '%s_index' % name first_key = '%s_first' % name @@ -201,7 +202,7 @@ def foreach_iterator(base_ctx, enum, name): yield ctx # copy changed items back into source context (?) # FIXME: maybe values could provide a ChainMap-style clone? - for k in base_ctx.keys(): + for k in list(base_ctx): base_ctx[k] = ctx[k] _FORMAT_REGEX = re.compile( @@ -226,7 +227,7 @@ class frozendict(dict): def update(self, *args, **kwargs): raise NotImplementedError("'update' not supported on frozendict") def __hash__(self): - return hash(frozenset((key, freehash(val)) for key, val in self.iteritems())) + return hash(frozenset((key, freehash(val)) for key, val in pycompat.items(self))) #################################### @@ -510,6 +511,7 @@ class QWeb(object): """ return ast.parse(dedent(""" from collections import OrderedDict + from odoo.tools import pycompat from odoo.addons.base.ir.ir_qweb.qweb import escape, unicodifier, foreach_iterator """)) @@ -665,7 +667,7 @@ class QWeb(object): # all directives have been compiled, there should be none left if any(att.startswith('t-') for att in el.attrib): - raise "Unknown directive on %s" % ("', '".join(directives), etree.tostring(el)) + raise NameError("Unknown directive on %s" % etree.tostring(el)) return [] def _values_var(self, varname, ctx): @@ -726,7 +728,7 @@ class QWeb(object): attrib = {} # If `el` introduced new namespaces, write them as attribute by using the # `attrib` dict. - for ns_prefix, ns_definition in set(el.nsmap.items()) - set(options['nsmap'].items()): + for ns_prefix, ns_definition in set(pycompat.items(el.nsmap)) - set(pycompat.items(options['nsmap'])): if ns_prefix is None: attrib['xmlns'] = ns_definition else: @@ -735,8 +737,9 @@ class QWeb(object): # Etree will also remove the ns prefixes indirection in the attributes. As we only have # the namespace definition, we'll use an nsmap where the keys are the definitions and # the values the prefixes in order to get back the right prefix and restore it. - nsprefixmap = {v: k for k, v in options['nsmap'].items() + el.nsmap.items()} - for key, value in el.attrib.items(): + ns = itertools.chain(pycompat.items(options['nsmap']), pycompat.items(el.nsmap)) + nsprefixmap = {v: k for k, v in ns} + for key, value in pycompat.items(el.attrib): attrib_qname = etree.QName(key) if attrib_qname.namespace: attrib['%s:%s' % (nsprefixmap[attrib_qname.namespace], attrib_qname.localname)] = value @@ -754,7 +757,7 @@ class QWeb(object): if unqualified_el_tag == 't': return content - tag = u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in attrib.iteritems()])) + tag = u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in pycompat.items(attrib)])) if unqualified_el_tag in self._void_elements: return [self._append(ast.Str(tag + '/>'))] + content else: @@ -766,10 +769,10 @@ class QWeb(object): # Etree will also remove the ns prefixes indirection in the attributes. As we only have # the namespace definition, we'll use an nsmap where the keys are the definitions and # the values the prefixes in order to get back the right prefix and restore it. - nsprefixmap = {v: k for k, v in options['nsmap'].items() + el.nsmap.items()} + nsprefixmap = {v: k for k, v in itertools.chain(pycompat.items(options['nsmap']), pycompat.items(el.nsmap))} nodes = [] - for key, value in el.attrib.iteritems(): + for key, value in pycompat.items(el.attrib): if not key.startswith('t-'): attrib_qname = etree.QName(key) if attrib_qname.namespace: @@ -784,7 +787,7 @@ class QWeb(object): We do not support namespaced dynamic attributes. """ nodes = [] - for name, value in el.attrib.iteritems(): + for name, value in pycompat.items(el.attrib): if name.startswith('t-attf-'): nodes.append((name[7:], self._compile_format(value))) elif name.startswith('t-att-'): @@ -810,7 +813,7 @@ class QWeb(object): def _compile_all_attributes(self, el, options, attr_already_created=False): """ Compile the attributes of the given elements into a list of AST nodes. """ body = [] - if any(name.startswith('t-att') or not name.startswith('t-') for name, value in el.attrib.iteritems()): + if any(name.startswith('t-att') or not name.startswith('t-') for name, value in pycompat.items(el.attrib)): if not attr_already_created: attr_already_created = True body.append( @@ -851,7 +854,7 @@ class QWeb(object): ))) if attr_already_created: - # for name, value in t_attrs.iteritems(): + # for name, value in pycompat.items(t_attrs): # if value or isinstance(value, basestring)): # append(u' ') # append(name) @@ -862,11 +865,11 @@ class QWeb(object): target=ast.Tuple(elts=[ast.Name(id='name', ctx=ast.Store()), ast.Name(id='value', ctx=ast.Store())], ctx=ast.Store()), iter=ast.Call( func=ast.Attribute( - value=ast.Name(id='t_attrs', ctx=ast.Load()), - attr='iteritems', + value=ast.Name(id='pycompat', ctx=ast.Load()), + attr='items', ctx=ast.Load() ), - args=[], keywords=[], + args=[ast.Name(id='t_attrs', ctx=ast.Load())], keywords=[], starargs=None, kwargs=None ), body=[ast.If( @@ -923,7 +926,7 @@ class QWeb(object): # If `el` introduced new namespaces, write them as attribute by using the # `extra_attrib` dict. - for ns_prefix, ns_definition in set(el.nsmap.items()) - set(options['nsmap'].items()): + for ns_prefix, ns_definition in set(pycompat.items(el.nsmap)) - set(pycompat.items(options['nsmap'])): if ns_prefix is None: extra_attrib['xmlns'] = ns_definition else: @@ -932,7 +935,7 @@ class QWeb(object): if unqualified_el_tag == 't': return content - body = [self._append(ast.Str(u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in extra_attrib.iteritems()]))))] + body = [self._append(ast.Str(u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in pycompat.items(extra_attrib)]))))] body.extend(self._compile_all_attributes(el, options, attr_already_created)) if unqualified_el_tag in self._void_elements: body.append(self._append(ast.Str(u'/>'))) @@ -1054,7 +1057,7 @@ class QWeb(object): def _compile_directive_if(self, el, options): orelse = [] next_el = el.getnext() - if next_el is not None and {'t-else', 't-elif'} & set(next_el.attrib.keys()): + if next_el is not None and {'t-else', 't-elif'} & set(next_el.attrib): if el.tail and not el.tail.isspace(): raise ValueError("Unexpected non-whitespace characters between t-if and t-else directives") el.tail = None @@ -1437,7 +1440,7 @@ class QWeb(object): # make the nsmap an ast dict keys = [] values = [] - for key, value in options['nsmap'].items(): + for key, value in pycompat.items(options['nsmap']): if isinstance(key, basestring): keys.append(ast.Str(s=key)) elif key is None: diff --git a/odoo/addons/base/ir/ir_sequence.py b/odoo/addons/base/ir/ir_sequence.py index a46f5e95ecae..58961e921ba6 100644 --- a/odoo/addons/base/ir/ir_sequence.py +++ b/odoo/addons/base/ir/ir_sequence.py @@ -6,6 +6,7 @@ import pytz from odoo import api, fields, models, _ from odoo.exceptions import UserError +from odoo.tools import pycompat _logger = logging.getLogger(__name__) @@ -178,7 +179,7 @@ class IrSequence(models.Model): 'weekday': '%w', 'h24': '%H', 'h12': '%I', 'min': '%M', 'sec': '%S' } res = {} - for key, format in sequences.iteritems(): + for key, format in pycompat.items(sequences): res[key] = effective_date.strftime(format) res['range_' + key] = range_date.strftime(format) res['current_' + key] = now.strftime(format) diff --git a/odoo/addons/base/ir/ir_translation.py b/odoo/addons/base/ir/ir_translation.py index 7d73f40d65b5..219ebcc471da 100644 --- a/odoo/addons/base/ir/ir_translation.py +++ b/odoo/addons/base/ir/ir_translation.py @@ -128,7 +128,7 @@ class IrTranslationImport(object): env = api.Environment(cr, SUPERUSER_ID, {}) src_relevant_fields = [] for model in env: - for field_name, field in env[model]._fields.items(): + for field_name, field in pycompat.items(env[model]._fields): if hasattr(field, 'translate') and callable(field.translate): src_relevant_fields.append("%s,%s" % (model, field_name)) @@ -530,7 +530,7 @@ class IrTranslation(models.Model): # check for read/write access on translated field records fmode = 'read' if mode == 'read' else 'write' - for mname, ids in model_ids.iteritems(): + for mname, ids in pycompat.items(model_ids): records = self.env[mname].browse(ids) records.check_access_rights(fmode) records.check_field_access_rights(fmode, model_fields[mname]) @@ -640,7 +640,7 @@ class IrTranslation(models.Model): return ['&', ('res_id', '=', rec.id), ('name', '=', name)] # insert missing translations, and extend domain for related fields - for name, fld in record._fields.items(): + for name, fld in pycompat.items(record._fields): if not fld.translate: continue diff --git a/odoo/addons/base/ir/ir_ui_view.py b/odoo/addons/base/ir/ir_ui_view.py index c557d15ee6b6..0a709985ff6b 100644 --- a/odoo/addons/base/ir/ir_ui_view.py +++ b/odoo/addons/base/ir/ir_ui_view.py @@ -8,6 +8,8 @@ import logging import os import re import time + +import itertools from dateutil.relativedelta import relativedelta from operator import itemgetter @@ -49,7 +51,7 @@ def keep_query(*keep_params, **additional_params): if not keep_params and not additional_params: keep_params = ('*',) params = additional_params.copy() - qs_keys = request.httprequest.args.keys() + qs_keys = list(request.httprequest.args) for keep_param in keep_params: for param in fnmatch.filter(qs_keys, keep_param): if param not in additional_params and param in qs_keys: @@ -251,11 +253,11 @@ actual arch. @api.depends('arch') def _compute_arch_base(self): # 'arch_base' is the same as 'arch' without translation - for view, view_wo_lang in zip(self, self.with_context(lang=None)): + for view, view_wo_lang in pycompat.izip(self, self.with_context(lang=None)): view.arch_base = view_wo_lang.arch def _inverse_arch_base(self): - for view, view_wo_lang in zip(self, self.with_context(lang=None)): + for view, view_wo_lang in pycompat.izip(self, self.with_context(lang=None)): view_wo_lang.arch = view.arch_base @api.depends('write_date') @@ -458,7 +460,7 @@ actual arch. # not required. The root cause is the INNER JOIN # used to implement it. views = self.search(conditions + [('model_ids.module', 'in', tuple(self.pool._init_modules))]) - views = self.search(conditions + [('id', 'in', list(self._context.get('check_view_ids') or (0,)) + map(int, views))]) + views = self.search(conditions + [('id', 'in', list(self._context.get('check_view_ids') or (0,)) + views.ids)]) else: views = self.search(conditions) @@ -581,10 +583,16 @@ actual arch. separator = child.get('separator', ',') if separator == ' ': separator = None # squash spaces - to_add = filter(bool, map(str.strip, child.get('add', '').split(separator))) - to_remove = map(str.strip, child.get('remove', '').split(separator)) - values = map(str.strip, node.get(attribute, '').split(separator)) - value = (separator or ' ').join(filter(lambda s: s not in to_remove, values) + to_add) + to_add = ( + s for s in (s.strip() for s in child.get('add', '').split(separator)) + if s + ) + to_remove = {s.strip() for s in child.get('remove', '').split(separator)} + values = (s.strip() for s in node.get(attribute, '').split(separator)) + value = (separator or ' ').join(itertools.chain( + (v for v in values if v not in to_remove), + to_add + )) if value: node.set(attribute, value) elif attribute in node.attrib: @@ -834,7 +842,7 @@ actual arch. collect(arch, self.env[model_name]) - for field, nodes in field_nodes.iteritems(): + for field, nodes in pycompat.items(field_nodes): # if field should trigger an onchange, add on_change="1" on the # nodes referring to field model = self.env[field.model_name] @@ -900,7 +908,7 @@ actual arch. node.set(action, 'false') arch = etree.tostring(node, encoding="utf-8").replace('\t', '') - for k in fields.keys(): + for k in list(fields): if k not in fields_def: del fields[k] for field in fields_def: @@ -924,7 +932,7 @@ actual arch. @tools.conditional( 'xml' not in config['dev_mode'], tools.ormcache('frozenset(self.env.user.groups_id.ids)', 'view_id', - 'tuple(map(self._context.get, self._read_template_keys()))'), + 'tuple(self._context.get(k) for k in self._read_template_keys())'), ) def _read_template(self, view_id): arch = self.browse(view_id).read_combined(['arch'])['arch'] @@ -1091,12 +1099,12 @@ actual arch. Model = self.env[model] Node = self.env[node_obj] - for model_key, model_value in Model._fields.iteritems(): + for model_key, model_value in pycompat.items(Model._fields): if model_value.type == 'one2many': if model_value.comodel_name == node_obj: _Node_Field = model_key _Model_Field = model_value.inverse_name - for node_key, node_value in Node._fields.iteritems(): + for node_key, node_value in pycompat.items(Node._fields): if node_value.type == 'one2many': if node_value.comodel_name == conn_obj: # _Source_Field = "Incoming Arrows" (connected via des_node) @@ -1159,7 +1167,7 @@ actual arch. GROUP BY coalesce(v.inherit_id, v.id)""" self._cr.execute(query, [model]) - rec = self.browse(map(itemgetter(0), self._cr.fetchall())) + rec = self.browse(it[0] for it in self._cr.fetchall()) return rec.with_context({'load_all_views': True})._check_xml() @api.model @@ -1173,7 +1181,7 @@ actual arch. xmlid_filter = "AND md.name IN %s" names = tuple( name - for (xmod, name), (model, res_id) in self.pool.model_data_reference_ids.items() + for (xmod, name), (model, res_id) in pycompat.items(self.pool.model_data_reference_ids) if xmod == module and model == self._name ) if not names: diff --git a/odoo/addons/base/ir/ir_values.py b/odoo/addons/base/ir/ir_values.py index 3672d7811c21..ace2998b2b31 100644 --- a/odoo/addons/base/ir/ir_values.py +++ b/odoo/addons/base/ir/ir_values.py @@ -5,7 +5,7 @@ from ast import literal_eval from odoo import api, fields, models, tools, _ from odoo.exceptions import AccessError, MissingError, ValidationError -from odoo.tools import pickle +from odoo.tools import pickle, pycompat import logging _logger = logging.getLogger(__name__) @@ -315,7 +315,7 @@ class IrValues(models.Model): for row in self._cr.dictfetchall(): value = pickle.loads(row['value'].encode('utf-8')) defaults.setdefault(row['name'], (row['id'], row['name'], value)) - return defaults.values() + return list(pycompat.values(defaults)) # use ormcache: this is called a lot by BaseModel.default_get()! @api.model @@ -426,4 +426,4 @@ class IrValues(models.Model): results[name] = (id, name, action_def) except (AccessError, MissingError): continue - return sorted(results.values()) + return sorted(pycompat.values(results)) diff --git a/odoo/addons/base/module/module.py b/odoo/addons/base/module/module.py index ae36ccd17277..c8a83a01bf1c 100644 --- a/odoo/addons/base/module/module.py +++ b/odoo/addons/base/module/module.py @@ -10,6 +10,8 @@ import shutil import tempfile import zipfile +from odoo.tools import pycompat + try: from urllib import parse as urlparse from urllib.request import urlopen @@ -209,9 +211,9 @@ class Module(models.Model): def format_view(v): return '%s%s (%s)' % (v.inherit_id and '* INHERIT ' or '', v.name, v.type) - module.views_by_module = "\n".join(sorted(map(format_view, browse('ir.ui.view')))) - module.reports_by_module = "\n".join(sorted(map(attrgetter('name'), browse('ir.actions.report')))) - module.menus_by_module = "\n".join(sorted(map(attrgetter('complete_name'), browse('ir.ui.menu')))) + module.views_by_module = "\n".join(sorted(format_view(v) for v in browse('ir.ui.view'))) + module.reports_by_module = "\n".join(sorted(r.name for r in browse('ir.actions.report'))) + module.menus_by_module = "\n".join(sorted(m.complete_name for m in browse('ir.ui.menu'))) @api.depends('icon') def _get_icon_image(self): @@ -668,7 +670,7 @@ class Module(models.Model): _logger.debug('Install from url: %r', urls) try: # 1. Download & unzip missing modules - for module_name, url in urls.iteritems(): + for module_name, url in pycompat.items(urls): if not url: continue # nothing to download, local version is already the last one @@ -687,7 +689,7 @@ class Module(models.Model): assert os.path.isdir(os.path.join(tmp, module_name)) # 2a. Copy/Replace module source in addons path - for module_name, url in urls.iteritems(): + for module_name, url in pycompat.items(urls): if module_name == OPENERP or not url: continue # OPENERP is special case, handled below, and no URL means local module module_path = modules.get_module_path(module_name, downloaded=True, display_warning=False) @@ -719,11 +721,11 @@ class Module(models.Model): self.update_list() - with_urls = [module_name for module_name, url in urls.iteritems() if url] + with_urls = [module_name for module_name, url in pycompat.items(urls) if url] downloaded = self.search([('name', 'in', with_urls)]) installed = self.search([('id', 'in', downloaded.ids), ('state', '=', 'installed')]) - to_install = self.search([('name', 'in', urls.keys()), ('state', '=', 'uninstalled')]) + to_install = self.search([('name', 'in', list(urls)), ('state', '=', 'uninstalled')]) post_install_action = to_install.button_immediate_install() if installed or to_install: diff --git a/odoo/addons/base/module/report/ir_module_reference_print.py b/odoo/addons/base/module/report/ir_module_reference_print.py index 1f2dff53243b..a933cdd94307 100644 --- a/odoo/addons/base/module/report/ir_module_reference_print.py +++ b/odoo/addons/base/module/report/ir_module_reference_print.py @@ -2,6 +2,8 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, models +from odoo.tools import pycompat + class IrModelReferenceReport(models.AbstractModel): _name = 'report.base.report_irmodulereference' @@ -21,7 +23,7 @@ class IrModelReferenceReport(models.AbstractModel): if data: res_ids = data.mapped('res_id') fnames = self.env['ir.model.fields'].browse(res_ids).mapped('name') - return sorted(self.env[model].fields_get(fnames).iteritems()) + return sorted(pycompat.items(self.env[model].fields_get(fnames))) return [] @api.model diff --git a/odoo/addons/base/res/ir_property.py b/odoo/addons/base/res/ir_property.py index 8e1b7eda860c..ec086fb6c61e 100644 --- a/odoo/addons/base/res/ir_property.py +++ b/odoo/addons/base/res/ir_property.py @@ -217,7 +217,7 @@ class Property(models.Model): prop.write({'value': value}) # create new properties for records that do not have one yet - for ref, id in refs.iteritems(): + for ref, id in pycompat.items(refs): value = clean(values[id]) if value != default_value: self.create({ @@ -248,13 +248,13 @@ class Property(models.Model): elif operator in ('!=', '<=', '<', '>', '>='): value = makeref(value) elif operator in ('in', 'not in'): - value = map(makeref, value) + value = [makeref(v) for v in value] elif operator in ('=like', '=ilike', 'like', 'not like', 'ilike', 'not ilike'): # most probably inefficient... but correct target = self.env[comodel] target_names = target.name_search(value, operator=operator, limit=None) - target_ids = map(itemgetter(0), target_names) - operator, value = 'in', map(makeref, target_ids) + target_ids = [n[0] for n in target_names] + operator, value = 'in', [makeref(v) for v in target_ids] elif field.type in ('integer', 'float'): # No record is created in ir.property if the field's type is float or integer with a value # equal to 0. Then to match with the records that are linked to a property field equal to 0, diff --git a/odoo/addons/base/res/res_company.py b/odoo/addons/base/res/res_company.py index 96e922f5e773..9dfdba96cba9 100644 --- a/odoo/addons/base/res/res_company.py +++ b/odoo/addons/base/res/res_company.py @@ -6,6 +6,7 @@ import re from odoo import api, fields, models, tools, _ from odoo.exceptions import ValidationError +from odoo.tools import pycompat class Company(models.Model): @@ -146,7 +147,7 @@ class Company(models.Model): if self.country_id: res['domain']['state_id'] = [('country_id', '=', self.country_id.id)] values = self.on_change_country(self.country_id.id)['value'] - for fname, value in values.iteritems(): + for fname, value in pycompat.items(values): setattr(self, fname, value) return res diff --git a/odoo/addons/base/res/res_config.py b/odoo/addons/base/res/res_config.py index 59764fca1e82..a19c6406dcba 100644 --- a/odoo/addons/base/res/res_config.py +++ b/odoo/addons/base/res/res_config.py @@ -9,7 +9,7 @@ from lxml import etree from odoo import api, models, registry, SUPERUSER_ID, _ from odoo.exceptions import AccessError, RedirectWarning, UserError -from odoo.tools import ustr +from odoo.tools import ustr, pycompat _logger = logging.getLogger(__name__) @@ -257,7 +257,7 @@ class ResConfigInstaller(models.TransientModel, ResConfigModuleInstallationMixin installer :rtype: [str] """ - return map(attrgetter('name'), self._already_installed()) + return [m.name for m in self._already_installed()] def _already_installed(self): """ For each module (boolean fields in a res.config.installer), @@ -267,7 +267,7 @@ class ResConfigInstaller(models.TransientModel, ResConfigModuleInstallationMixin :returns: a list of all installed modules in this installer :rtype: recordset (collection of Record) """ - selectable = [name for name, field in self._fields.iteritems() + selectable = [name for name, field in pycompat.items(self._fields) if field.type == 'boolean'] return self.env['ir.module.module'].search([('name', 'in', selectable), ('state', 'in', ['to install', 'installed', 'to upgrade'])]) @@ -292,7 +292,7 @@ class ResConfigInstaller(models.TransientModel, ResConfigModuleInstallationMixin """ base = set(module_name for installer in self.read() - for module_name, to_install in installer.iteritems() + for module_name, to_install in pycompat.items(installer) if self._fields[module_name].type == 'boolean' and to_install) hooks_results = set() @@ -302,7 +302,7 @@ class ResConfigInstaller(models.TransientModel, ResConfigModuleInstallationMixin hooks_results.update(hook() or set()) additionals = set(module - for requirements, consequences in self._install_if.iteritems() + for requirements, consequences in pycompat.items(self._install_if) if base.issuperset(requirements) for module in consequences) @@ -462,13 +462,13 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) ref = self.env.ref defaults, groups, modules, others = [], [], [], [] - for name, field in self._fields.iteritems(): + for name, field in pycompat.items(self._fields): if name.startswith('default_') and hasattr(field, 'default_model'): defaults.append((name, field.default_model, name[8:])) elif name.startswith('group_') and field.type in ('boolean', 'selection') and \ hasattr(field, 'implied_group'): field_group_xmlids = getattr(field, 'group', 'base.group_user').split(',') - field_groups = Groups.concat(*map(ref, field_group_xmlids)) + field_groups = Groups.concat(*(ref(it) for it in field_group_xmlids)) groups.append((name, field_groups, ref(field.implied_group))) elif name.startswith('module_') and field.type in ('boolean', 'selection'): module = IrModule.sudo().search([('name', '=', name[7:])], limit=1) diff --git a/odoo/addons/base/res/res_lang.py b/odoo/addons/base/res/res_lang.py index 19a5b9be3396..f4e2d7a15780 100644 --- a/odoo/addons/base/res/res_lang.py +++ b/odoo/addons/base/res/res_lang.py @@ -8,6 +8,7 @@ import re from operator import itemgetter from odoo import api, fields, models, tools, _ +from odoo.tools import pycompat from odoo.tools.safe_eval import safe_eval from odoo.exceptions import UserError, ValidationError @@ -22,7 +23,7 @@ class Lang(models.Model): _description = "Languages" _order = "active desc,name" - _disallowed_datetime_patterns = tools.DATETIME_FORMATS_MAP.keys() + _disallowed_datetime_patterns = list(tools.DATETIME_FORMATS_MAP) _disallowed_datetime_patterns.remove('%y') # this one is in fact allowed, just not good practice name = fields.Char(required=True) @@ -125,7 +126,7 @@ class Lang(models.Model): # For some locales, nl_langinfo returns a D_FMT/T_FMT that contains # unsupported '%-' patterns, e.g. for cs_CZ format = format.replace('%-', '%') - for pattern, replacement in tools.DATETIME_FORMATS_MAP.iteritems(): + for pattern, replacement in pycompat.items(tools.DATETIME_FORMATS_MAP): format = format.replace(pattern, replacement) return str(format) @@ -318,5 +319,5 @@ def intersperse(string, counts, separator=''): left, rest, right = intersperse_pat.match(string).groups() def reverse(s): return s[::-1] splits = split(reverse(rest), counts) - res = separator.join(map(reverse, reverse(splits))) + res = separator.join(reverse(s) for s in reverse(splits)) return left + res + right, len(splits) > 0 and len(splits) -1 or 0 diff --git a/odoo/addons/base/res/res_partner.py b/odoo/addons/base/res/res_partner.py index fe758d36de12..17e6507a01e8 100644 --- a/odoo/addons/base/res/res_partner.py +++ b/odoo/addons/base/res/res_partner.py @@ -680,7 +680,7 @@ class Partner(models.Model): query += ' limit %s' where_clause_params.append(limit) self.env.cr.execute(query, where_clause_params) - partner_ids = map(lambda x: x[0], self.env.cr.fetchall()) + partner_ids = [row[0] for row in self.env.cr.fetchall()] if partner_ids: return self.browse(partner_ids).name_get() diff --git a/odoo/addons/base/res/res_users.py b/odoo/addons/base/res/res_users.py index ae8863eee4d7..106fc32d56fc 100644 --- a/odoo/addons/base/res/res_users.py +++ b/odoo/addons/base/res/res_users.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import pytz import datetime +import itertools import logging from collections import defaultdict @@ -13,7 +14,7 @@ from odoo import api, fields, models, tools, SUPERUSER_ID, _ from odoo.exceptions import AccessDenied, AccessError, UserError, ValidationError from odoo.osv import expression from odoo.service.db import check_super -from odoo.tools import partition +from odoo.tools import partition, pycompat _logger = logging.getLogger(__name__) @@ -29,7 +30,7 @@ def name_boolean_group(id): return 'in_group_' + str(id) def name_selection_groups(ids): - return 'sel_groups_' + '_'.join(map(str, ids)) + return 'sel_groups_' + '_'.join(str(it) for it in ids) def is_boolean_group(name): return name.startswith('in_group_') @@ -44,7 +45,7 @@ def get_boolean_group(name): return int(name[9:]) def get_selection_groups(name): - return map(int, name[11:].split('_')) + return [int(v) for v in name[11:].split('_')] def parse_m2m(commands): "return a list of ids corresponding to a many2many value" @@ -92,7 +93,7 @@ class Groups(models.Model): @api.depends('category_id.name', 'name') def _compute_full_name(self): # Important: value must be stored in environment of group, not group1! - for group, group1 in zip(self, self.sudo()): + for group, group1 in pycompat.izip(self, self.sudo()): if group1.category_id: group.full_name = '%s / %s' % (group1.category_id.name, group1.name) else: @@ -111,7 +112,7 @@ class Groups(models.Model): operand = [operand] where = [] for group in operand: - values = filter(bool, group.split('/')) + values = [v for v in group.split('/') if v] group_name = values.pop().strip() category_name = values and '/'.join(values).strip() or group_name group_domain = [('name', operator, lst and [group_name] or group_name)] @@ -299,13 +300,11 @@ class Users(models.Model): canwrite = self.env['ir.model.access'].check('res.users', 'write', False) if not canwrite: - def override_password(vals): - if (vals['id'] != self._uid): + for vals in result: + if vals['id'] != self._uid: for key in USER_PRIVATE_FIELDS: if key in vals: vals[key] = '********' - return vals - result = map(override_password, result) return result @@ -343,7 +342,7 @@ class Users(models.Model): raise UserError(_("You cannot deactivate the user you're currently logged in as.")) if self == self.env.user: - for key in values.keys(): + for key in list(values): if not (key in self.SELF_WRITEABLE_FIELDS or key.startswith('context_')): break else: @@ -624,7 +623,7 @@ class GroupsImplied(models.Model): if values.get('users') or values.get('implied_ids'): # add all implied groups (to all users of each group) for group in self: - vals = {'users': zip(repeat(4), group.with_context(active_test=False).users.ids)} + vals = {'users': list(pycompat.izip(repeat(4), group.with_context(active_test=False).users.ids))} super(GroupsImplied, group.trans_implied_ids).write(vals) return res @@ -766,7 +765,7 @@ class GroupsView(models.Model): # determine sequence order: a group appears after its implied groups order = {g: len(g.trans_implied_ids & gs) for g in gs} # check whether order is total, i.e., sequence orders are distinct - if len(set(order.itervalues())) == len(gs): + if len(set(pycompat.values(order))) == len(gs): return (app, 'selection', gs.sorted(key=order.get)) else: return (app, 'boolean', gs) @@ -780,7 +779,7 @@ class GroupsView(models.Model): others += g # build the result res = [] - for app, gs in sorted(by_app.iteritems(), key=lambda it: it[0].sequence or 0): + for app, gs in sorted(pycompat.items(by_app), key=lambda it: it[0].sequence or 0): res.append(linearize(app, gs)) if others: res.append((self.env['ir.module.category'], 'boolean', others)) @@ -820,7 +819,7 @@ class UsersView(models.Model): add, rem = [], [] values1 = {} - for key, val in values.iteritems(): + for key, val in pycompat.items(values): if is_boolean_group(key): (add if val else rem).append(get_boolean_group(key)) elif is_selection_groups(key): @@ -832,7 +831,10 @@ class UsersView(models.Model): if 'groups_id' not in values and (add or rem): # remove group ids in `rem` and add group ids in `add` - values1['groups_id'] = zip(repeat(3), rem) + zip(repeat(4), add) + values1['groups_id'] = list(itertools.chain( + pycompat.izip(repeat(3), rem), + pycompat.izip(repeat(4), add) + )) return values1 @@ -847,7 +849,7 @@ class UsersView(models.Model): @api.multi def read(self, fields=None, load='_classic_read'): # determine whether reified groups fields are required, and which ones - fields1 = fields or self.fields_get().keys() + fields1 = fields or list(self.fields_get()) group_fields, other_fields = partition(is_reified_group, fields1) # read regular fields (other_fields); add 'groups_id' if necessary diff --git a/odoo/addons/base/tests/test_api.py b/odoo/addons/base/tests/test_api.py index 8270d6086d52..55cf1ff26b50 100644 --- a/odoo/addons/base/tests/test_api.py +++ b/odoo/addons/base/tests/test_api.py @@ -79,11 +79,11 @@ class TestAPI(common.TransactionCase): domain = [('name', 'ilike', 'j')] partners = self.env['res.partner'].search(domain) self.assertTrue(partners) - ids = map(int, partners) + ids = partners.ids # modify those partners, and check that partners has not changed partners.write({'active': False}) - self.assertEqual(ids, map(int, partners)) + self.assertEqual(ids, partners.ids) # redo the search, and check that the result is now empty partners2 = self.env['res.partner'].search(domain) @@ -98,7 +98,7 @@ class TestAPI(common.TransactionCase): self.assertIsRecordset(user.groups_id, 'res.groups') partners = self.env['res.partner'].search([]) - for name, field in partners._fields.iteritems(): + for name, field in pycompat.items(partners._fields): if field.type == 'many2one': for p in partners: self.assertIsRecord(p[name], field.comodel_name) @@ -296,7 +296,7 @@ class TestAPI(common.TransactionCase): self.assertItemsEqual(partners.ids, country_id_cache) # partners' countries are ready for prefetching - country_ids = set(cid for cids in country_id_cache.itervalues() for cid in cids) + country_ids = set(cid for cids in pycompat.values(country_id_cache) for cid in cids) self.assertTrue(len(country_ids) > 1) self.assertItemsEqual(country_ids, partners._prefetch['res.country']) diff --git a/odoo/addons/base/tests/test_expression.py b/odoo/addons/base/tests/test_expression.py index 4dea4797b0ce..87099e78754a 100644 --- a/odoo/addons/base/tests/test_expression.py +++ b/odoo/addons/base/tests/test_expression.py @@ -5,7 +5,7 @@ import psycopg2 from odoo.models import BaseModel from odoo.tests.common import TransactionCase -from odoo.tools import mute_logger +from odoo.tools import mute_logger, pycompat import odoo.osv.expression as expression @@ -85,10 +85,10 @@ class TestExpression(TransactionCase): 'b ab': [cids['B'], cids['AB']], } pids = {} - for name, cat_ids in partners_config.iteritems(): + for name, cat_ids in pycompat.items(partners_config): pids[name] = partners.create({'name': name, 'category_id': [(6, 0, cat_ids)]}).id - base_domain = [('id', 'in', pids.values())] + base_domain = [('id', 'in', list(pycompat.values(pids)))] def test(op, value, expected): found_ids = partners.search(base_domain + [('category_id', op, value)]).ids diff --git a/odoo/addons/base/tests/test_float.py b/odoo/addons/base/tests/test_float.py index 8c4ee380bd25..725ba48365b2 100644 --- a/odoo/addons/base/tests/test_float.py +++ b/odoo/addons/base/tests/test_float.py @@ -98,10 +98,10 @@ class TestFloatPrecision(TransactionCase): precisions = [2, 2, 2, 2, 2, 2, 3, 4] # Note: max precision for double floats is 53 bits of precision or # 17 significant decimal digits - for magnitude in pycompat.range(7): - for frac, exp, prec in zip(fractions, expecteds, precisions): + for magnitude in range(7): + for frac, exp, prec in pycompat.izip(fractions, expecteds, precisions): for sign in [-1,1]: - for x in pycompat.range(0, 10000, 97): + for x in range(0, 10000, 97): n = x * 10 ** magnitude f = sign * (n + frac) f_exp = ('-' if f != 0 and sign == -1 else '') + str(n) + exp diff --git a/odoo/addons/base/tests/test_ir_filters.py b/odoo/addons/base/tests/test_ir_filters.py index d932f16be11e..b8de7590ad27 100644 --- a/odoo/addons/base/tests/test_ir_filters.py +++ b/odoo/addons/base/tests/test_ir_filters.py @@ -4,11 +4,12 @@ from odoo import exceptions from odoo.tests.common import TransactionCase, ADMIN_USER_ID -def noid(d): +def noid(seq): """ Removes values that are not relevant for the test comparisons """ - d.pop('id', None) - d.pop('action_id', None) - return d + for d in seq: + d.pop('id', None) + d.pop('action_id', None) + return seq class FiltersCase(TransactionCase): @@ -34,7 +35,7 @@ class TestGetFilters(FiltersCase): filters = self.env['ir.filters'].sudo(self.USER_ID).get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'), dict(name='b', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'), dict(name='c', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'), @@ -52,7 +53,7 @@ class TestGetFilters(FiltersCase): filters = self.env['ir.filters'].sudo(self.USER_ID).get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', is_default=False, user_id=False, domain='[]', context='{}', sort='[]'), dict(name='b', is_default=False, user_id=False, domain='[]', context='{}', sort='[]'), dict(name='c', is_default=False, user_id=False, domain='[]', context='{}', sort='[]'), @@ -69,7 +70,7 @@ class TestGetFilters(FiltersCase): filters = self.env['ir.filters'].sudo(self.USER_ID).get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', is_default=False, user_id=False, domain='[]', context='{}', sort='[]'), dict(name='c', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'), ]) @@ -95,7 +96,7 @@ class TestOwnDefaults(FiltersCase): }) filters = Filters.get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', user_id=self.USER_NG, is_default=True, domain='[]', context='{}', sort='[]') ]) @@ -120,7 +121,7 @@ class TestOwnDefaults(FiltersCase): }) filters = Filters.get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='b', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='c', user_id=self.USER_NG, is_default=True, domain='[]', context='{}', sort='[]'), @@ -146,7 +147,7 @@ class TestOwnDefaults(FiltersCase): }) filters = Filters.get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='b', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='c', user_id=self.USER_NG, is_default=True, domain='[]', context='{}', sort='[]'), @@ -172,7 +173,7 @@ class TestOwnDefaults(FiltersCase): }) filters = Filters.get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', user_id=self.USER_NG, is_default=True, domain='[]', context='{}', sort='[]'), dict(name='b', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'), ]) @@ -204,7 +205,7 @@ class TestGlobalDefaults(FiltersCase): }) filters = Filters.get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', user_id=False, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='b', user_id=False, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='c', user_id=False, is_default=True, domain='[]', context='{}', sort='[]'), @@ -271,7 +272,7 @@ class TestGlobalDefaults(FiltersCase): }) filters = Filters.get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', user_id=False, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='b', user_id=False, is_default=True, domain='[]', context=context_value, sort='[]'), ]) diff --git a/odoo/addons/base/tests/test_orm.py b/odoo/addons/base/tests/test_orm.py index 0da71a84ad51..bf95bee08f7c 100644 --- a/odoo/addons/base/tests/test_orm.py +++ b/odoo/addons/base/tests/test_orm.py @@ -141,7 +141,7 @@ class TestORM(TransactionCase): partner_ids_by_year = defaultdict(list) partners = self.env['res.partner'] - for name, date in partners_data.items(): + for name, date in pycompat.items(partners_data): p = partners.create(dict(name=name, date=date)) partner_ids.append(p.id) partner_ids_by_day[date].append(p.id) @@ -297,7 +297,7 @@ class TestO2MSerialization(TransactionCase): def test_CREATE_commands(self): " returns the VALUES dict as-is " values = [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}] - results = self.env['res.partner'].resolve_2many_commands('child_ids', map(CREATE, values)) + results = self.env['res.partner'].resolve_2many_commands('child_ids', [CREATE(v) for v in values]) self.assertEqual(results, values) def test_LINK_TO_command(self): @@ -307,7 +307,7 @@ class TestO2MSerialization(TransactionCase): self.env['res.partner'].create({'name': 'bar'}).id, self.env['res.partner'].create({'name': 'baz'}).id, ] - commands = map(LINK_TO, ids) + commands = [LINK_TO(v) for v in ids] results = self.env['res.partner'].resolve_2many_commands('child_ids', commands, ['name']) self.assertItemsEqual(results, [ @@ -356,7 +356,7 @@ class TestO2MSerialization(TransactionCase): self.env['res.partner'].create({'name': 'bar'}).id, self.env['res.partner'].create({'name': 'baz'}).id, ] - commands = map(DELETE, ids) + commands = [DELETE(v) for v in ids] results = self.env['res.partner'].resolve_2many_commands('child_ids', commands, ['name']) self.assertEqual(results, []) diff --git a/odoo/addons/base/tests/test_qweb.py b/odoo/addons/base/tests/test_qweb.py index dcf41b092d65..cd459fe7669f 100644 --- a/odoo/addons/base/tests/test_qweb.py +++ b/odoo/addons/base/tests/test_qweb.py @@ -13,6 +13,7 @@ from itertools import chain from odoo.modules import get_module_resource from odoo.tests.common import TransactionCase from odoo.addons.base.ir.ir_qweb import QWebException +from odoo.tools import pycompat def dedent_and_strip(string): @@ -411,7 +412,7 @@ class TestQWebNS(TransactionCase): 'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2', 'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2' } - self.assertSetEqual(set(expected_ns.items()) - set(result_etree.nsmap.items()), set()) + self.assertSetEqual(set(pycompat.items(expected_ns)) - set(pycompat.items(result_etree.nsmap)), set()) # check that the t-call did its work cac_lines = result_etree.findall('.//cac:line', namespaces={'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2'}) diff --git a/odoo/addons/base/tests/test_search.py b/odoo/addons/base/tests/test_search.py index d52cd9db9c46..97670fdd805f 100644 --- a/odoo/addons/base/tests/test_search.py +++ b/odoo/addons/base/tests/test_search.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.tests.common import TransactionCase +from odoo.tools import pycompat class test_search(TransactionCase): @@ -107,7 +108,7 @@ class test_search(TransactionCase): user_ids[u] = Users.create({'name': u, 'login': u}).id cron_ids[u] = Cron.create({'name': u, 'model_id': self.env.ref('base.model_res_partner').id, 'user_id': user_ids[u]}).id - ids = Cron.search([('id', 'in', cron_ids.values())], order='user_id').ids + ids = Cron.search([('id', 'in', list(pycompat.values(cron_ids)))], order='user_id').ids expected_ids = [cron_ids[l] for l in 'ABC'] self.assertEqual(ids, expected_ids) @@ -127,7 +128,7 @@ class test_search(TransactionCase): create('F', parent_id=cat_ids['D']) expected_ids = [cat_ids[x] for x in 'ADEFBC'] - found_ids = Cats.search([('id', 'in', cat_ids.values())]).ids + found_ids = Cats.search([('id', 'in', list(pycompat.values(cat_ids)))]).ids self.assertEqual(found_ids, expected_ids) def test_13_m2o_order_loop_multi(self): diff --git a/odoo/addons/base/tests/test_views.py b/odoo/addons/base/tests/test_views.py index bd462932e5f2..40cd922340ce 100644 --- a/odoo/addons/base/tests/test_views.py +++ b/odoo/addons/base/tests/test_views.py @@ -638,7 +638,7 @@ class TestViews(ViewCase): kw.setdefault('mode', 'extension' if kw.get('inherit_id') else 'primary') kw.setdefault('active', True) - keys = sorted(kw.keys()) + keys = sorted(kw) fields = ','.join('"%s"' % (k.replace('"', r'\"'),) for k in keys) params = ','.join('%%(%s)s' % (k,) for k in keys) diff --git a/odoo/addons/test_impex/tests/test_export.py b/odoo/addons/test_impex/tests/test_export.py index 226ba9943f64..72a3b2a6f754 100644 --- a/odoo/addons/test_impex/tests/test_export.py +++ b/odoo/addons/test_impex/tests/test_export.py @@ -4,6 +4,7 @@ import itertools from odoo.tests import common +from odoo.tools import pycompat class CreatorCase(common.TransactionCase): @@ -494,9 +495,9 @@ class test_o2m_multiple(CreatorCase): """ fields = ['const', 'child1/value', 'child2/value'] child1 = [(0, False, {'value': v, 'str': 'record%.02d' % index}) - for index, v in zip(itertools.count(), [4, 42, 36, 4, 13])] + for index, v in pycompat.izip(itertools.count(), [4, 42, 36, 4, 13])] child2 = [(0, False, {'value': v, 'str': 'record%.02d' % index}) - for index, v in zip(itertools.count(10), [8, 12, 8, 55, 33, 13])] + for index, v in pycompat.izip(itertools.count(10), [8, 12, 8, 55, 33, 13])] self.assertEqual( self.export(child1=child1, child2=False, fields=fields), diff --git a/odoo/addons/test_limits/models.py b/odoo/addons/test_limits/models.py index 506578a811b3..957e3c3d6f92 100644 --- a/odoo/addons/test_limits/models.py +++ b/odoo/addons/test_limits/models.py @@ -5,7 +5,6 @@ import time import sys from odoo import models, api -from odoo.tools import pycompat class m(models.Model): """ This model exposes a few methods that will consume between 'almost no @@ -39,7 +38,7 @@ class m(models.Model): t0 = time.clock() t1 = time.clock() while t1 - t0 < seconds: - for i in pycompat.range(10000000): + for i in range(10000000): x = i * i t1 = time.clock() return True diff --git a/odoo/addons/test_new_api/models.py b/odoo/addons/test_new_api/models.py index c84827d47ceb..2f288e89f744 100644 --- a/odoo/addons/test_new_api/models.py +++ b/odoo/addons/test_new_api/models.py @@ -5,6 +5,7 @@ import datetime from odoo import models, fields, api, _ from odoo.exceptions import AccessError, ValidationError +from odoo.tools import pycompat class Category(models.Model): @@ -45,7 +46,7 @@ class Category(models.Model): categories.append(category[0]) categories.append(self) # assign parents following sequence - for parent, child in zip(categories, categories[1:]): + for parent, child in pycompat.izip(categories, categories[1:]): if parent and child: child.parent = parent # assign name of last category, and reassign display_name (to normalize it) @@ -364,4 +365,4 @@ class ComputeRecursive(models.Model): if rec.parent: rec.display_name = rec.parent.display_name + " / " + rec.name else: - rec.display_name = rec.name \ No newline at end of file + rec.display_name = rec.name diff --git a/odoo/addons/test_new_api/tests/test_one2many.py b/odoo/addons/test_new_api/tests/test_one2many.py index 9ade2c0fe193..e1ad547e95ea 100644 --- a/odoo/addons/test_new_api/tests/test_one2many.py +++ b/odoo/addons/test_new_api/tests/test_one2many.py @@ -45,7 +45,7 @@ class One2manyCase(TransactionCase): # Check the lines first self.assertItemsEqual( self.multi.lines.mapped('name'), - map(str, range(10))) + [str(i) for i in range(10)]) # Modify the first line and drop the last one self.multi.lines[0].name = "hello" self.multi.lines = self.multi.lines[:-1] diff --git a/odoo/addons/test_pylint/tests/test_pylint.py b/odoo/addons/test_pylint/tests/test_pylint.py index 1ae544e56a68..f98dcf88b3c6 100644 --- a/odoo/addons/test_pylint/tests/test_pylint.py +++ b/odoo/addons/test_pylint/tests/test_pylint.py @@ -30,22 +30,33 @@ class TestPyLint(TransactionCase): 'relative-import', 'deprecated-module', 'import-star-module-level', - # 'bad-python3-import', # TODO: more stuff used in report + + 'bad-builtin', + + 'dict-iter-method', + 'dict-view-method', 'long-suffix', - 'apply-builtin', - 'cmp-builtin', - 'coerce-builtin', - 'execfile-builtin', - 'input-builtin', - 'intern-builtin', - 'long-builtin', - 'raw_input-builtin', - 'reload-builtin', - 'xrange-builtin', + ] + + BAD_FUNCTIONS = [ + 'apply', + 'cmp', + 'coerce', + 'execfile', + 'input', + 'intern', + 'long', + 'raw_input', + 'reload', + 'xrange', + 'long', + 'map', + 'filter', + 'zip', # TODO: enable once report has been removed - # 'file-builtin', - # 'reduce-builtin', + # 'file', + # 'reduce', ] def _skip_test(self, reason): @@ -69,6 +80,8 @@ class TestPyLint(TransactionCase): '--enable=%s' % ','.join(self.ENABLED_CODES), '--reports=n', "--msg-template='{msg} ({msg_id}) at {path}:{line}'", + '--load-plugins=pylint.extensions.bad_builtin', + '--bad-functions=%s' % ','.join(self.BAD_FUNCTIONS), ] try: diff --git a/odoo/api.py b/odoo/api.py index 1adbd08f6e5d..dc51870f20a6 100644 --- a/odoo/api.py +++ b/odoo/api.py @@ -55,7 +55,7 @@ from weakref import WeakSet from decorator import decorator from werkzeug.local import Local, release_local -from odoo.tools import frozendict, classproperty +from odoo.tools import frozendict, classproperty, pycompat _logger = logging.getLogger(__name__) @@ -85,7 +85,7 @@ class Params(object): params = [] for arg in self.args: params.append(repr(arg)) - for item in sorted(self.kwargs.iteritems()): + for item in sorted(pycompat.items(self.kwargs)): params.append("%s=%r" % item) return ', '.join(params) @@ -100,7 +100,7 @@ class Meta(type): # dummy parent class to catch overridden methods decorated with 'returns' parent = type.__new__(meta, name, bases, {}) - for key, value in attrs.items(): + for key, value in list(pycompat.items(attrs)): if not key.startswith('__') and callable(value): # make the method inherit from decorators value = propagate(getattr(parent, key, None), value) @@ -916,7 +916,7 @@ class Environment(Mapping): def remove_todo(self, field, records): """ Mark ``field`` as recomputed on ``records``. """ recs_list = [recs - records for recs in self.all.todo.pop(field, [])] - recs_list = filter(None, recs_list) + recs_list = [r for r in recs_list if r] if recs_list: self.all.todo[field] = recs_list @@ -938,15 +938,14 @@ class Environment(Mapping): # make a full copy of the cache, and invalidate it cache_dump = dict( (field, dict(field_cache)) - for field, field_cache in self.cache.iteritems() + for field, field_cache in pycompat.items(self.cache) ) self.invalidate_all() # re-fetch the records, and compare with their former cache invalids = [] - for field, field_dump in cache_dump.iteritems(): - ids = filter(None, field_dump) - records = self[field.model_name].browse(ids) + for field, field_dump in pycompat.items(cache_dump): + records = self[field.model_name].browse(f for f in field_dump if f) for record in records: try: cached = field_dump[record.id] diff --git a/odoo/cli/command.py b/odoo/cli/command.py index 2ab042f58e80..503ffcb360a7 100644 --- a/odoo/cli/command.py +++ b/odoo/cli/command.py @@ -28,7 +28,7 @@ class Help(Command): """Display the list of available commands""" def run(self, args): print("Available commands:\n") - names = commands.keys() + names = list(commands) padding = max([len(k) for k in names]) + 2 for k in sorted(names): name = k.ljust(padding, ' ') diff --git a/odoo/fields.py b/odoo/fields.py index e200d7b32dca..8d5d865968b6 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -335,7 +335,7 @@ class Field(object): def __init__(self, string=Default, **kwargs): kwargs['string'] = string - args = {key: val for key, val in kwargs.iteritems() if val is not Default} + args = {key: val for key, val in pycompat.items(kwargs) if val is not Default} self.args = args or EMPTY_DICT self._setup_done = None @@ -364,7 +364,7 @@ class Field(object): """ Set all field attributes at once (with slot defaults). """ # optimization: we assign slots only assign = object.__setattr__ - for key, val in self._slots.iteritems(): + for key, val in pycompat.items(self._slots): assign(self, key, attrs.pop(key, val)) if attrs: assign(self, '_attrs', attrs) @@ -542,7 +542,7 @@ class Field(object): if not getattr(self, attr): setattr(self, attr, getattr(field, prop)) - for attr, value in field._attrs.iteritems(): + for attr, value in pycompat.items(field._attrs): if attr not in self._attrs: setattr(self, attr, value) @@ -565,7 +565,7 @@ class Field(object): """ Compute the related field ``self`` on ``records``. """ # when related_sudo, bypass access rights checks when reading values others = records.sudo() if self.related_sudo else records - for record, other in zip(records, others): + for record, other in pycompat.izip(records, others): if not record.id and record.env != other.env: # draft records: copy record's cache to other's cache first copy_cache(record, other.env) @@ -650,7 +650,7 @@ class Field(object): model = model0.env.get(field.comodel_name) # add self's model dependencies - for mname, fnames in model0._depends.iteritems(): + for mname, fnames in pycompat.items(model0._depends): model = model0.env[mname] for fname in fnames: field = model._fields[fname] @@ -1002,7 +1002,7 @@ class Field(object): # HACK: if result is in the wrong cache, copy values if recs.env != env: computed = record._field_computed[self] - for source, target in zip(recs, recs.with_env(env)): + for source, target in pycompat.izip(recs, recs.with_env(env)): try: values = target._convert_to_cache({ f.name: source[f.name] for f in computed @@ -1071,8 +1071,8 @@ class Field(object): for field, path in records._field_triggers[self]: bymodel[field.model_name][path].append(field) - for model_name, bypath in bymodel.iteritems(): - for path, fields in bypath.iteritems(): + for model_name, bypath in pycompat.items(bymodel): + for path, fields in pycompat.items(bypath): if path and any(field.compute and field.store for field in fields): # process stored fields stored = set(field for field in fields if field.compute and field.store) @@ -1755,7 +1755,7 @@ class Selection(Field): if 'selection_add' in field.args: # use an OrderedDict to update existing values selection_add = field.args['selection_add'] - self.selection = OrderedDict(self.selection + selection_add).items() + self.selection = list(pycompat.items(OrderedDict(self.selection + selection_add))) def _description_selection(self, env): """ return the selection list (pairs (value, label)); labels are @@ -1934,7 +1934,7 @@ class Many2one(_Relational): super(Many2one, self)._setup_attrs(model, name) # determine self.delegate if not self.delegate: - self.delegate = name in model._inherits.values() + self.delegate = name in pycompat.values(model._inherits) def update_db(self, model, columns): comodel = model.env[self.comodel_name] diff --git a/odoo/http.py b/odoo/http.py index 0b3ad105e399..fa5b44d1226e 100644 --- a/odoo/http.py +++ b/odoo/http.py @@ -52,7 +52,7 @@ import odoo from odoo.service.server import memory_info from odoo.service import security, model as service_model from odoo.tools.func import lazy_property -from odoo.tools import ustr, consteq, frozendict +from odoo.tools import ustr, consteq, frozendict, pycompat, unique from odoo.modules.module import module_manifest @@ -288,8 +288,8 @@ class WebRequest(object): def set_handler(self, endpoint, arguments, auth): # is this needed ? - arguments = dict((k, v) for k, v in arguments.iteritems() - if not k.startswith("_ignored_")) + arguments ={k: v for k, v in pycompat.items(arguments) + if not k.startswith("_ignored_")} self.endpoint_arguments = arguments self.endpoint = endpoint self.auth_method = auth @@ -837,7 +837,7 @@ more details. """ response = Response(data, headers=headers) if cookies: - for k, v in cookies.iteritems(): + for k, v in pycompat.items(cookies): response.set_cookie(k, v) return response @@ -878,7 +878,7 @@ class ControllerType(type): super(ControllerType, cls).__init__(name, bases, attrs) # flag old-style methods with req as first argument - for k, v in attrs.items(): + for k, v in pycompat.items(attrs): if inspect.isfunction(v) and hasattr(v, 'original_func'): # Set routing type on original functions routing_type = v.routing.get('type') @@ -941,14 +941,12 @@ def routing_map(modules, nodb_only, converters=None): result = [klass] return result - uniq = lambda it: collections.OrderedDict((id(x), x) for x in it).values() - for module in modules: if module not in controllers_per_module: continue for _, cls in controllers_per_module[module]: - subclasses = uniq(c for c in get_subclasses(cls) if c is not cls) + subclasses = list(unique(c for c in get_subclasses(cls) if c is not cls)) if subclasses: name = "%s (extended by %s)" % (cls.__name__, ', '.join(sub.__name__ for sub in subclasses)) cls = type(name, tuple(reversed(subclasses)), {}) @@ -1050,7 +1048,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): security.check(self.db, self.uid, self.password) def logout(self, keep_db=False): - for k in self.keys(): + for k in list(self): if not (keep_db and k == 'db'): del self[k] self._default_values() @@ -1137,7 +1135,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): # NOTE we do not store files in the session itself to avoid loading them in memory. # By storing them in the session store, we ensure every worker (even ones on other # servers) can access them. It also allow stale files to be deleted by `session_gc`. - for f in req.files.values(): + for f in pycompat.values(req.files): storename = 'werkzeug_%s_%s.file' % (self.sid, uuid.uuid4().hex) path = os.path.join(root.session_store.path, storename) with open(path, 'w') as fp: @@ -1155,7 +1153,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): try: if data: # regenerate files filenames with the current session store - for name, (storename, filename, content_type) in data['files'].iteritems(): + for name, (storename, filename, content_type) in pycompat.items(data['files']): path = os.path.join(root.session_store.path, storename) files.add(name, (path, filename, content_type)) yield werkzeug.datastructures.CombinedMultiDict([data['form'], files]) @@ -1163,7 +1161,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): yield None finally: # cleanup files - for f, _, _ in files.values(): + for f, _, _ in pycompat.values(files): try: os.unlink(f) except IOError: diff --git a/odoo/models.py b/odoo/models.py index a1535123c9db..a9448b3ca052 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -108,9 +108,6 @@ def check_method_name(name): if regex_private.match(name): raise AccessError(_('Private methods (such as %s) cannot be called remotely.') % (name,)) -def intersect(la, lb): - return filter(lambda x: x in lb, la) - def same_name(f, g): """ Test whether functions ``f`` and ``g`` are identical or have the same name """ return f == g or getattr(f, '__name__', 0) == getattr(g, '__name__', 1) @@ -150,7 +147,7 @@ class MetaModel(api.Meta): self.module_to_models[self._module].append(self) # check for new-api conversion error: leave comma after field definition - for key, val in attrs.iteritems(): + for key, val in pycompat.items(attrs): if type(val) is tuple and len(val) == 1 and isinstance(val[0], Field): _logger.error("Trailing comma after field definition: %s.%s", self, key) if isinstance(val, Field): @@ -515,7 +512,7 @@ class BaseModel(object): cls._inherits.update(base._inherits) - for mname, fnames in base._depends.iteritems(): + for mname, fnames in pycompat.items(base._depends): cls._depends[mname] = cls._depends.get(mname, []) + fnames for cons in base._constraints: @@ -525,7 +522,7 @@ class BaseModel(object): cls._sql_constraints += base._sql_constraints cls._sequence = cls._sequence or (cls._table + '_id_seq') - cls._constraints = cls._constraints.values() + cls._constraints = list(pycompat.values(cls._constraints)) # update _inherits_children of parent models for parent_name in cls._inherits: @@ -707,7 +704,7 @@ class BaseModel(object): This method is used when exporting data via client menu """ - fields_to_export = map(fix_import_export_id_paths, fields_to_export) + fields_to_export = [fix_import_export_id_paths(f) for f in fields_to_export] if raw_data: self = self.with_context(export_raw_data=True) return {'datas': self._export_rows(fields_to_export)} @@ -740,7 +737,7 @@ class BaseModel(object): cr = self._cr cr.execute('SAVEPOINT model_load') - fields = map(fix_import_export_id_paths, fields) + fields = [fix_import_export_id_paths(f) for f in fields] fg = self.fields_get() ids = [] @@ -835,7 +832,7 @@ class BaseModel(object): # copy non-relational fields to record dict record = {fnames[0]: value - for fnames, value in itertools.izip(fields_, row) + for fnames, value in pycompat.izip(fields_, row) if not is_relational(fnames[0])} # Get all following rows which have relational values attached to @@ -849,13 +846,13 @@ class BaseModel(object): # get only cells for this sub-field, should be strictly # non-empty, field path [None] is for name_get field - indices, subfields = zip(*((index, fnames[1:] or [None]) + indices, subfields = pycompat.izip(*((index, fnames[1:] or [None]) for index, fnames in enumerate(fields_) if fnames[0] == relfield)) # return all rows which have at least one value for the # subfields of relfield - relfield_data = filter(any, map(itemgetter_tuple(indices), record_span)) + relfield_data = [it for it in pycompat.imap(itemgetter_tuple(indices), record_span) if any(it)] record[relfield] = [ subrecord for subrecord, _subinfo in comodel._extract_records(subfields, relfield_data, log=log) @@ -876,7 +873,7 @@ class BaseModel(object): :returns: a list of triplets of (id, xid, record) :rtype: list((int|None, str|None, dict)) """ - field_names = {name: field.string for name, field in self._fields.iteritems()} + field_names = {name: field.string for name, field in pycompat.items(self._fields)} if self.env.lang: field_names.update(self.env['ir.translation'].get_field_string(self._name)) @@ -1007,7 +1004,7 @@ class BaseModel(object): defaults = self._convert_to_write(defaults) # add default values for inherited fields - for model, names in parent_fields.iteritems(): + for model, names in pycompat.items(parent_fields): defaults.update(self.env[model].default_get(names)) return defaults @@ -1083,7 +1080,7 @@ class BaseModel(object): :rtype: etree._Element """ group = E.group(col="4") - for fname, field in self._fields.iteritems(): + for fname, field in pycompat.items(self._fields): if field.automatic: continue elif field.type in ('one2many', 'many2many', 'text', 'html'): @@ -1500,14 +1497,14 @@ class BaseModel(object): # avoid overriding inherited values when parent is set avoid_models = { parent_model - for parent_model, parent_field in self._inherits.iteritems() + for parent_model, parent_field in pycompat.items(self._inherits) if parent_field in values } # compute missing fields missing_defaults = { name - for name, field in self._fields.iteritems() + for name, field in pycompat.items(self._fields) if name not in values if name not in MAGIC_COLUMNS if not (field.inherited and field.related_field.model_name in avoid_models) @@ -1518,7 +1515,7 @@ class BaseModel(object): # override defaults with the provided values, never allow the other way around defaults = self.default_get(list(missing_defaults)) - for name, value in defaults.iteritems(): + for name, value in pycompat.items(defaults): if self._fields[name].type == 'many2many' and value and isinstance(value[0], pycompat.integer_types): # convert a list of ids into a list of commands defaults[name] = [(6, 0, value)] @@ -1603,10 +1600,10 @@ class BaseModel(object): if field.relational and groups._fold_name in groups._fields: fold = {group.id: group[groups._fold_name] for group in groups.browse([key for key in result if key])} - for key, line in result.iteritems(): + for key, line in pycompat.items(result): line['__fold'] = fold.get(key, False) - return result.values() + return list(pycompat.values(result)) @api.model def _read_group_prepare(self, orderby, aggregated_fields, annotated_groupbys, query): @@ -1840,7 +1837,7 @@ class BaseModel(object): def _read_group_raw(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True): self.check_access_rights('read') query = self._where_calc(domain) - fields = fields or [f.name for f in self._fields.itervalues() if f.store] + fields = fields or [f.name for f in pycompat.values(self._fields) if f.store] groupby = [groupby] if isinstance(groupby, basestring) else list(OrderedSet(groupby)) groupby_list = groupby[:1] if lazy else groupby @@ -1914,7 +1911,7 @@ class BaseModel(object): self._read_group_resolve_many2one_fields(fetched_data, annotated_groupbys) - data = map(lambda r: {k: self._read_group_prepare_data(k,v, groupby_dict) for k,v in r.iteritems()}, fetched_data) + data = ({k: self._read_group_prepare_data(k,v, groupby_dict) for k,v in pycompat.items(r)} for r in fetched_data) result = [self._read_group_format_result(d, annotated_groupbys, groupby, domain) for d in data] if lazy: # Right now, read_group only fill results in lazy mode (by default). @@ -2020,7 +2017,7 @@ class BaseModel(object): # fields which were required but have been removed (or will be added by # another module) cr = self._cr - cols = [name for name, field in self._fields.iteritems() + cols = [name for name, field in pycompat.items(self._fields) if field.store and field.column_type] cr.execute("SELECT a.attname, a.attnotnull" " FROM pg_class c, pg_attribute a" @@ -2120,7 +2117,7 @@ class BaseModel(object): recs = self.with_context(active_test=False).search([]) recs._recompute_todo(field) - for field in self._fields.itervalues(): + for field in pycompat.values(self._fields): if not field.store: continue @@ -2208,9 +2205,9 @@ class BaseModel(object): """ Determine inherited fields. """ # determine candidate inherited fields fields = {} - for parent_model, parent_field in self._inherits.iteritems(): + for parent_model, parent_field in pycompat.items(self._inherits): parent = self.env[parent_model] - for name, field in parent._fields.iteritems(): + for name, field in pycompat.items(parent._fields): # inherited fields are implemented as related fields, with the # following specific properties: # - reading inherited fields should not bypass access rights @@ -2223,13 +2220,13 @@ class BaseModel(object): ) # add inherited fields that are not redefined locally - for name, field in fields.iteritems(): + for name, field in pycompat.items(fields): if name not in self._fields: self._add_field(name, field) @api.model def _inherits_check(self): - for table, field_name in self._inherits.items(): + for table, field_name in pycompat.items(self._inherits): field = self._fields.get(field_name) if not field: _logger.info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.', field_name, self._name) @@ -2242,7 +2239,7 @@ class BaseModel(object): field.ondelete = "cascade" # reflect fields with delegate=True in dictionary self._inherits - for field in self._fields.itervalues(): + for field in pycompat.values(self._fields): if field.type == 'many2one' and not field.related and field.delegate: if not field.required: _logger.warning("Field %s with delegate=True must be required.", field) @@ -2330,7 +2327,7 @@ class BaseModel(object): # set up fields bad_fields = [] - for name, field in cls._fields.iteritems(): + for name, field in pycompat.items(cls._fields): try: field.setup_full(self) except Exception: @@ -2349,11 +2346,11 @@ class BaseModel(object): # map each field to the fields computed with the same method groups = defaultdict(list) - for field in cls._fields.itervalues(): + for field in pycompat.values(cls._fields): if field.compute: cls._field_computed[field] = group = groups[field.compute] group.append(field) - for fields in groups.itervalues(): + for fields in pycompat.values(groups): compute_sudo = fields[0].compute_sudo if not all(field.compute_sudo == compute_sudo for field in fields): _logger.warning("%s: inconsistent 'compute_sudo' for computed fields: %s", @@ -2366,7 +2363,7 @@ class BaseModel(object): if isinstance(self, Model): # set up field triggers (on database-persisted models only) - for field in cls._fields.itervalues(): + for field in pycompat.values(cls._fields): # dependencies of custom fields may not exist; ignore that case exceptions = (Exception,) if field.manual else () with tools.ignore(*exceptions): @@ -2405,7 +2402,7 @@ class BaseModel(object): readonly = not (has_access('write') or has_access('create')) res = {} - for fname, field in self._fields.iteritems(): + for fname, field in pycompat.items(self._fields): if allfields and fname not in allfields: continue if field.groups and not self.user_has_groups(field.groups): @@ -2417,7 +2414,7 @@ class BaseModel(object): description['states'] = {} if attributes: description = {key: val - for key, val in description.iteritems() + for key, val in pycompat.items(description) if key in attributes} res[fname] = description @@ -2452,9 +2449,9 @@ class BaseModel(object): return True if not fields: - fields = filter(valid, self._fields) + fields = [name for name in self._fields if valid(name)] else: - invalid_fields = set(filter(lambda name: not valid(name), fields)) + invalid_fields = {name for name in fields if not valid(name)} if invalid_fields: _logger.info('Access Denied by ACLs for operation: %s, uid: %s, model: %s, fields: %s', operation, self._uid, self._name, ', '.join(invalid_fields)) @@ -2528,7 +2525,7 @@ class BaseModel(object): if self._context.get('prefetch_fields', True) and field.prefetch: fs.update( f - for f in self._fields.itervalues() + for f in pycompat.values(self._fields) # select fields that can be prefetched if f.prefetch # discard fields with groups that the user may not access @@ -2591,7 +2588,7 @@ class BaseModel(object): order_str = self._generate_order_by(None, query) # determine the fields that are stored as columns in tables; - fields = map(self._fields.get, field_names + inherited_field_names) + fields = [self._fields[n] for n in (field_names + inherited_field_names)] fields_pre = [ field for field in fields @@ -2608,7 +2605,7 @@ class BaseModel(object): res = 'pg_size_pretty(length(%s)::bigint)' % res return '%s as "%s"' % (res, col) - qual_names = map(qualify, set(fields_pre + [self._fields['id']])) + qual_names = [qualify(name) for name in set(fields_pre + [self._fields['id']])] # determine the actual query to execute from_clause, where_clause, params = query.get_sql() @@ -2666,8 +2663,7 @@ class BaseModel(object): if extras: raise AccessError( _("Database fetch misses ids ({}) and has extra ids ({}), may be caused by a type incoherence in a previous request").format( - ', '.join(map(repr, missing._ids)), - ', '.join(map(repr, extras._ids)), + missing._ids, extras._ids, )) # mark non-existing records in missing forbidden = missing.exists() @@ -2990,7 +2986,7 @@ class BaseModel(object): # split up fields into old-style and pure new-style ones old_vals, new_vals, unknown = {}, {}, [] - for key, val in vals.iteritems(): + for key, val in pycompat.items(vals): field = self._fields.get(key) if field: if field.store or field.inherited: @@ -3003,7 +2999,7 @@ class BaseModel(object): if unknown: _logger.warning("%s.write() with unknown fields: %s", self._name, ', '.join(sorted(unknown))) - protected_fields = map(self._fields.get, new_vals) + protected_fields = [self._fields[n] for n in new_vals] with self.env.protecting(protected_fields, self): # write old-style fields with (low-level) method _write if old_vals: @@ -3054,7 +3050,7 @@ class BaseModel(object): query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL) ORDER BY %s" % \ (self._table, self._parent_name, self._parent_order) cr.execute(query, (tuple(self.ids),)) - parents_changed = map(operator.itemgetter(0), cr.fetchall()) + parents_changed = [x[0] for x in cr.fetchall()] updates = [] # list of (column, expr) or (column, pattern, value) upd_todo = [] # list of column names to set explicitly @@ -3062,7 +3058,7 @@ class BaseModel(object): direct = [] # list of direcly updated columns has_trans = self.env.lang and self.env.lang != 'en_US' single_lang = len(self.env['res.lang'].get_installed()) <= 1 - for name, val in vals.iteritems(): + for name, val in pycompat.items(vals): field = self._fields[name] if field and field.deprecated: _logger.warning('Field %s.%s is deprecated: %s', self._name, name, field.deprecated) @@ -3124,7 +3120,7 @@ class BaseModel(object): # defaults in context must be removed when call a one2many or many2many rel_context = {key: val - for key, val in self._context.iteritems() + for key, val in pycompat.items(self._context) if not key.startswith('default_')} # call the 'write' method of fields which are not columns @@ -3137,7 +3133,7 @@ class BaseModel(object): # write inherited fields on the corresponding parent records unknown_fields = set(updend) - for parent_model, parent_field in self._inherits.iteritems(): + for parent_model, parent_field in pycompat.items(self._inherits): parent_ids = [] for sub_ids in cr.split_for_in_conditions(self.ids): query = "SELECT DISTINCT %s FROM %s WHERE id IN %%s" % (parent_field, self._table) @@ -3258,7 +3254,7 @@ class BaseModel(object): # split up fields into old-style and pure new-style ones old_vals, new_vals, unknown = {}, {}, [] - for key, val in vals.iteritems(): + for key, val in pycompat.items(vals): field = self._fields.get(key) if field: if field.store or field.inherited: @@ -3274,7 +3270,7 @@ class BaseModel(object): # create record with old-style fields record = self.browse(self._create(old_vals)) - protected_fields = map(self._fields.get, new_vals) + protected_fields = [self._fields[n] for n in new_vals] with self.env.protecting(protected_fields, record): # put the values of pure new-style fields into cache, and inverse them record.modified(set(new_vals) - set(old_vals)) @@ -3299,7 +3295,7 @@ class BaseModel(object): # data of parent records to create or update, by model tocreate = { parent_model: {'id': vals.pop(parent_field, None)} - for parent_model, parent_field in self._inherits.iteritems() + for parent_model, parent_field in pycompat.items(self._inherits) } # list of column assignments defined as tuples like: @@ -3314,7 +3310,7 @@ class BaseModel(object): upd_todo = [] unknown_fields = [] protected_fields = [] - for name, val in vals.items(): + for name, val in list(pycompat.items(vals)): field = self._fields.get(name) if not field: unknown_fields.append(name) @@ -3330,7 +3326,7 @@ class BaseModel(object): _logger.warning('No such field(s) in model %s: %s.', self._name, ', '.join(unknown_fields)) # create or update parent records - for parent_model, parent_vals in tocreate.iteritems(): + for parent_model, parent_vals in pycompat.items(tocreate): parent_id = parent_vals.pop('id') if not parent_id: parent_id = self.env[parent_model].create(parent_vals).id @@ -3339,12 +3335,12 @@ class BaseModel(object): updates.append((self._inherits[parent_model], '%s', parent_id)) # set boolean fields to False by default (to make search more powerful) - for name, field in self._fields.iteritems(): + for name, field in pycompat.items(self._fields): if field.type == 'boolean' and field.store and name not in vals: vals[name] = False # determine SQL values - for name, val in vals.iteritems(): + for name, val in pycompat.items(vals): field = self._fields[name] if field.store and field.column_type: updates.append((name, field.column_format, field.convert_to_column(val, self))) @@ -3375,7 +3371,7 @@ class BaseModel(object): if self.env.lang and self.env.lang != 'en_US': # add translations for self.env.lang - for name, val in vals.iteritems(): + for name, val in pycompat.items(vals): field = self._fields[name] if field.store and field.column_type and field.translate is True: tname = "%s,%s" % (self._name, name) @@ -3423,7 +3419,7 @@ class BaseModel(object): # defaults in context must be removed when call a one2many or many2many rel_context = {key: val - for key, val in self._context.iteritems() + for key, val in pycompat.items(self._context) if not key.startswith('default_')} # call the 'write' method of fields which are not columns @@ -3728,11 +3724,11 @@ class BaseModel(object): # build a black list of fields that should not be copied blacklist = set(MAGIC_COLUMNS + ['parent_left', 'parent_right']) - whitelist = set(name for name, field in self._fields.iteritems() if not field.inherited) + whitelist = set(name for name, field in pycompat.items(self._fields) if not field.inherited) def blacklist_given_fields(model): # blacklist the fields that are given by inheritance - for parent_model, parent_field in model._inherits.items(): + for parent_model, parent_field in pycompat.items(model._inherits): blacklist.add(parent_field) if parent_field in default: # all the fields of 'parent_model' are given by the record: @@ -3741,17 +3737,17 @@ class BaseModel(object): else: blacklist_given_fields(self.env[parent_model]) # blacklist deprecated fields - for name, field in model._fields.iteritems(): + for name, field in pycompat.items(model._fields): if field.deprecated: blacklist.add(name) blacklist_given_fields(self) fields_to_copy = {name: field - for name, field in self._fields.iteritems() + for name, field in pycompat.items(self._fields) if field.copy and name not in default and name not in blacklist} - for name, field in fields_to_copy.iteritems(): + for name, field in pycompat.items(fields_to_copy): if field.type == 'one2many': # duplicate following the order of the ids because we'll rely on # it later for copying translations in copy_translation()! @@ -3789,7 +3785,7 @@ class BaseModel(object): old_wo_lang, new_wo_lang = (old + new).with_context(lang=None) Translation = old.env['ir.translation'] - for name, field in old._fields.iteritems(): + for name, field in pycompat.items(old._fields): if not field.copy: continue @@ -3799,7 +3795,7 @@ class BaseModel(object): # foreseen in copy_data() old_lines = old[name].sorted(key='id') new_lines = new[name].sorted(key='id') - for (old_line, new_line) in zip(old_lines, new_lines): + for (old_line, new_line) in pycompat.izip(old_lines, new_lines): old_line.copy_translations(new_line) elif field.translate: @@ -3969,7 +3965,7 @@ class BaseModel(object): """ results = self._get_external_ids() return {key: val[0] if val else '' - for key, val in results.iteritems()} + for key, val in pycompat.items(results)} # backwards compatibility get_xml_id = get_external_id @@ -4236,7 +4232,7 @@ class BaseModel(object): """ List of actual record ids in this recordset (ignores placeholder ids for records to create) """ - return filter(None, list(self._ids)) + return [it for it in self._ids if it] # backward-compatibility with former browse records _cr = property(lambda self: self.env.cr) @@ -4342,7 +4338,7 @@ class BaseModel(object): target = self if update else self.browse([], self._prefetch) return { name: fields[name].convert_to_cache(value, target, validate=validate) - for name, value in values.iteritems() + for name, value in pycompat.items(values) if name in fields } @@ -4352,14 +4348,14 @@ class BaseModel(object): """ return { name: self._fields[name].convert_to_record(value, self) - for name, value in values.iteritems() + for name, value in pycompat.items(values) } def _convert_to_write(self, values): """ Convert the ``values`` dictionary into the format of :meth:`write`. """ fields = self._fields result = {} - for name, value in values.iteritems(): + for name, value in pycompat.items(values): if name in fields: field = fields[name] value = field.convert_to_cache(value, self, validate=False) @@ -4423,7 +4419,7 @@ class BaseModel(object): """ if isinstance(func, basestring): name = func - func = lambda rec: filter(None, rec.mapped(name)) + func = lambda rec: any(rec.mapped(name)) return self.browse([rec.id for rec in self if func(rec)]) def sorted(self, key=None, reverse=False): @@ -4440,13 +4436,13 @@ class BaseModel(object): return self.browse(reversed(recs._ids)) if reverse else recs if isinstance(key, basestring): key = itemgetter(key) - return self.browse(map(attrgetter('id'), sorted(self, key=key, reverse=reverse))) + return self.browse(item.id for item in sorted(self, key=key, reverse=reverse)) @api.multi def update(self, values): """ Update the records in ``self`` with ``values``. """ for record in self: - for name, value in values.iteritems(): + for name, value in pycompat.items(values): record[name] = value # @@ -4667,7 +4663,7 @@ class BaseModel(object): the records of model ``self`` in cache that have no value for ``field`` (:class:`Field` instance). """ - ids = filter(None, self._prefetch[self._name] - set(self.env.cache[field])) + ids = [it for it in self._prefetch[self._name] - set(self.env.cache[field]) if it] return self.browse(ids) @api.model @@ -4690,9 +4686,9 @@ class BaseModel(object): if fnames is None: if ids is None: return self.env.invalidate_all() - fields = self._fields.values() + fields = list(pycompat.values(self._fields)) else: - fields = map(self._fields.__getitem__, fnames) + fields = [self._fields[n] for n in fnames] # invalidate fields and inverse fields, too spec = [(f, ids) for f in fields] + \ @@ -4746,10 +4742,11 @@ class BaseModel(object): updates[frozendict(vals)].add(rec.id) # update records in batch when possible with recs.env.norecompute(): - for vals, ids in updates.iteritems(): + for vals, ids in pycompat.items(updates): recs.browse(ids)._write(dict(vals)) # mark computed fields as done - map(recs._recompute_done, fs) + for f in fs: + recs._recompute_done(f) # # Generic onchange method @@ -4779,7 +4776,7 @@ class BaseModel(object): if not result.get(names): result[names] = node.attrib.get('on_change') # traverse the subviews included in relational fields - for subinfo in info['fields'][name].get('views', {}).itervalues(): + for subinfo in pycompat.values(info['fields'][name].get('views', {})): process(etree.fromstring(subinfo['arch']), subinfo, names) else: for child in node: @@ -4802,19 +4799,19 @@ class BaseModel(object): return if res.get('value'): res['value'].pop('id', None) - self.update({key: val for key, val in res['value'].iteritems() if key in self._fields}) + self.update({key: val for key, val in pycompat.items(res['value']) if key in self._fields}) if res.get('domain'): result.setdefault('domain', {}).update(res['domain']) if res.get('warning'): if result.get('warning'): # Concatenate multiple warnings warning = result['warning'] - warning['message'] = '\n\n'.join(filter(None, [ + warning['message'] = '\n\n'.join(s for s in [ warning.get('title'), warning.get('message'), res['warning'].get('title'), res['warning'].get('message'), - ])) + ] if s) warning['title'] = _('Warnings') else: result['warning'] = res['warning'] @@ -4931,7 +4928,7 @@ class BaseModel(object): record.mapped(field_seq) # determine which fields have been modified - for name, oldval in values.iteritems(): + for name, oldval in pycompat.items(values): field = self._fields[name] newval = record[name] if newval != oldval or ( @@ -5005,7 +5002,7 @@ class RecordCache(MutableMapping): """ if args and isinstance(args[0], SpecialValue): values = dict.fromkeys(self._recs._ids, args[0]) - for name, field in self._recs._fields.iteritems(): + for name, field in pycompat.items(self._recs._fields): if name != 'id': self._recs.env.cache[field].update(values) else: @@ -5023,7 +5020,7 @@ class RecordCache(MutableMapping): """ Iterate over the field names with a regular value in cache. """ cache, id = self._recs.env.cache, self._recs.id dummy = SpecialValue(None) - for name, field in self._recs._fields.iteritems(): + for name, field in pycompat.items(self._recs._fields): if name != 'id' and not isinstance(cache[field].get(id, dummy), SpecialValue): yield name diff --git a/odoo/modules/db.py b/odoo/modules/db.py index 7106ff82b69a..bde759843cfb 100644 --- a/odoo/modules/db.py +++ b/odoo/modules/db.py @@ -99,7 +99,7 @@ def create_categories(cr, categories): category = [] while categories: category.append(categories[0]) - xml_id = 'module_category_' + ('_'.join(map(lambda x: x.lower(), category))).replace('&', 'and').replace(' ', '_') + xml_id = 'module_category_' + ('_'.join(x.lower() for x in category)).replace('&', 'and').replace(' ', '_') # search via xml_id (because some categories are renamed) cr.execute("SELECT res_id FROM ir_model_data WHERE name=%s AND module=%s AND model=%s", (xml_id, "base", "ir.module.category")) diff --git a/odoo/modules/graph.py b/odoo/modules/graph.py index d962ea78bdc6..731b6f6557ab 100644 --- a/odoo/modules/graph.py +++ b/odoo/modules/graph.py @@ -8,7 +8,7 @@ import logging import odoo import odoo.tools as tools - +from odoo.tools import pycompat _logger = logging.getLogger(__name__) @@ -36,7 +36,7 @@ class Graph(dict): return # update the graph with values from the database (if exist) ## First, we set the default values for each package in graph - additional_data = dict((key, {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None}) for key in self.keys()) + additional_data = {key: {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None} for key in pycompat.keys(self)} ## Then we get the values from the database cr.execute('SELECT name, id, state, demo AS dbdemo, latest_version AS installed_version' ' FROM ir_module_module' @@ -46,8 +46,8 @@ class Graph(dict): ## and we update the default values with values from the database additional_data.update((x['name'], x) for x in cr.dictfetchall()) - for package in self.values(): - for k, v in additional_data[package.name].items(): + for package in pycompat.values(self): + for k, v in pycompat.items(additional_data[package.name]): setattr(package, k, v) def add_module(self, cr, module, force=None): @@ -94,7 +94,7 @@ class Graph(dict): self.update_from_db(cr) for package in later: - unmet_deps = filter(lambda p: p not in self, dependencies[package]) + unmet_deps = [p for p in dependencies[package] if p not in self] _logger.error('module %s: Unmet dependencies: %s', package, ', '.join(unmet_deps)) return len(self) - len_graph @@ -102,9 +102,9 @@ class Graph(dict): def __iter__(self): level = 0 - done = set(self.keys()) + done = set(pycompat.keys(self)) while done: - level_modules = sorted((name, module) for name, module in self.items() if module.depth==level) + level_modules = sorted((name, module) for name, module in pycompat.items(self) if module.depth==level) for name, module in level_modules: done.remove(name) yield module @@ -164,7 +164,10 @@ class Node(object): setattr(child, name, value + 1) def __iter__(self): - return itertools.chain(iter(self.children), *map(iter, self.children)) + return itertools.chain( + self.children, + itertools.chain.from_iterable(self.children) + ) def __str__(self): return self._pprint() diff --git a/odoo/modules/loading.py b/odoo/modules/loading.py index f8c65d401776..bd58376a7b98 100644 --- a/odoo/modules/loading.py +++ b/odoo/modules/loading.py @@ -20,6 +20,7 @@ import odoo.tools as tools from odoo import api, SUPERUSER_ID from odoo.modules.module import adapt_version, initialize_sys_path, load_openerp_module +from odoo.tools import pycompat _logger = logging.getLogger(__name__) _test_logger = logging.getLogger('odoo.tests') @@ -295,15 +296,15 @@ def load_modules(db, force_demo=False, status=None, update_module=False): _logger.info('updating modules list') Module.update_list() - _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys())) + _check_module_names(cr, itertools.chain(tools.config['init'], tools.config['update'])) - module_names = [k for k, v in tools.config['init'].items() if v] + module_names = [k for k, v in pycompat.items(tools.config['init']) if v] if module_names: modules = Module.search([('state', '=', 'uninstalled'), ('name', 'in', module_names)]) if modules: modules.button_install() - module_names = [k for k, v in tools.config['update'].items() if v] + module_names = [k for k, v in pycompat.items(tools.config['update']) if v] if module_names: modules = Module.search([('state', '=', 'installed'), ('name', 'in', module_names)]) if modules: @@ -391,7 +392,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False): getattr(py_module, uninstall_hook)(cr, registry) Module = env['ir.module.module'] - Module.browse(modules_to_remove.values()).module_uninstall() + Module.browse(pycompat.values(modules_to_remove)).module_uninstall() # Recursive reload, should only happen once, because there should be no # modules to remove next time cr.commit() @@ -414,7 +415,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False): _logger.info('Modules loaded.') # STEP 8: call _register_hook on every model - for model in env.values(): + for model in pycompat.values(env): model._register_hook() # STEP 9: save installed/updated modules for post-install tests diff --git a/odoo/modules/migration.py b/odoo/modules/migration.py index 5b1dcf09ab2b..0fa6699cf24a 100644 --- a/odoo/modules/migration.py +++ b/odoo/modules/migration.py @@ -13,6 +13,7 @@ from os.path import join as opj from odoo.modules.module import get_resource_path import odoo.release as release import odoo.tools as tools +from odoo.tools import pycompat from odoo.tools.parse_version import parse_version @@ -87,13 +88,12 @@ class MigrationManager(object): return "%s.%s" % (release.major_version, version) def _get_migration_versions(pkg): - versions = list(set( + versions = sorted({ ver - for lv in self.migrations[pkg.name].values() - for ver, lf in lv.items() + for lv in pycompat.values(self.migrations[pkg.name]) + for ver, lf in pycompat.items(lv) if lf - )) - versions.sort(key=lambda k: parse_version(convert_version(k))) + }, key=lambda k: parse_version(convert_version(k))) return versions def _get_migration_files(pkg, version, stage): @@ -107,7 +107,7 @@ class MigrationManager(object): 'maintenance': opj('base', 'maintenance', 'migrations', pkg.name), } - for x in mapping.keys(): + for x in mapping: if version in m.get(x): for f in m[x][version]: if not f.startswith(stage + '-'): diff --git a/odoo/modules/module.py b/odoo/modules/module.py index dd1485e1d8ac..e6194a5fb10c 100644 --- a/odoo/modules/module.py +++ b/odoo/modules/module.py @@ -23,6 +23,7 @@ import odoo import odoo.tools as tools import odoo.release as release from odoo import SUPERUSER_ID, api +from odoo.tools import pycompat MANIFEST_NAMES = ('__manifest__.py', '__openerp__.py') README = ['README.rst', 'README.md', 'README.txt'] @@ -323,7 +324,7 @@ def load_information_from_description_file(module, mod_path=None): 'sequence': 100, 'summary': '', } - info.update(itertools.izip( + info.update(pycompat.izip( 'depends data demo test init_xml update_xml demo_xml'.split(), iter(list, None))) @@ -395,7 +396,11 @@ def get_modules(): for mname in MANIFEST_NAMES: if os.path.isfile(opj(dir, name, mname)): return True - return map(clean, filter(is_really_module, os.listdir(dir))) + return [ + clean(it) + for it in os.listdir(dir) + if is_really_module(it) + ] plist = [] initialize_sys_path() @@ -492,7 +497,7 @@ def run_unit_tests(module_name, dbname, position=runs_at_install): r = True for m in mods: tests = unwrap_suite(unittest.TestLoader().loadTestsFromModule(m)) - suite = unittest.TestSuite(itertools.ifilter(position, tests)) + suite = unittest.TestSuite(t for t in tests if position(t)) if suite.countTestCases(): t0 = time.time() @@ -532,5 +537,5 @@ def unwrap_suite(test): return for item in itertools.chain.from_iterable( - itertools.imap(unwrap_suite, subtests)): + unwrap_suite(t) for t in subtests): yield item diff --git a/odoo/modules/registry.py b/odoo/modules/registry.py index e0cfb532fd13..71d7b5d9fbec 100644 --- a/odoo/modules/registry.py +++ b/odoo/modules/registry.py @@ -16,7 +16,8 @@ import threading import odoo from .. import SUPERUSER_ID from odoo.tools import (assertion_report, lazy_classproperty, config, - lazy_property, table_exists, topological_sort, OrderedSet) + lazy_property, table_exists, topological_sort, + OrderedSet, pycompat) from odoo.tools.lru import LRU _logger = logging.getLogger(__name__) @@ -156,7 +157,7 @@ class Registry(Mapping): def delete_all(cls): """ Delete all the registries. """ with cls._lock: - for db_name in cls.registries.keys(): + for db_name in list(pycompat.keys(cls.registries)): cls.delete(db_name) # @@ -192,8 +193,8 @@ class Registry(Mapping): # map fields on their dependents dependents = { field: set(dep for dep, _ in model._field_triggers[field] if dep != field) - for model in self.itervalues() - for field in model._fields.itervalues() + for model in pycompat.values(self) + for field in pycompat.values(model._fields) } # sort them topologically, and associate a sequence number to each field mapping = { @@ -261,7 +262,7 @@ class Registry(Mapping): env['ir.model']._add_manual_models() # prepare the setup on all models - models = env.values() + models = list(pycompat.values(env)) for model in models: model._prepare_setup() @@ -310,18 +311,18 @@ class Registry(Mapping): # make sure all tables are present missing = [name - for name, model in env.items() + for name, model in pycompat.items(env) if not model._abstract and not table_exists(cr, model._table)] if missing: _logger.warning("Models have no table: %s.", ", ".join(missing)) # recreate missing tables following model dependencies - deps = {name: model._depends for name, model in env.items()} + deps = {name: model._depends for name, model in pycompat.items(env)} for name in topological_sort(deps): if name in missing: _logger.info("Recreate table of model %s.", name) env[name].init() # check again, and log errors if tables are still missing - for name, model in env.items(): + for name, model in pycompat.items(env): if not model._abstract and not table_exists(cr, model._table): _logger.error("Model %s has no table.", name) @@ -340,7 +341,7 @@ class Registry(Mapping): """ Clear the caches associated to methods decorated with ``tools.ormcache`` or ``tools.ormcache_multi`` for all the models. """ - for model in self.models.itervalues(): + for model in pycompat.values(self.models): model.clear_caches() def setup_signaling(self): diff --git a/odoo/osv/expression.py b/odoo/osv/expression.py index b98fca44b391..8df300a2bd4b 100644 --- a/odoo/osv/expression.py +++ b/odoo/osv/expression.py @@ -1018,7 +1018,7 @@ class expression(object): subop = 'not inselect' if operator in NEGATIVE_TERM_OPERATORS else 'inselect' subquery = 'SELECT "%s" FROM "%s" WHERE "%s" IN %%s' % (rel_id1, rel_table, rel_id2) # avoid flattening of argument in to_sql() - subquery = cr.mogrify(subquery, [tuple(filter(None, res_ids))]) + subquery = cr.mogrify(subquery, [tuple(it for it in res_ids if it)]) push(create_substitution_leaf(leaf, ('id', subop, (subquery, [])), internal=True)) if call_null_m2m: @@ -1200,7 +1200,7 @@ class expression(object): else: field = model._fields[left] instr = ','.join([field.column_format] * len(params)) - params = map(partial(field.convert_to_column, record=model), params) + params = [field.convert_to_column(p, record=model) for p in params] query = '(%s."%s" %s (%s))' % (table_alias, left, operator, instr) else: # The case for (left, 'in', []) or (left, 'not in', []). diff --git a/odoo/osv/orm.py b/odoo/osv/orm.py index 6a815c9577a7..c06c4e664960 100644 --- a/odoo/osv/orm.py +++ b/odoo/osv/orm.py @@ -1,6 +1,7 @@ import json from lxml import etree +from odoo.tools import pycompat from ..exceptions import except_orm from ..models import ( MetaModel, @@ -35,12 +36,12 @@ def transfer_field_to_modifiers(field, modifiers): for attr in ('invisible', 'readonly', 'required'): state_exceptions[attr] = [] default_values[attr] = bool(field.get(attr)) - for state, modifs in (field.get("states",{})).items(): + for state, modifs in pycompat.items(field.get("states",{})): for modif in modifs: if default_values[modif[0]] != modif[1]: state_exceptions[modif[0]].append(state) - for attr, default_value in default_values.items(): + for attr, default_value in pycompat.items(default_values): if state_exceptions[attr]: modifiers[attr] = [("state", "not in" if default_value else "in", state_exceptions[attr])] else: diff --git a/odoo/release.py b/odoo/release.py index 51385a7a02bf..c563d9ff4cd3 100644 --- a/odoo/release.py +++ b/odoo/release.py @@ -13,8 +13,8 @@ RELEASE_LEVELS_DISPLAY = {ALPHA: ALPHA, # (6,1,0,'beta',0) < (6,1,0,'candidate',1) < (6,1,0,'candidate',2) # (6,1,0,'candidate',2) < (6,1,0,'final',0) < (6,1,2,'final',0) version_info = (11, 0, 0, ALPHA, 1, '') -version = '.'.join(map(str, version_info[:2])) + RELEASE_LEVELS_DISPLAY[version_info[3]] + str(version_info[4] or '') + version_info[5] -series = serie = major_version = '.'.join(map(str, version_info[:2])) +version = '.'.join(str(s) for s in version_info[:2]) + RELEASE_LEVELS_DISPLAY[version_info[3]] + str(version_info[4] or '') + version_info[5] +series = serie = major_version = '.'.join(str(s) for s in version_info[:2]) product_name = 'Odoo' description = 'Odoo Server' diff --git a/odoo/service/db.py b/odoo/service/db.py index e20dcb7442ae..e451b151d31e 100644 --- a/odoo/service/db.py +++ b/odoo/service/db.py @@ -351,7 +351,7 @@ def list_db_incompatible(databases): :return: A list of databases that are incompatible """ incompatible_databases = [] - server_version = '.'.join(map(str, version_info[:2])) + server_version = '.'.join(str(v) for v in version_info[:2]) for database_name in databases: with closing(db_connect(database_name).cursor()) as cr: if odoo.tools.table_exists(cr, 'ir_module_module'): diff --git a/odoo/service/model.py b/odoo/service/model.py index ba243bf432c7..78a07460a38e 100644 --- a/odoo/service/model.py +++ b/odoo/service/model.py @@ -137,7 +137,7 @@ def check(f): time.sleep(wait_time) except IntegrityError as inst: registry = odoo.registry(dbname) - for key in registry._sql_error.keys(): + for key in pycompat.keys(registry._sql_error): if key in inst[0]: raise ValidationError(tr(registry._sql_error[key], 'sql_constraint') or inst[0]) if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION): diff --git a/odoo/service/server.py b/odoo/service/server.py index 5de54d8188e5..fa6ed422f817 100644 --- a/odoo/service/server.py +++ b/odoo/service/server.py @@ -40,7 +40,7 @@ from odoo.modules.module import run_unit_tests, runs_post_install from odoo.modules.registry import Registry from odoo.release import nt_service_name import odoo.tools.config as config -from odoo.tools import stripped_sys_argv, dumpstacks, log_ormcache_stats +from odoo.tools import stripped_sys_argv, dumpstacks, log_ormcache_stats, pycompat _logger = logging.getLogger(__name__) @@ -216,7 +216,7 @@ class ThreadedServer(CommonServer): time.sleep(SLEEP_INTERVAL + number) # Steve Reich timing style registries = odoo.modules.registry.Registry.registries _logger.debug('cron%d polling for jobs', number) - for db_name, registry in registries.iteritems(): + for db_name, registry in pycompat.items(registries): while registry.ready: try: acquired = odoo.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name) @@ -513,7 +513,7 @@ class PreforkServer(CommonServer): def process_timeout(self): now = time.time() - for (pid, worker) in self.workers.items(): + for (pid, worker) in pycompat.items(self.workers): if worker.watchdog_timeout is not None and \ (now - worker.watchdog_time) >= worker.watchdog_timeout: _logger.error("%s (%s) timeout after %ss", @@ -534,8 +534,8 @@ class PreforkServer(CommonServer): def sleep(self): try: # map of fd -> worker - fds = dict([(w.watchdog_pipe[0], w) for k, w in self.workers.items()]) - fd_in = fds.keys() + [self.pipe[0]] + fds = {w.watchdog_pipe[0]: w for k, w in pycompat.items(self.workers)} + fd_in = list(pycompat.keys(fds)) + [self.pipe[0]] # check for ping or internal wakeups ready = select.select(fd_in, [], [], self.beat) # update worker watchdogs @@ -584,7 +584,7 @@ class PreforkServer(CommonServer): if graceful: _logger.info("Stopping gracefully") limit = time.time() + self.timeout - for pid in self.workers.keys(): + for pid in self.workers: self.worker_kill(pid, signal.SIGINT) while self.workers and time.time() < limit: try: @@ -596,7 +596,7 @@ class PreforkServer(CommonServer): time.sleep(0.1) else: _logger.info("Stopping forcefully") - for pid in self.workers.keys(): + for pid in self.workers: self.worker_kill(pid, signal.SIGTERM) if self.socket: self.socket.close() @@ -874,7 +874,7 @@ def load_test_file_yml(registry, test_file): def load_test_file_py(registry, test_file): # Locate python module based on its filename and run the tests test_path, _ = os.path.splitext(os.path.abspath(test_file)) - for mod_name, mod_mod in sys.modules.items(): + for mod_name, mod_mod in pycompat.items(sys.modules): if mod_mod: mod_path, _ = os.path.splitext(getattr(mod_mod, '__file__', '')) if test_path == mod_path: diff --git a/odoo/sql_db.py b/odoo/sql_db.py index 9a438d387dea..a722c3ea41b8 100644 --- a/odoo/sql_db.py +++ b/odoo/sql_db.py @@ -47,7 +47,7 @@ def undecimalize(symb, cr): return None return float(symb) -for name, typeoid in types_mapping.items(): +for name, typeoid in pycompat.items(types_mapping): psycopg2.extensions.register_type(psycopg2.extensions.new_type(typeoid, name, lambda x, cr: x)) psycopg2.extensions.register_type(psycopg2.extensions.new_type((700, 701, 1700,), 'float', undecimalize)) @@ -190,9 +190,9 @@ class Cursor(object): row = self._obj.fetchone() return row and self.__build_dict(row) def dictfetchmany(self, size): - return map(self.__build_dict, self._obj.fetchmany(size)) + return [self.__build_dict(row) for row in self._obj.fetchmany(size)] def dictfetchall(self): - return map(self.__build_dict, self._obj.fetchall()) + return [self.__build_dict(row) for row in self._obj.fetchall()] def __del__(self): if not self._closed and not self._cnx.closed: @@ -260,7 +260,7 @@ class Cursor(object): sqllogs = {'from': self.sql_from_log, 'into': self.sql_into_log} sum = 0 if sqllogs[type]: - sqllogitems = sqllogs[type].items() + sqllogitems = pycompat.items(sqllogs[type]) _logger.debug("SQL LOG %s:", type) for r in sorted(sqllogitems, key=lambda k: k[1]): delay = timedelta(microseconds=r[1][1]) @@ -473,7 +473,7 @@ class LazyCursor(object): if cr is None: from odoo import registry cr = self._cursor = registry(self.dbname).cursor() - for _ in pycompat.range(self._depth): + for _ in range(self._depth): cr.__enter__() return getattr(cr, name) diff --git a/odoo/tests/common.py b/odoo/tests/common.py index 78cfbfbdbf9d..a491c23ef76c 100644 --- a/odoo/tests/common.py +++ b/odoo/tests/common.py @@ -135,7 +135,7 @@ class BaseCase(unittest.TestCase): def shortDescription(self): doc = self._testMethodDoc - return doc and ' '.join(filter(None, map(str.strip, doc.splitlines()))) or None + return doc and ' '.join(l.strip() for l in doc.splitlines() if not l.isspace()) or None class TransactionCase(BaseCase): diff --git a/odoo/tools/amount_to_text_en.py b/odoo/tools/amount_to_text_en.py index 6a8e7020b4a8..b1854b1c4d6b 100644 --- a/odoo/tools/amount_to_text_en.py +++ b/odoo/tools/amount_to_text_en.py @@ -71,7 +71,7 @@ def amount_to_text(number, currency): cents_number = int(list[1]) cents_name = (cents_number > 1) and 'Cents' or 'Cent' - return ' '.join(filter(None, [start_word, units_name, (start_word or units_name) and (end_word or cents_name) and 'and', end_word, cents_name])) + return ' '.join(w for w in [start_word, units_name, (start_word or units_name) and (end_word or cents_name) and 'and', end_word, cents_name] if w) #------------------------------------------------------------- diff --git a/odoo/tools/appdirs.py b/odoo/tools/appdirs.py index f98fa1f8ae35..8617dabfa4fc 100644 --- a/odoo/tools/appdirs.py +++ b/odoo/tools/appdirs.py @@ -15,7 +15,7 @@ from __future__ import print_function # - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html __version_info__ = (1, 3, 0) -__version__ = '.'.join(map(str, __version_info__)) +__version__ = '.'.join(str(v) for v in __version_info__) import sys diff --git a/odoo/tools/cache.py b/odoo/tools/cache.py index 48dc42ba04ca..acc01895a48a 100644 --- a/odoo/tools/cache.py +++ b/odoo/tools/cache.py @@ -8,6 +8,8 @@ from decorator import decorator from inspect import formatargspec, getargspec import logging +from odoo.tools import pycompat + unsafe_eval = eval _logger = logging.getLogger(__name__) @@ -112,7 +114,7 @@ class ormcache_context(ormcache): spec = getargspec(self.method) args = formatargspec(*spec)[1:-1] cont_expr = "(context or {})" if 'context' in spec.args else "self._context" - keys_expr = "tuple(map(%s.get, %r))" % (cont_expr, self.keys) + keys_expr = "tuple(%s.get(k) for k in %r)" % (cont_expr, self.keys) if self.args: code = "lambda %s: (%s, %s)" % (args, ", ".join(self.args), keys_expr) else: @@ -200,10 +202,10 @@ def log_ormcache_stats(sig=None, frame=None): me = threading.currentThread() me_dbname = me.dbname entries = defaultdict(int) - for dbname, reg in Registry.registries.iteritems(): - for key in reg.cache.iterkeys(): + for dbname, reg in pycompat.items(Registry.registries): + for key in reg.cache: entries[(dbname,) + key[:2]] += 1 - for key, count in sorted(entries.items()): + for key, count in sorted(pycompat.items(entries)): dbname, model_name, method = key me.dbname = dbname stat = STAT[key] diff --git a/odoo/tools/config.py b/odoo/tools/config.py index 3a227fb51422..0f51fbcb2691 100644 --- a/odoo/tools/config.py +++ b/odoo/tools/config.py @@ -13,7 +13,8 @@ import odoo.conf import odoo.loglevels as loglevels import logging import odoo.release as release -from . import appdirs +from . import appdirs, pycompat + class MyOption (optparse.Option, object): """ optparse Option with two additional attributes. @@ -55,7 +56,7 @@ def _deduplicate_loggers(loggers): # there are no duplicates within the output sequence return ( '{}:{}'.format(logger, level) - for logger, level in dict(it.split(':') for it in loggers).iteritems() + for logger, level in pycompat.items(dict(it.split(':') for it in loggers)) ) @@ -453,10 +454,10 @@ class configmanager(object): self.options['demo'] = (dict(self.options['init']) if not self.options['without_demo'] else {}) self.options['update'] = opt.update and dict.fromkeys(opt.update.split(','), 1) or {} - self.options['translate_modules'] = opt.translate_modules and map(lambda m: m.strip(), opt.translate_modules.split(',')) or ['all'] + self.options['translate_modules'] = opt.translate_modules and [m.strip() for m in opt.translate_modules.split(',')] or ['all'] self.options['translate_modules'].sort() - dev_split = opt.dev_mode and map(str.strip, opt.dev_mode.split(',')) or [] + dev_split = opt.dev_mode and [s.strip() for s in opt.dev_mode.split(',')] or [] self.options['dev_mode'] = 'all' in dev_split and dev_split + ['pdb', 'reload', 'qweb', 'werkzeug', 'xml'] or dev_split if opt.pg_path: @@ -527,9 +528,9 @@ class configmanager(object): def save(self): p = ConfigParser.ConfigParser() - loglevelnames = dict(zip(self._LOGLEVELS.values(), self._LOGLEVELS.keys())) + loglevelnames = dict(pycompat.izip(pycompat.values(self._LOGLEVELS), pycompat.keys(self._LOGLEVELS))) p.add_section('options') - for opt in sorted(self.options.keys()): + for opt in sorted(pycompat.keys(self.options)): if opt in ('version', 'language', 'translate_out', 'translate_in', 'overwrite_existing_translations', 'init', 'update'): continue if opt in self.blacklist_for_save: @@ -541,9 +542,9 @@ class configmanager(object): else: p.set('options', opt, self.options[opt]) - for sec in sorted(self.misc.keys()): + for sec in sorted(pycompat.keys(self.misc)): p.add_section(sec) - for opt in sorted(self.misc[sec].keys()): + for opt in sorted(pycompat.keys(self.misc[sec])): p.set(sec,opt,self.misc[sec][opt]) # try to create the directories and write the file diff --git a/odoo/tools/convert.py b/odoo/tools/convert.py index 815ee9a64f1b..7e8e9f6e3ce5 100644 --- a/odoo/tools/convert.py +++ b/odoo/tools/convert.py @@ -98,7 +98,7 @@ def _eval_xml(self, node, env): q = safe_eval(f_search, idref2) ids = env[f_model].search(q).ids if f_use != 'id': - ids = map(lambda x: x[f_use], env[f_model].browse(ids).read([f_use])) + ids = [x[f_use] for x in env[f_model].browse(ids).read([f_use])] _fields = env[f_model]._fields if (f_name in _fields) and _fields[f_name].type == 'many2many': return ids @@ -659,7 +659,7 @@ form: module.record_id""" % (xml_id,) _fields = self.env[rec_model]._fields # if the current field is many2many if (f_name in _fields) and _fields[f_name].type == 'many2many': - f_val = [(6, 0, map(lambda x: x[f_use], s))] + f_val = [(6, 0, [x[f_use] for x in s])] elif len(s): # otherwise (we are probably in a many2one field), # take the first element of the search @@ -707,7 +707,7 @@ form: module.record_id""" % (xml_id,) 'model': 'ir.ui.view', } for att in ['forcecreate', 'context']: - if att in el.keys(): + if att in el.attrib: record_attrs[att] = el.attrib.pop(att) Field = builder.E.field @@ -733,7 +733,7 @@ form: module.record_id""" % (xml_id,) record.append(Field(name='customize_show', eval=el.get('customize_show'))) groups = el.attrib.pop('groups', None) if groups: - grp_lst = map(lambda x: "ref('%s')" % x, groups.split(',')) + grp_lst = [("ref('%s')" % x) for x in groups.split(',')] record.append(Field(name="groups_id", eval="[(6, 0, ["+', '.join(grp_lst)+"])]")) if el.attrib.pop('page', None) == 'True': record.append(Field(name="page", eval="True")) @@ -854,7 +854,7 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', if not (line and any(line)): continue try: - datas.append(map(ustr, line)) + datas.append([ustr(v) for v in line]) except Exception: _logger.error("Cannot import the line: %s", line) diff --git a/odoo/tools/float_utils.py b/odoo/tools/float_utils.py index 8c71d7a29b26..fa150a1d9028 100644 --- a/odoo/tools/float_utils.py +++ b/odoo/tools/float_utils.py @@ -217,10 +217,10 @@ if __name__ == "__main__": fractions = [.0, .015, .01499, .675, .67499, .4555, .4555, .45555] expecteds = ['.00', '.02', '.01', '.68', '.67', '.46', '.456', '.4556'] precisions = [2, 2, 2, 2, 2, 2, 3, 4] - for magnitude in pycompat.range(7): - for frac, exp, prec in zip(fractions, expecteds, precisions): + for magnitude in range(7): + for frac, exp, prec in pycompat.izip(fractions, expecteds, precisions): for sign in [-1,1]: - for x in pycompat.range(0, 10000, 97): + for x in range(0, 10000, 97): n = x * 10**magnitude f = sign * (n + frac) f_exp = ('-' if f != 0 and sign == -1 else '') + str(n) + exp diff --git a/odoo/tools/func.py b/odoo/tools/func.py index 8f49864eda22..2e204206f047 100644 --- a/odoo/tools/func.py +++ b/odoo/tools/func.py @@ -33,7 +33,7 @@ class lazy_property(object): """ Reset all lazy properties on the instance `obj`. """ cls = type(obj) obj_dict = vars(obj) - for name in obj_dict.keys(): + for name in list(obj_dict): if isinstance(getattr(cls, name, None), lazy_property): obj_dict.pop(name) diff --git a/odoo/tools/graph.py b/odoo/tools/graph.py index 82fc0e0978ac..5e3edabf05a5 100755 --- a/odoo/tools/graph.py +++ b/odoo/tools/graph.py @@ -5,6 +5,9 @@ import operator import math +from odoo.tools import pycompat + + class graph(object): def __init__(self, nodes, transitions, no_ancester=None): """Initialize graph's object @@ -256,7 +259,7 @@ class graph(object): """The ranks are normalized by setting the least rank to zero. """ - least_rank = min(map(lambda x: x['x'], self.result.values())) + least_rank = min(x['x'] for x in pycompat.values(self.result.values)) if least_rank!=0: for node in self.result: @@ -380,7 +383,7 @@ class graph(object): """Finds actual-order of the nodes with respect to maximum number of nodes in a rank in component """ mid_pos = 0.0 - max_level = max(map(lambda x: len(x), self.levels.values())) + max_level = max(len(x) for x in pycompat.values(self.levels.values)) for level in self.levels: if level: @@ -471,7 +474,7 @@ class graph(object): """ if self.Is_Cyclic: - max_level = max(map(lambda x: len(x), self.levels.values())) + max_level = max(len(x) for x in pycompat.values(self.levels.values)) if max_level%2: self.result[self.start]['y'] = (max_level+1)/2 + self.max_order + (self.max_order and 1) @@ -483,7 +486,7 @@ class graph(object): else: self.result[self.start]['y'] = 0 self.tree_order(self.start, 0) - min_order = math.fabs(min(map(lambda x: x['y'], self.result.values()))) + min_order = math.fabs(min(x['y'] for x in pycompat.values(self.result.values))) index = self.start_nodes.index(self.start) same = False @@ -537,7 +540,7 @@ class graph(object): self.result[start]['y'] = base + factor factor += 1 - self.max_order = max(map(lambda x: x['y'], self.result.values())) + self.max_order = max(x['y'] for x in pycompat.values(self.result.values)) def find_starts(self): """Finds other start nodes of the graph in the case when graph is disconneted @@ -626,7 +629,7 @@ class graph(object): self.make_chain() self.preprocess_order() self.order = {} - max_rank = max(map(lambda x: x, self.levels.keys())) + max_rank = max(x for x in self.levels) for i in range(max_rank+1): self.order[i] = 0 @@ -657,7 +660,7 @@ class graph(object): for node in self.no_ancester: for sec_node in self.transitions.get(node, []): - if sec_node in self.partial_order.keys(): + if sec_node in pycompat.keys(self.partial_order): self.transitions[self.start_nodes[0]].append(node) break @@ -743,7 +746,7 @@ if __name__=='__main__': for node in nodes: node_res[node] = result[node] - for name,node in node_res.items(): + for name,node in pycompat.items(node_res): draw.arc( (int(node['y']-radius), int(node['x']-radius),int(node['y']+radius), int(node['x']+radius) ), 0, 360, (128,128,128)) draw.text( (int(node['y']), int(node['x'])), str(name), (128,128,128)) diff --git a/odoo/tools/lru.py b/odoo/tools/lru.py index 7c485a039d37..a9e4a8b8f18b 100644 --- a/odoo/tools/lru.py +++ b/odoo/tools/lru.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- # taken from http://code.activestate.com/recipes/252524-length-limited-o1-lru-cache-implementation/ import threading + +from odoo.tools import pycompat from .func import synchronized __all__ = ['LRU'] @@ -92,6 +94,7 @@ class LRU(object): def __len__(self): return len(self.d) + # FIXME: should this have a P2 and a P3 version or something? @synchronized() def iteritems(self): cur = self.first @@ -106,12 +109,12 @@ class LRU(object): @synchronized() def itervalues(self): - for i,j in self.iteritems(): + for i,j in pycompat.items(self): yield j @synchronized() def keys(self): - return self.d.keys() + return list(pycompat.keys(self.d)) @synchronized() def pop(self,key): diff --git a/odoo/tools/mail.py b/odoo/tools/mail.py index 490006082d56..9689512df00d 100644 --- a/odoo/tools/mail.py +++ b/odoo/tools/mail.py @@ -16,6 +16,7 @@ from lxml import etree import odoo from odoo.loglevels import ustr +from odoo.tools import pycompat _logger = logging.getLogger(__name__) @@ -84,7 +85,7 @@ class _Cleaner(clean.Cleaner): new_node.text = text new_node.tail = tail if attrs: - for key, val in attrs.iteritems(): + for key, val in pycompat.items(attrs): new_node.set(key, val) return new_node @@ -153,7 +154,7 @@ class _Cleaner(clean.Cleaner): if style[0].lower() in self._style_whitelist: valid_styles[style[0].lower()] = style[1] if valid_styles: - el.attrib['style'] = '; '.join('%s: %s' % (key, val) for (key, val) in valid_styles.iteritems()) + el.attrib['style'] = '; '.join('%s: %s' % (key, val) for (key, val) in pycompat.items(valid_styles)) else: del el.attrib['style'] @@ -530,4 +531,4 @@ def decode_smtp_header(smtp_header): # was mail_thread.decode_header() def decode_message_header(message, header, separator=' '): - return separator.join(map(decode_smtp_header, filter(None, message.get_all(header, [])))) + return separator.join(decode_smtp_header(h) for h in message.get_all(header, []) if h) diff --git a/odoo/tools/mimetypes.py b/odoo/tools/mimetypes.py index 370c4b2e85bd..3abdc22963d9 100644 --- a/odoo/tools/mimetypes.py +++ b/odoo/tools/mimetypes.py @@ -10,6 +10,8 @@ import logging import re import zipfile +from odoo.tools import pycompat + __all__ = ['guess_mimetype'] _logger = logging.getLogger(__name__) @@ -33,7 +35,7 @@ def _check_ooxml(data): # then there is a directory whose name denotes the type of the file: # word, pt (powerpoint) or xl (excel) - for dirname, mime in _ooxml_dirs.iteritems(): + for dirname, mime in pycompat.items(_ooxml_dirs): if any(entry.startswith(dirname) for entry in filenames): return mime diff --git a/odoo/tools/misc.py b/odoo/tools/misc.py index 492be9951e42..81c27105ff14 100644 --- a/odoo/tools/misc.py +++ b/odoo/tools/misc.py @@ -22,7 +22,7 @@ import time import werkzeug.utils import zipfile from collections import defaultdict, Iterable, Mapping, MutableSet, OrderedDict -from itertools import islice, izip, groupby, repeat +from itertools import islice, groupby, repeat from lxml import etree from .which import which @@ -282,7 +282,7 @@ def flatten(list): r = [] for e in list: if isiterable(e): - map(r.append, flatten(e)) + r.extend(flatten(e)) else: r.append(e) return r @@ -304,7 +304,7 @@ def reverse_enumerate(l): File "<stdin>", line 1, in <module> StopIteration """ - return izip(pycompat.range(len(l)-1, -1, -1), reversed(l)) + return pycompat.izip(range(len(l)-1, -1, -1), reversed(l)) def partition(pred, elems): """ Return a pair equivalent to: @@ -337,10 +337,12 @@ def topological_sort(elems): visited.add(n) if n in elems: # first visit all dependencies of n, then append n to result - map(visit, elems[n]) + for it in elems[n]: + visit(it) result.append(n) - map(visit, elems) + for el in elems: + visit(el) return result @@ -458,7 +460,7 @@ def logged(f): vector = ['Call -> function: %r' % f] for i, arg in enumerate(args): vector.append(' arg %02d: %s' % (i, pformat(arg))) - for key, value in kwargs.items(): + for key, value in pycompat.items(kwargs): vector.append(' kwarg %10s: %s' % (key, pformat(value))) timeb4 = time.time() @@ -523,9 +525,9 @@ def detect_ip_addr(): # try 32 bit kernel: if ip_addr is None: - ifaces = filter(None, [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)]) + ifaces = [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)] - for ifname in [iface for iface in ifaces if iface != 'lo']: + for ifname in [iface for iface in ifaces if iface if iface != 'lo']: ip_addr = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, pack('256s', ifname[:15]))[20:24]) break @@ -909,7 +911,7 @@ def dumpstacks(sig=None, frame=None): 'uid': getattr(th, 'uid', 'n/a'), 'dbname': getattr(th, 'dbname', 'n/a')} for th in threading.enumerate()} - for threadId, stack in sys._current_frames().items(): + for threadId, stack in pycompat.items(sys._current_frames()): thread_info = threads_info.get(threadId, {}) code.append("\n# Thread: %s (id:%s) (db:%s) (uid:%s)" % (thread_info.get('name', 'n/a'), @@ -939,7 +941,7 @@ def freehash(arg): if isinstance(arg, Mapping): return hash(frozendict(arg)) elif isinstance(arg, Iterable): - return hash(frozenset(map(freehash, arg))) + return hash(frozenset(freehash(item) for item in arg)) else: return id(arg) @@ -960,7 +962,7 @@ class frozendict(dict): def update(self, *args, **kwargs): raise NotImplementedError("'update' not supported on frozendict") def __hash__(self): - return hash(frozenset((key, freehash(val)) for key, val in self.iteritems())) + return hash(frozenset((key, freehash(val)) for key, val in pycompat.items(self))) class Collector(Mapping): """ A mapping from keys to lists. This is essentially a space optimization @@ -1122,7 +1124,7 @@ def format_date(env, value, lang_code=False, date_format=False): def _consteq(str1, str2): """ Constant-time string comparison. Suitable to compare bytestrings of fixed, known length only, because length difference is optimized. """ - return len(str1) == len(str2) and sum(ord(x)^ord(y) for x, y in zip(str1, str2)) == 0 + return len(str1) == len(str2) and sum(ord(x)^ord(y) for x, y in pycompat.izip(str1, str2)) == 0 consteq = getattr(passlib.utils, 'consteq', _consteq) diff --git a/odoo/tools/osutil.py b/odoo/tools/osutil.py index af12c6558b23..2bb180a1d170 100644 --- a/odoo/tools/osutil.py +++ b/odoo/tools/osutil.py @@ -39,7 +39,7 @@ def walksymlinks(top, topdown=True, onerror=None): if topdown: yield dirpath, dirnames, filenames - symlinks = filter(lambda dirname: os.path.islink(os.path.join(dirpath, dirname)), dirnames) + symlinks = (dirname for dirname in dirnames if os.path.islink(os.path.join(dirpath, dirname))) for s in symlinks: for x in walksymlinks(os.path.join(dirpath, s), topdown, onerror): yield x diff --git a/odoo/tools/parse_version.py b/odoo/tools/parse_version.py index 348fb3664a6a..e9ff071c0b64 100644 --- a/odoo/tools/parse_version.py +++ b/odoo/tools/parse_version.py @@ -7,6 +7,8 @@ from __future__ import print_function import re +from odoo.tools import pycompat + component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE) replace = {'pre':'c', 'preview':'c','-':'final-','_':'final-','rc':'c','dev':'@','saas':'','~':''}.get @@ -72,7 +74,7 @@ if __name__ == '__main__': if verbose: print(v, pv) - for a, b in zip(pvs, pvs[1:]): + for a, b in pycompat.izip(pvs, pvs[1:]): assert a < b, '%s < %s == %s' % (a, b, a < b) chk(('0', '4.2', '4.2.3.4', '5.0.0-alpha', '5.0.0-rc1', '5.0.0-rc1.1', '5.0.0_rc2', '5.0.0_rc3', '5.0.0'), False) diff --git a/odoo/tools/pycompat.py b/odoo/tools/pycompat.py index 50b6f492a59d..3fbb847a9fac 100644 --- a/odoo/tools/pycompat.py +++ b/odoo/tools/pycompat.py @@ -6,10 +6,12 @@ import sys PY2 = sys.version_info[0] == 2 if PY2: - # pylint: disable=long-builtin,xrange-builtin + # pylint: disable=long-builtin,dict-iter-method integer_types = (int, long) - range = xrange + keys = lambda d: iter(d.iterkeys()) + values = lambda d: iter(d.itervalues()) + items = lambda d: iter(d.iteritems()) # noinspection PyUnresolvedReferences from itertools import imap, izip, ifilter @@ -19,9 +21,12 @@ if PY2: del cls.__next__ return cls else: + # pylint: disable=bad-functions integer_types = (int,) - range = range + keys = lambda d: iter(d.keys()) + values = lambda d: iter(d.values()) + items = lambda d: iter(d.items()) imap = map izip = zip diff --git a/odoo/tools/safe_eval.py b/odoo/tools/safe_eval.py index ebe97619e35d..1897486eb82f 100644 --- a/odoo/tools/safe_eval.py +++ b/odoo/tools/safe_eval.py @@ -23,7 +23,6 @@ from types import CodeType import logging import werkzeug -from odoo.tools import pycompat from .misc import ustr import odoo @@ -251,7 +250,7 @@ _BUILTINS = { 'divmod': divmod, 'isinstance': isinstance, 'range': range, - 'xrange': pycompat.range, + 'xrange': range, 'zip': zip, 'Exception': Exception, } diff --git a/odoo/tools/test_reports.py b/odoo/tools/test_reports.py index 17065848129b..cad44431c1de 100644 --- a/odoo/tools/test_reports.py +++ b/odoo/tools/test_reports.py @@ -174,7 +174,7 @@ def try_report_action(cr, uid, action_id, active_model=None, active_ids=None, view_data.update(wiz_data) _logger.debug("View data is: %r", view_data) - for fk, field in view_res.get('fields',{}).items(): + for fk, field in pycompat.items(view_res.get('fields',{})): # Default fields returns list of int, while at create() # we need to send a [(6,0,[int,..])] if field['type'] in ('one2many', 'many2many') \ diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py index dcdb95d09554..b2996af6ae3e 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -255,7 +255,7 @@ def translate_xml_node(node, callback, method, parser=None): append_content(result, translate_content(todo) if todo_has_text else todo) # translate the required attributes - for name, value in result.items(): + for name, value in pycompat.items(result.attrib): if name in TRANSLATED_ATTRS: result.set(name, translate_text(value) or value) @@ -660,7 +660,7 @@ def trans_export(lang, modules, buffer, format, cr): row.setdefault('tnrs', []).append((type, name, res_id)) row.setdefault('comments', set()).update(comments) - for src, row in sorted(grouped_rows.items()): + for src, row in sorted(pycompat.items(grouped_rows)): if not lang: # translation template, so no translation value row['translation'] = '' @@ -674,7 +674,7 @@ def trans_export(lang, modules, buffer, format, cr): module = row[0] rows_by_module.setdefault(module, []).append(row) tmpdir = tempfile.mkdtemp() - for mod, modrows in rows_by_module.items(): + for mod, modrows in pycompat.items(rows_by_module): tmpmoddir = join(tmpdir, mod, 'i18n') os.makedirs(tmpmoddir) pofilename = (lang if lang else mod) + ".po" + ('t' if not lang else '') @@ -1073,7 +1073,7 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True, dic = dict.fromkeys(('type', 'name', 'res_id', 'src', 'value', 'comments', 'imd_model', 'imd_name', 'module')) dic['lang'] = lang - dic.update(zip(fields, row)) + dic.update(pycompat.izip(fields, row)) # discard the target from the POT targets. src = dic['src'] @@ -1111,7 +1111,7 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True, # Then process the entries implied by the POT file (which is more # correct w.r.t. the targets) if some of them remain. pot_rows = [] - for src, target in pot_targets.iteritems(): + for src, target in pycompat.items(pot_targets): if target.value: for type, name, res_id in target.targets: pot_rows.append((type, name, res_id, src, target.value, target.comments)) diff --git a/odoo/tools/yaml_import.py b/odoo/tools/yaml_import.py index 36c6c2482490..4cb2e7ea1843 100644 --- a/odoo/tools/yaml_import.py +++ b/odoo/tools/yaml_import.py @@ -13,7 +13,7 @@ import yaml import odoo from . import assertion_report -from . import yaml_tag +from . import yaml_tag, pycompat from .config import config from .misc import file_open, DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT from odoo import SUPERUSER_ID @@ -36,8 +36,8 @@ class YamlImportAbortion(Exception): def _is_yaml_mapping(node, tag_constructor): value = isinstance(node, dict) \ - and len(node.keys()) == 1 \ - and isinstance(node.keys()[0], tag_constructor) + and len(node) == 1 \ + and isinstance(next(iter(node)), tag_constructor) return value def is_comment(node): @@ -202,7 +202,7 @@ class YamlInterpreter(object): def process_assert(self, node): if isinstance(node, dict): - assertion, expressions = node.items()[0] + assertion, expressions = next(pycompat.items(node)) else: assertion, expressions = node, [] @@ -275,7 +275,7 @@ class YamlInterpreter(object): return record_dict def process_record(self, node): - record, fields = node.items()[0] + record, fields = next(pycompat.items(node)) model = self.env[record.model] view_id = record.view if view_id and (view_id is not True) and isinstance(view_id, basestring): @@ -377,7 +377,7 @@ class YamlInterpreter(object): def process_vals(fg, vals): """ sanitize the given field values """ result = {} - for field_name, field_value in vals.iteritems(): + for field_name, field_value in pycompat.items(vals): if field_name not in fg: continue if fg[field_name]['type'] == 'many2one' and isinstance(field_value, (tuple, list)): @@ -391,14 +391,14 @@ class YamlInterpreter(object): elif isinstance(command, dict): return process_vals(sub_fg, command) return command - field_value = map(process, field_value or []) + field_value = [process(v) for v in (field_value or [])] result[field_name] = field_value return result def post_process(fg, elems, vals): """ filter out readonly fields from vals """ result = {} - for field_name, field_value in vals.iteritems(): + for field_name, field_value in pycompat.items(vals): if is_readonly(elems[field_name]): continue if fg[field_name]['type'] in ('one2many', 'many2many'): @@ -412,7 +412,7 @@ class YamlInterpreter(object): elif isinstance(command, dict): return (0, 0, post_process(sub_fg, sub_elems, command)) return command - field_value = map(process, field_value or []) + field_value = [process(v) for v in (field_value or [])] result[field_name] = field_value return result @@ -444,7 +444,7 @@ class YamlInterpreter(object): record_dict.update(process_vals(fg, result.get('value', {}))) # fill in fields, and execute onchange where necessary - for field_name, field_elem in elems.iteritems(): + for field_name, field_elem in pycompat.items(elems): assert field_name in fg, "The field '%s' is defined in the form view but not on the object '%s'!" % (field_name, model._name) if is_readonly(field_elem): # skip readonly fields @@ -474,7 +474,7 @@ class YamlInterpreter(object): result = recs.onchange(dict(record_dict, **parent_values), field_name, onchange_spec) record_dict.update(process_vals(fg, { key: val - for key, val in result.get('value', {}).iteritems() + for key, val in pycompat.items(result.get('value', {})) if key not in fields # do not shadow values explicitly set in yaml })) @@ -483,7 +483,7 @@ class YamlInterpreter(object): else: record_dict = {} - for field_name, expression in fields.iteritems(): + for field_name, expression in pycompat.items(fields): if record_dict.get(field_name): continue field_value = self._eval_field(model, field_name, expression, parent=record_dict, default=False, context=context) @@ -494,7 +494,7 @@ class YamlInterpreter(object): # should not be sent to create. This bug appears with not stored function fields in the new API. return { key: val - for key, val in record_dict.iteritems() + for key, val in pycompat.items(record_dict) for field in [model._fields[key].base_field] if field.store or field.inverse } @@ -580,7 +580,7 @@ class YamlInterpreter(object): self.sudo_env = self.env(user=SUPERUSER_ID) def process_python(self, node): - python, statements = node.items()[0] + python, statements = next(pycompat.items(node)) assert python.model or python.id, "!python node must have attribute `model` or `id`" if python.id is None: record = self.env[python.model] @@ -640,7 +640,7 @@ class YamlInterpreter(object): return args def process_function(self, node): - function, params = node.items()[0] + function, params = next(pycompat.items(node)) if self.isnoupdate(function) and self.mode != 'init': return model = self.env[function.model] @@ -770,9 +770,9 @@ class YamlInterpreter(object): def process_ir_set(self, node): if not self.mode == 'init': return False - _, fields = node.items()[0] + _, fields = next(pycompat.items(node)) res = {} - for fieldname, expression in fields.items(): + for fieldname, expression in pycompat.items(fields): if is_eval(expression): value = safe_eval(expression.expression, self.eval_context) else: @@ -878,7 +878,7 @@ class YamlInterpreter(object): elif not is_preceded_by_comment: if isinstance(node, dict): msg = "Creating %s\n with %s" - args = node.items()[0] + args = next(pycompat.items(node)) self._log(msg, *args) else: self._log(node) diff --git a/odoo/tools/yaml_tag.py b/odoo/tools/yaml_tag.py index f921c598f30a..2b7141b66937 100644 --- a/odoo/tools/yaml_tag.py +++ b/odoo/tools/yaml_tag.py @@ -1,6 +1,9 @@ import yaml import logging +from . import pycompat + + class YamlTag(object): """ Superclass for constructors of custom tags defined in yaml file. @@ -13,7 +16,7 @@ class YamlTag(object): def __getattr__(self, attr): return None def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, sorted(self.__dict__.items())) + return "<%s %s>" % (self.__class__.__name__, sorted(pycompat.items(self.__dict__))) class Assert(YamlTag): def __init__(self, model, id=None, severity=logging.WARNING, string="NONAME", **kwargs): diff --git a/setup.py b/setup.py index 2fce5faa39d7..465f40b1f0ab 100644 --- a/setup.py +++ b/setup.py @@ -24,9 +24,9 @@ def py2exe_datafiles(): import babel data_files['babel/localedata'] = glob(join(dirname(babel.__file__), 'localedata', '*')) others = ['global.dat', 'numbers.py', 'support.py', 'plural.py'] - data_files['babel'] = map(lambda f: join(dirname(babel.__file__), f), others) + data_files['babel'] = [join(dirname(babel.__file__), f) for f in others] others = ['frontend.py', 'mofile.py'] - data_files['babel/messages'] = map(lambda f: join(dirname(babel.__file__), 'messages', f), others) + data_files['babel/messages'] = [join(dirname(babel.__file__), 'messages', f) for f in others] import pytz tzdir = dirname(pytz.__file__) @@ -51,7 +51,7 @@ def py2exe_datafiles(): for f in filenames if not f.endswith(('.py', '.pyc', '.pyo'))] - return data_files.items() + return list(data_files.items()) def py2exe_options(): @@ -128,7 +128,7 @@ setup( url=url, author=author, author_email=author_email, - classifiers=filter(None, classifiers.split('\n')), + classifiers=[c for c in classifiers.split('\n') if c], license=license, scripts=['setup/odoo'], packages=find_packages(), diff --git a/setup/setup_dev.py b/setup/setup_dev.py index 0038ad3fd8af..1ad6fe2f5ff9 100755 --- a/setup/setup_dev.py +++ b/setup/setup_dev.py @@ -153,7 +153,7 @@ def main(): elif len(sys.argv) == 2 and sys.argv[1] in cmds: cmds[sys.argv[1]]() else: - sys.exit('Unknow command. Command available: %r' % (cmds.keys(),)) + sys.exit('Unknow command. Command available: %r' % (list(cmds,)) if __name__ == "__main__": main() -- GitLab