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> &nbsp; &nbsp; &bull; <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