diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index 29fcaa17c8e..daa407d636f 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -5,7 +5,7 @@ import math
from odoo.osv import expression
from odoo.tools.float_utils import float_round as round
-from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, pycompat
+from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.exceptions import UserError, ValidationError
from odoo import api, fields, models, _
diff --git a/addons/account/models/account_analytic_line.py b/addons/account/models/account_analytic_line.py
index 20b44c04dbb..1d212d31bf8 100644
--- a/addons/account/models/account_analytic_line.py
+++ b/addons/account/models/account_analytic_line.py
@@ -44,7 +44,7 @@ class AccountAnalyticLine(models.Model):
# Compute based on pricetype
amount_unit = self.product_id.price_compute('standard_price', uom=unit)[self.product_id.id]
amount = amount_unit * self.unit_amount or 0.0
- result = round(amount, self.currency_id.decimal_places) * -1
+ result = self.currency_id.round(amount) * -1
self.amount = result
self.general_account_id = account
self.product_uom_id = unit
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index d5630cdc134..ee0d1903fd0 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -395,7 +395,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 pycompat.items(onchanges):
+ for onchange_method, changed_fields in onchanges.items():
if any(f not in vals for f in changed_fields):
invoice = self.new(vals)
getattr(invoice, onchange_method)()
@@ -516,7 +516,7 @@ class AccountInvoice(models.Model):
tax_grouped = invoice.get_taxes_values()
# Create new tax lines
- for tax in pycompat.values(tax_grouped):
+ for tax in tax_grouped.values():
account_invoice_tax.create(tax)
# dummy write on self to trigger recomputations
@@ -535,7 +535,7 @@ class AccountInvoice(models.Model):
def _onchange_invoice_line_ids(self):
taxes_grouped = self.get_taxes_values()
tax_lines = self.tax_line_ids.filtered('manual')
- for tax in pycompat.values(taxes_grouped):
+ for tax in taxes_grouped.values():
tax_lines += tax_lines.new(tax)
self.tax_line_ids = tax_lines
return
@@ -886,7 +886,7 @@ class AccountInvoice(models.Model):
else:
line2[tmp] = l
line = []
- for key, val in pycompat.items(line2):
+ for key, val in line2.items():
line.append((0, 0, val))
return line
@@ -1079,7 +1079,7 @@ class AccountInvoice(models.Model):
result = []
for line in lines:
values = {}
- for name, field in pycompat.items(line._fields):
+ for name, field in line._fields.items():
if name in MAGIC_COLUMNS:
continue
elif field.type == 'many2one':
@@ -1249,7 +1249,7 @@ 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(pycompat.items(res), key=lambda l: l[0].sequence)
+ res = sorted(res.items(), key=lambda l: l[0].sequence)
res = [(l[0].name, l[1]) for l in res]
return res
@@ -1350,7 +1350,7 @@ class AccountInvoiceLine(models.Model):
node.set('domain', "[('purchase_ok', '=', True)]")
else:
node.set('domain', "[('sale_ok', '=', True)]")
- res['arch'] = etree.tostring(doc)
+ res['arch'] = etree.tostring(doc, encoding='unicode')
return res
@api.v8
@@ -1550,14 +1550,13 @@ class AccountPaymentTerm(models.Model):
currency = self.env['res.currency'].browse(self.env.context['currency_id'])
else:
currency = self.env.user.company_id.currency_id
- prec = currency.decimal_places
for line in self.line_ids:
if line.value == 'fixed':
- amt = round(line.value_amount, prec)
+ amt = currency.round(line.value_amount)
elif line.value == 'percent':
- amt = round(value * (line.value_amount / 100.0), prec)
+ amt = currency.round(value * (line.value_amount / 100.0))
elif line.value == 'balance':
- amt = round(amount, prec)
+ amt = currency.round(amount)
if amt:
next_date = fields.Date.from_string(date_ref)
if line.option == 'day_after_invoice_date':
@@ -1572,7 +1571,7 @@ class AccountPaymentTerm(models.Model):
result.append((fields.Date.to_string(next_date), amt))
amount -= amt
amount = sum(amt for _, amt in result)
- dist = round(value - amount, prec)
+ dist = currency.round(value - amount)
if dist:
last_date = result and result[-1][0] or fields.Date.today()
result.append((last_date, dist))
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index d2369a6292b..384e9b0f8e7 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -192,10 +192,10 @@ class AccountMove(models.Model):
@api.multi
def _check_lock_date(self):
for move in self:
- lock_date = max(move.company_id.period_lock_date, move.company_id.fiscalyear_lock_date)
+ lock_date = max(move.company_id.period_lock_date or '0000-00-00', move.company_id.fiscalyear_lock_date or '0000-00-00')
if self.user_has_groups('account.group_account_manager'):
lock_date = move.company_id.fiscalyear_lock_date
- if move.date <= lock_date:
+ if move.date <= (lock_date or '0000-00-00'):
if self.user_has_groups('account.group_account_manager'):
message = _("You cannot add/modify entries prior to and inclusive of the lock date %s") % (lock_date)
else:
@@ -1030,7 +1030,7 @@ class AccountMoveLine(models.Model):
partial_rec_set = self.env['account.partial.reconcile']
aml_id = False
partial_rec_id = False
- maxdate = None
+ maxdate = '0000-00-00'
for aml in self:
total_debit += aml.debit
total_credit += aml.credit
@@ -1492,7 +1492,7 @@ class AccountPartialReconcile(models.Model):
# The move date should be the maximum date between payment and invoice
# (in case of payment in advance). However, we should make sure the
# move date is not recorded after the end of year closing.
- if move_date > self.company_id.fiscalyear_lock_date:
+ if (move_date or '0000-00-00' > self.company_id.fiscalyear_lock_date or '0000-00-00'):
res['date'] = move_date
return res
@@ -1720,7 +1720,7 @@ class AccountPartialReconcile(models.Model):
#make sure that all partial reconciliations share the same secondary currency otherwise it's not
#possible to compute the exchange difference entry and it has to be done manually.
currency = list(partial_rec_set)[0].currency_id
- maxdate = None
+ maxdate = '0000-00-00'
for partial_rec in partial_rec_set:
if partial_rec.currency_id != currency:
diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py
index 2fe33e02e3a..d99b3fe99ce 100644
--- a/addons/account/models/chart_template.py
+++ b/addons/account/models/chart_template.py
@@ -7,8 +7,6 @@ from odoo.exceptions import UserError
import logging
-from odoo.tools import pycompat
-
_logger = logging.getLogger(__name__)
def migrate_set_tags_and_taxes_updatable(cr, registry, module):
@@ -327,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 pycompat.items(generated_tax_res['account_dict']):
+ for key, value in generated_tax_res['account_dict'].items():
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 f2f0f85f7c0..de32aaa5f3d 100644
--- a/addons/account/models/partner.py
+++ b/addons/account/models/partner.py
@@ -5,7 +5,7 @@ from operator import itemgetter
import time
from odoo import api, fields, models, _
-from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, pycompat
+from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.exceptions import ValidationError
from odoo.addons.base.res.res_partner import WARNING_MESSAGE, WARNING_HELP
@@ -73,7 +73,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 list(pycompat.items(accounts)):
+ for key, acc in accounts.items():
if acc in ref_dict:
accounts[key] = ref_dict[acc]
return accounts
@@ -293,7 +293,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 pycompat.items(all_partners_and_children):
+ for partner, child_ids in all_partners_and_children.items():
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_partner_ledger.py b/addons/account/report/account_partner_ledger.py
index 96e54607e4e..2da6c42abe7 100644
--- a/addons/account/report/account_partner_ledger.py
+++ b/addons/account/report/account_partner_ledger.py
@@ -109,7 +109,7 @@ class ReportPartnerLedger(models.AbstractModel):
self.env.cr.execute(query, tuple(params))
partner_ids = [res['partner_id'] for res in self.env.cr.dictfetchall()]
partners = obj_partner.browse(partner_ids)
- partners = sorted(partners, key=lambda x: (x.ref, x.name))
+ partners = sorted(partners, key=lambda x: (x.ref or '', x.name or ''))
return {
'doc_ids': partner_ids,
diff --git a/addons/account/report/account_report_financial.py b/addons/account/report/account_report_financial.py
index 01e2080307f..65560736fff 100644
--- a/addons/account/report/account_report_financial.py
+++ b/addons/account/report/account_report_financial.py
@@ -3,7 +3,6 @@
import time
from odoo import api, models, _
from odoo.exceptions import UserError
-from odoo.tools import pycompat
class ReportFinancial(models.AbstractModel):
@@ -28,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(pycompat.values(mapping)) + \
+ request = "SELECT account_id as id, " + ', '.join(mapping.values()) + \
" FROM " + tables + \
" WHERE account_id IN %s " \
+ filters + \
@@ -55,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 pycompat.values(res[report.id]['account']):
+ for value in res[report.id]['account'].values():
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 pycompat.values(res[report.id]['account']):
+ for value in res[report.id]['account'].values():
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 pycompat.items(res2):
+ for key, value in res2.items():
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 pycompat.items(res2):
+ for key, value in res2.items():
for field in fields:
res[report.id][field] += value[field]
return res
@@ -86,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 pycompat.items(comparison_res):
+ for report_id, value in comparison_res.items():
res[report_id]['comp_bal'] = value['balance']
report_acc = res[report_id].get('account')
if report_acc:
- for account_id, val in pycompat.items(comparison_res[report_id].get('account')):
+ for account_id, val in comparison_res[report_id].get('account').items():
report_acc[account_id]['comp_bal'] = val['balance']
for report in child_reports:
@@ -115,7 +114,7 @@ class ReportFinancial(models.AbstractModel):
if res[report.id].get('account'):
sub_lines = []
- for account_id, value in pycompat.items(res[report.id]['account']):
+ for account_id, value in res[report.id]['account'].items():
#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 0093915eee5..ac2aca16951 100644
--- a/addons/account/tests/test_reconciliation.py
+++ b/addons/account/tests/test_reconciliation.py
@@ -2,8 +2,6 @@ from odoo.addons.account.tests.account_test_classes import AccountingTestCase
import time
import unittest
-from odoo.tools import pycompat
-
class TestReconciliation(AccountingTestCase):
@@ -339,8 +337,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 pycompat.values(result)), 60.61)
- self.assertEquals(sum(res['credit'] for res in pycompat.items(result)), 60.61)
+ self.assertEquals(sum(res['debit'] for res in result.values()), 60.61)
+ self.assertEquals(sum(res['credit'] for res in result.items()), 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:
diff --git a/addons/account_asset/models/account_asset.py b/addons/account_asset/models/account_asset.py
index cc2a29a09a9..15d471301b6 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, pycompat
+from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF
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 pycompat.items(vals['value']):
+ for k, v in vals['value'].items():
setattr(self, k, v)
def onchange_category_id_values(self, category_id):
@@ -583,7 +583,7 @@ class AccountAssetDepreciationLine(models.Model):
message = ''
if message_description:
message = '%s' % message_description
- for name, values in pycompat.items(tracked_values):
+ for name, values in tracked_values.items():
message += '
• %s: ' % name
message += '%s
' % values
return message
diff --git a/addons/account_asset/wizard/asset_modify.py b/addons/account_asset/wizard/asset_modify.py
index 9f8ce7cd497..d31df656455 100644
--- a/addons/account_asset/wizard/asset_modify.py
+++ b/addons/account_asset/wizard/asset_modify.py
@@ -39,7 +39,7 @@ class AssetModify(models.TransientModel):
node = doc.xpath("//field[@name='method_number']")[0]
node.set('invisible', '1')
setup_modifiers(node, result['fields']['method_number'])
- result['arch'] = etree.tostring(doc)
+ result['arch'] = etree.tostring(doc, encoding='unicode')
return result
@api.model
diff --git a/addons/account_voucher/models/account_voucher.py b/addons/account_voucher/models/account_voucher.py
index e394af36296..40ae66c3395 100644
--- a/addons/account_voucher/models/account_voucher.py
+++ b/addons/account_voucher/models/account_voucher.py
@@ -5,7 +5,6 @@
from odoo import fields, models, api, _
from odoo.addons import decimal_precision as dp
from odoo.exceptions import UserError
-from odoo.tools import pycompat
class AccountVoucher(models.Model):
@@ -358,7 +357,7 @@ class AccountVoucherLine(models.Model):
self.company_id.id,
self.voucher_id.currency_id.id,
self.voucher_id.voucher_type)
- for fname, fvalue in pycompat.items(onchange_res['value']):
+ for fname, fvalue in onchange_res['value'].items():
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 2d4fe1b17d2..a0c90382938 100644
--- a/addons/anonymization/models/anonymization.py
+++ b/addons/anonymization/models/anonymization.py
@@ -6,6 +6,7 @@ from operator import itemgetter
from odoo import api, fields, models, _
from odoo.exceptions import UserError
+from odoo.tools import pycompat
FIELD_STATES = [('clear', 'Clear'), ('anonymized', 'Anonymized'), ('not_existing', 'Not Existing'), ('new', 'New')]
ANONYMIZATION_HISTORY_STATE = [('started', 'Started'), ('done', 'Done'), ('in_exception', 'Exception occured')]
@@ -13,7 +14,7 @@ ANONYMIZATION_DIRECTION = [('clear -> anonymized', 'clear -> anonymized'), ('ano
def group(lst, cols):
- if isinstance(cols, basestring):
+ if isinstance(cols, pycompat.string_types):
cols = [cols]
return dict((k, [v for v in itr]) for k, itr in groupby(sorted(lst, key=itemgetter(*cols)), itemgetter(*cols)))
diff --git a/addons/anonymization/wizard/anonymize_wizard.py b/addons/anonymization/wizard/anonymize_wizard.py
index fb514fe778e..a319347f918 100644
--- a/addons/anonymization/wizard/anonymize_wizard.py
+++ b/addons/anonymization/wizard/anonymize_wizard.py
@@ -120,7 +120,7 @@ class IrModelFieldsAnonymizeWizard(models.TransientModel):
else:
raise UserError(_("The database anonymization is currently in an unstable state. Some fields are anonymized,"
" while some fields are not anonymized. You should try to solve this problem before trying to do anything else."))
- res['arch'] = etree.tostring(eview)
+ res['arch'] = etree.tostring(eview, encoding='unicode')
return res
@api.multi
diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py
index 8f8db0fab0d..262dfdad86d 100644
--- a/addons/auth_signup/controllers/main.py
+++ b/addons/auth_signup/controllers/main.py
@@ -8,7 +8,6 @@ from odoo.addons.auth_signup.models.res_users import SignupError
from odoo.addons.web.controllers.main import ensure_db, Home
from odoo.exceptions import UserError
from odoo.http import request
-from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -106,7 +105,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 pycompat.items(token_infos):
+ for k, v in token_infos.items():
qcontext.setdefault(k, v)
except:
qcontext['error'] = _("Invalid signup token")
diff --git a/addons/auth_signup/models/res_partner.py b/addons/auth_signup/models/res_partner.py
index 0cf5c25b7ff..ecf63246580 100644
--- a/addons/auth_signup/models/res_partner.py
+++ b/addons/auth_signup/models/res_partner.py
@@ -8,8 +8,6 @@ from collections import defaultdict
from datetime import datetime, timedelta
from odoo import api, exceptions, fields, models, _
-from odoo.tools import pycompat
-
class SignupError(Exception):
pass
diff --git a/addons/base_address_city/models/res_partner.py b/addons/base_address_city/models/res_partner.py
index f6fcbf61d42..e3e3ad574e0 100644
--- a/addons/base_address_city/models/res_partner.py
+++ b/addons/base_address_city/models/res_partner.py
@@ -35,5 +35,5 @@ class Partner(models.Model):
city_id_node = etree.fromstring(replacement_xml)
city_node.getparent().replace(city_node, city_id_node)
- arch = etree.tostring(doc)
+ arch = etree.tostring(doc, encoding='unicode')
return arch
diff --git a/addons/base_address_extended/models/base_address_extended.py b/addons/base_address_extended/models/base_address_extended.py
index 1d49c1c94c4..9a210dde769 100644
--- a/addons/base_address_extended/models/base_address_extended.py
+++ b/addons/base_address_extended/models/base_address_extended.py
@@ -5,7 +5,6 @@ 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 +134,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 pycompat.items(vals):
+ for k, v in vals.items():
partner[k] = v
diff --git a/addons/base_automation/models/base_automation.py b/addons/base_automation/models/base_automation.py
index 061828c161e..9896a9706d8 100644
--- a/addons/base_automation/models/base_automation.py
+++ b/addons/base_automation/models/base_automation.py
@@ -12,7 +12,6 @@ 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__)
@@ -264,7 +263,7 @@ class BaseAutomation(models.Model):
if res:
if 'value' in res:
res['value'].pop('id', None)
- self.update({key: val for key, val in pycompat.items(res['value']) if key in self._fields})
+ self.update({key: val for key, val in res['value'].items() 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 98df9356576..d6e5ce9176a 100644
--- a/addons/base_gengo/wizard/base_gengo_translations.py
+++ b/addons/base_gengo/wizard/base_gengo_translations.py
@@ -8,7 +8,6 @@ import uuid
from odoo import api, fields, models, tools, _
from odoo.exceptions import UserError, ValidationError
-from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -194,7 +193,7 @@ class BaseGengoTranslations(models.TransientModel):
term_ids.write(vals)
jobs = response.get('jobs', [])
if jobs:
- for t_id, job in pycompat.items(jobs):
+ for t_id, job in jobs.items():
self._update_terms_job(job)
return
diff --git a/addons/base_import/models/base_import.py b/addons/base_import/models/base_import.py
index 85fb49924f6..19941858dfa 100644
--- a/addons/base_import/models/base_import.py
+++ b/addons/base_import/models/base_import.py
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-import csv
import datetime
import io
import itertools
@@ -17,12 +16,6 @@ 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, pycompat
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-
-
FIELDS_RECURSION_LIMIT = 2
ERROR_PREVIEW_BYTES = 200
_logger = logging.getLogger(__name__)
@@ -49,7 +42,7 @@ FILE_TYPE_DICT = {
}
EXTENSIONS = {
'.' + ext: handler
- for mime, (ext, handler, req) in pycompat.items(FILE_TYPE_DICT)
+ for mime, (ext, handler, req) in FILE_TYPE_DICT.items()
}
@@ -121,7 +114,7 @@ class Import(models.TransientModel):
}]
model_fields = Model.fields_get()
blacklist = models.MAGIC_COLUMNS + [Model.CONCURRENCY_CHECK_FIELD]
- for name, field in pycompat.items(model_fields):
+ for name, field in model_fields.items():
if name in blacklist:
continue
# an empty string means the field is deprecated, @deprecated must
@@ -134,7 +127,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(pycompat.values(states))):
+ for attr, value in itertools.chain.from_iterable(states.values())):
continue
field_value = {
'id': name,
@@ -214,9 +207,9 @@ class Import(models.TransientModel):
if cell.ctype is xlrd.XL_CELL_NUMBER:
is_float = cell.value % 1 != 0.0
values.append(
- unicode(cell.value)
+ pycompat.text_type(cell.value)
if is_float
- else unicode(int(cell.value))
+ else pycompat.text_type(int(cell.value))
)
elif cell.ctype is xlrd.XL_CELL_DATE:
is_datetime = cell.value % 1 != 0.0
@@ -268,14 +261,13 @@ class Import(models.TransientModel):
# csv module expect utf-8, see http://docs.python.org/2/library/csv.html
csv_data = csv_data.decode(encoding).encode('utf-8')
- csv_iterator = csv.reader(
- StringIO(csv_data),
+ csv_iterator = pycompat.csv_reader(
+ io.BytesIO(csv_data),
quotechar=str(options['quoting']),
delimiter=str(options['separator']))
return (
- [item.decode('utf-8') for item in row]
- for row in csv_iterator
+ row for row in csv_iterator
if any(x for x in row if x.strip())
)
@@ -603,7 +595,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 pycompat.items(all_fields):
+ for name, field in all_fields.items():
if field['type'] in ('date', 'datetime') and name in import_fields:
# Parse date
index = import_fields.index(name)
@@ -611,11 +603,13 @@ class Import(models.TransientModel):
server_format = DEFAULT_SERVER_DATE_FORMAT if field['type'] == 'date' else DEFAULT_SERVER_DATETIME_FORMAT
if options.get('%s_format' % field['type'], server_format) != server_format:
- user_format = ustr(options.get('%s_format' % field['type'])).encode('utf-8')
+ # datetime.str[fp]time takes *native strings* in both
+ # versions, for both data and pattern
+ user_format = pycompat.to_native(options.get('%s_format' % field['type']))
for num, line in enumerate(data):
if line[index]:
try:
- line[index] = dt.strftime(dt.strptime(ustr(line[index].strip()).encode('utf-8'), user_format), server_format)
+ line[index] = dt.strftime(dt.strptime(pycompat.to_native(line[index].strip()), user_format), server_format)
except ValueError as e:
raise ValueError(_("Column %s contains incorrect values. Error in line %d: %s") % (name, num + 1, e))
except Exception as e:
@@ -659,7 +653,7 @@ class Import(models.TransientModel):
except ValueError as error:
return [{
'type': 'error',
- 'message': unicode(error),
+ 'message': pycompat.text_type(error),
'record': False,
}]
diff --git a/addons/base_import/models/odf_ods_reader.py b/addons/base_import/models/odf_ods_reader.py
index c2613c6baa5..daa7d2b16e2 100644
--- a/addons/base_import/models/odf_ods_reader.py
+++ b/addons/base_import/models/odf_ods_reader.py
@@ -19,8 +19,6 @@ from odf import opendocument
from odf.table import Table, TableRow, TableCell
from odf.text import P
-from odoo.tools import pycompat
-
class ODSReader(object):
@@ -97,4 +95,4 @@ class ODSReader(object):
return self.SHEETS[name]
def getFirstSheet(self):
- return next(iter(pycompat.values(self.SHEETS)))
+ return next(iter(self.SHEETS.values()))
diff --git a/addons/base_import/tests/test_base_import.py b/addons/base_import/tests/test_base_import.py
index 2d1d850d3a9..cf324b44abd 100644
--- a/addons/base_import/tests/test_base_import.py
+++ b/addons/base_import/tests/test_base_import.py
@@ -1,13 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-import csv
+import base64
import io
import unittest
from odoo.tests.common import TransactionCase, can_import
from odoo.modules.module import get_module_resource
-from odoo.tools import mute_logger
+from odoo.tools import mute_logger, pycompat
ID_FIELD = {
'id': 'id',
@@ -231,10 +230,10 @@ class TestPreview(TransactionCase):
def test_csv_success(self):
import_wizard = self.env['base_import.import'].create({
'res_model': 'base_import.tests.models.preview',
- 'file': 'name,Some Value,Counter\n'
- 'foo,1,2\n'
- 'bar,3,4\n'
- 'qux,5,6\n',
+ 'file': b'name,Some Value,Counter\n'
+ b'foo,1,2\n'
+ b'bar,3,4\n'
+ b'qux,5,6\n',
'file_type': 'text/csv'
})
@@ -359,10 +358,10 @@ class test_convert_import_data(TransactionCase):
def test_all(self):
import_wizard = self.env['base_import.import'].create({
'res_model': 'base_import.tests.models.preview',
- 'file': 'name,Some Value,Counter\n'
- 'foo,1,2\n'
- 'bar,3,4\n'
- 'qux,5,6\n',
+ 'file': b'name,Some Value,Counter\n'
+ b'foo,1,2\n'
+ b'bar,3,4\n'
+ b'qux,5,6\n',
'file_type': 'text/csv'
})
@@ -381,8 +380,8 @@ class test_convert_import_data(TransactionCase):
def test_date_fields(self):
import_wizard = self.env['base_import.import'].create({
'res_model': 'res.partner',
- 'file': 'name,date,create_date\n'
- '"foo","2013年07月18日","2016-10-12 06:06"\n',
+ 'file': u'name,date,create_date\n'
+ u'"foo","2013年07月18日","2016-10-12 06:06"\n'.encode('utf-8'),
'file_type': 'text/csv'
})
@@ -409,10 +408,10 @@ class test_convert_import_data(TransactionCase):
"""
import_wizard = self.env['base_import.import'].create({
'res_model': 'base_import.tests.models.preview',
- 'file': 'name,Some Value,Counter\n'
- 'foo,1,2\n'
- 'bar,3,4\n'
- 'qux,5,6\n',
+ 'file': b'name,Some Value,Counter\n'
+ b'foo,1,2\n'
+ b'bar,3,4\n'
+ b'qux,5,6\n',
'file_type': 'text/csv'
})
data, fields = import_wizard._convert_import_data(
@@ -433,10 +432,10 @@ class test_convert_import_data(TransactionCase):
"""
import_wizard = self.env['base_import.import'].create({
'res_model': 'base_import.tests.models.preview',
- 'file': 'name,Some Value,Counter\n'
- 'foo,1,2\n'
- ',3,\n'
- ',5,6\n',
+ 'file': b'name,Some Value,Counter\n'
+ b'foo,1,2\n'
+ b',3,\n'
+ b',5,6\n',
'file_type': 'text/csv'
})
data, fields = import_wizard._convert_import_data(
@@ -453,12 +452,12 @@ class test_convert_import_data(TransactionCase):
def test_empty_rows(self):
import_wizard = self.env['base_import.import'].create({
'res_model': 'base_import.tests.models.preview',
- 'file': 'name,Some Value\n'
- 'foo,1\n'
- '\n'
- 'bar,2\n'
- ' \n'
- '\t \n',
+ 'file': b'name,Some Value\n'
+ b'foo,1\n'
+ b'\n'
+ b'bar,2\n'
+ b' \n'
+ b'\t \n',
'file_type': 'text/csv'
})
data, fields = import_wizard._convert_import_data(
@@ -475,8 +474,8 @@ class test_convert_import_data(TransactionCase):
def test_nofield(self):
import_wizard = self.env['base_import.import'].create({
'res_model': 'base_import.tests.models.preview',
- 'file': 'name,Some Value,Counter\n'
- 'foo,1,2\n',
+ 'file': b'name,Some Value,Counter\n'
+ b'foo,1,2\n',
'file_type': 'text/csv'
})
@@ -485,8 +484,8 @@ class test_convert_import_data(TransactionCase):
def test_falsefields(self):
import_wizard = self.env['base_import.import'].create({
'res_model': 'base_import.tests.models.preview',
- 'file': 'name,Some Value,Counter\n'
- 'foo,1,2\n',
+ 'file': b'name,Some Value,Counter\n'
+ b'foo,1,2\n',
'file_type': 'text/csv'
})
@@ -501,11 +500,11 @@ class test_convert_import_data(TransactionCase):
Ensure importing keep newlines
"""
output = io.BytesIO()
- writer = csv.writer(output, quoting=csv.QUOTE_ALL)
+ writer = pycompat.csv_writer(output, quoting=1)
- data_row = ["\tfoo\n\tbar", " \"hello\" \n\n 'world' "]
+ data_row = [u"\tfoo\n\tbar", u" \"hello\" \n\n 'world' "]
- writer.writerow(["name", "Some Value"])
+ writer.writerow([u"name", u"Some Value"])
writer.writerow(data_row)
import_wizard = self.env['base_import.import'].create({
@@ -527,16 +526,15 @@ class test_failures(TransactionCase):
Ensure big fields (e.g. b64-encoded image data) can be imported and
we're not hitting limits of the default CSV parser config
"""
- import csv, cStringIO
from PIL import Image
im = Image.new('RGB', (1920, 1080))
- fout = cStringIO.StringIO()
+ fout = io.BytesIO()
- writer = csv.writer(fout, dialect=None)
+ writer = pycompat.csv_writer(fout, dialect=None)
writer.writerows([
- ['name', 'db_datas'],
- ['foo', im.tobytes().encode('base64')]
+ [u'name', u'db_datas'],
+ [u'foo', base64.b64encode(im.tobytes()).decode('ascii')]
])
import_wizard = self.env['base_import.import'].create({
diff --git a/addons/base_import_module/models/ir_module.py b/addons/base_import_module/models/ir_module.py
index ec94bc22d27..5bad5ea53eb 100644
--- a/addons/base_import_module/models/ir_module.py
+++ b/addons/base_import_module/models/ir_module.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+import base64
import logging
import os
import sys
@@ -8,7 +9,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, pycompat
+from odoo.tools import convert_file, exception_to_unicode
from odoo.tools.osutil import tempdir
_logger = logging.getLogger(__name__)
@@ -63,8 +64,8 @@ class IrModule(models.Model):
for root, dirs, files in os.walk(path_static):
for static_file in files:
full_path = opj(root, static_file)
- with open(full_path, 'r') as fp:
- data = fp.read().encode('base64')
+ with open(full_path, 'rb') as fp:
+ data = base64.b64encode(fp.read())
url_path = '/%s%s' % (module, full_path.split(path)[1].replace(os.path.sep, '/'))
url_path = url_path.decode(sys.getfilesystemencoding())
filename = os.path.split(url_path)[1]
@@ -118,6 +119,6 @@ class IrModule(models.Model):
finally:
addons.module.ad_paths.remove(module_dir)
r = ["Successfully imported module '%s'" % mod for mod in success]
- for mod, error in pycompat.items(errors):
+ for mod, error in errors.items():
r.append("Error while importing module '%s': %r" % (mod, error))
return '\n'.join(r), module_names
diff --git a/addons/board/controllers/main.py b/addons/board/controllers/main.py
index 2ba236ea109..ebd9b568583 100644
--- a/addons/board/controllers/main.py
+++ b/addons/board/controllers/main.py
@@ -29,7 +29,7 @@ class Board(Controller):
'domain': str(domain)
})
column.insert(0, new_action)
- arch = ElementTree.tostring(xml, 'utf-8')
+ arch = ElementTree.tostring(xml, encoding='unicode')
request.env['ir.ui.view.custom'].create({
'user_id': request.session.uid,
'ref_id': view_id,
diff --git a/addons/board/models/board.py b/addons/board/models/board.py
index 3acd277826a..ebbbec74e2c 100644
--- a/addons/board/models/board.py
+++ b/addons/board/models/board.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 Board(models.AbstractModel):
@@ -41,13 +42,8 @@ class Board(models.AbstractModel):
if child.tag == 'action' and child.get('invisible'):
node.remove(child)
else:
- child = remove_unauthorized_children(child)
+ remove_unauthorized_children(child)
return node
- def encode(s):
- if isinstance(s, unicode):
- return s.encode('utf8')
- return s
-
- archnode = etree.fromstring(encode(arch))
- return etree.tostring(remove_unauthorized_children(archnode), pretty_print=True)
+ archnode = etree.fromstring(arch)
+ return etree.tostring(remove_unauthorized_children(archnode), pretty_print=True, encoding='unicode')
diff --git a/addons/bus/controllers/main.py b/addons/bus/controllers/main.py
index 40118764751..4e82116e7c7 100644
--- a/addons/bus/controllers/main.py
+++ b/addons/bus/controllers/main.py
@@ -4,6 +4,8 @@ from odoo import exceptions
from odoo.http import Controller, request, route
from odoo.addons.bus.models.bus import dispatch
+from odoo.tools import pycompat
+
class BusController(Controller):
""" Examples:
@@ -14,7 +16,7 @@ class BusController(Controller):
@route('/longpolling/send', type="json", auth="public")
def send(self, channel, message):
- if not isinstance(channel, basestring):
+ if not isinstance(channel, pycompat.string_types):
raise Exception("bus.Bus only string channels are allowed.")
return request.env['bus.bus'].sendone(channel, message)
@@ -33,7 +35,7 @@ class BusController(Controller):
options = {}
if not dispatch:
raise Exception("bus.Bus unavailable")
- if [c for c in channels if not isinstance(c, basestring)]:
+ if [c for c in channels if not isinstance(c, pycompat.string_types)]:
raise Exception("bus.Bus only string channels are allowed.")
if request.registry.in_test_mode():
raise exceptions.UserError("bus.Bus not available in test mode")
diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py
index 9c8b088aeb1..e80a2727282 100644
--- a/addons/calendar/models/calendar.py
+++ b/addons/calendar/models/calendar.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import base64
import babel.dates
import collections
@@ -33,7 +34,7 @@ def calendar_id2real_id(calendar_id=None, with_date=False):
:param with_date: if a value is passed to this param it will return dates based on value of withdate + calendar_id
:return: real event id
"""
- if calendar_id and isinstance(calendar_id, (basestring)):
+ if calendar_id and isinstance(calendar_id, pycompat.string_types):
res = [bit for bit in calendar_id.split('-') if bit]
if len(res) == 2:
real_id = res[0]
@@ -47,7 +48,7 @@ def calendar_id2real_id(calendar_id=None, with_date=False):
def get_real_ids(ids):
- if isinstance(ids, (basestring, pycompat.integer_types)):
+ if isinstance(ids, (pycompat.string_types, pycompat.integer_types)):
return calendar_id2real_id(ids)
if isinstance(ids, (list, tuple)):
@@ -173,6 +174,7 @@ class Attendee(models.Model):
mails_to_send = self.env['mail.mail']
for attendee in self:
if attendee.email or attendee.partner_id.email:
+ # FIXME: is ics_file text or bytes?
ics_file = ics_files.get(attendee.event_id.id)
mail_id = invitation_template.send_mail(attendee.id)
@@ -181,7 +183,7 @@ class Attendee(models.Model):
vals['attachment_ids'] = [(0, 0, {'name': 'invitation.ics',
'mimetype': 'text/calendar',
'datas_fname': 'invitation.ics',
- 'datas': str(ics_file).encode('base64')})]
+ 'datas': base64.b64encode(ics_file)})]
vals['model'] = None # We don't want to have the mail in the tchatter while in queue!
vals['res_id'] = False
current_mail = self.env['mail.mail'].browse(mail_id)
@@ -468,7 +470,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(pycompat.items(_interval_selection)), 'Unit', required=True, default='hours')
+ interval = fields.Selection(list(_interval_selection.items()), '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')
@@ -630,8 +632,8 @@ class Meeting(models.Model):
}
# formats will be used for str{f,p}time() which do not support unicode in Python 2, coerce to str
- format_date = lang_params.get("date_format", '%B-%d-%Y').encode('utf-8')
- format_time = lang_params.get("time_format", '%I-%M %p').encode('utf-8')
+ format_date = pycompat.to_native(lang_params.get("date_format", '%B-%d-%Y'))
+ format_time = pycompat.to_native(lang_params.get("time_format", '%I-%M %p'))
return (format_date, format_time)
@api.model
@@ -917,11 +919,11 @@ class Meeting(models.Model):
elif interval == 'minutes':
delta = timedelta(minutes=duration)
trigger.value = delta
- valarm.add('DESCRIPTION').value = alarm.name or 'Odoo'
+ valarm.add('DESCRIPTION').value = alarm.name or u'Odoo'
for attendee in meeting.attendee_ids:
attendee_add = event.add('attendee')
- attendee_add.value = 'MAILTO:' + (attendee.email or '')
- result[meeting.id] = cal.serialize()
+ attendee_add.value = u'MAILTO:' + (attendee.email or u'')
+ result[meeting.id] = cal.serialize().encode('utf-8')
return result
@@ -1058,7 +1060,7 @@ class Meeting(models.Model):
pile.reverse()
new_pile = []
for item in pile:
- if not isinstance(item, basestring):
+ if not isinstance(item, pycompat.string_types):
res = item
elif str(item) == str('&'):
first = new_pile.pop()
@@ -1207,7 +1209,7 @@ class Meeting(models.Model):
if interval == 'day':
# Day number (1-31)
- result = unicode(date.day)
+ result = pycompat.text_type(date.day)
elif interval == 'month':
# Localized month name and year
@@ -1219,6 +1221,7 @@ class Meeting(models.Model):
elif interval == 'time':
# Localized time
+ # FIXME: formats are specifically encoded to bytes, maybe use babel?
dummy, format_time = self._get_date_formats()
result = tools.ustr(date.strftime(format_time + " %Z"))
@@ -1289,7 +1292,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(pycompat.values(id_map)))
+ real = self.browse(set(id_map.values()))
super(Meeting, real)._get_message_unread()
for event in self:
if event.id == id_map[event.id]:
@@ -1301,7 +1304,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(pycompat.values(id_map)))
+ real = self.browse(set(id_map.values()))
super(Meeting, real)._get_message_needaction()
for event in self:
if event.id == id_map[event.id]:
@@ -1314,7 +1317,7 @@ class Meeting(models.Model):
@api.returns('self', lambda value: value.id)
def message_post(self, **kwargs):
thread_id = self.id
- if isinstance(self.id, basestring):
+ if isinstance(self.id, pycompat.string_types):
thread_id = get_real_ids(self.id)
if self.env.context.get('default_date'):
context = dict(self.env.context)
@@ -1350,7 +1353,7 @@ class Meeting(models.Model):
for arg in args:
if arg[0] == 'id':
for n, calendar_id in enumerate(arg[2]):
- if isinstance(calendar_id, basestring):
+ if isinstance(calendar_id, pycompat.string_types):
arg[2][n] = calendar_id.split('-')[0]
return super(Meeting, self)._name_search(name=name, args=args, operator=operator, limit=limit, name_get_uid=name_get_uid)
@@ -1388,7 +1391,7 @@ class Meeting(models.Model):
# set end_date for calendar searching
if any(field in values for field in ['recurrency', 'end_type', 'count', 'rrule_type', 'start', 'stop']):
for real_meeting in real_meetings:
- if real_meeting.recurrency and real_meeting.end_type in ('count', unicode('count')):
+ if real_meeting.recurrency and real_meeting.end_type == u'count':
final_date = real_meeting._get_recurrency_end_date()
super(Meeting, real_meeting).write({'final_date': final_date})
@@ -1472,7 +1475,7 @@ class Meeting(models.Model):
for calendar_id, real_id in select:
res = real_data[real_id].copy()
ls = calendar_id2real_id(calendar_id, with_date=res and res.get('duration', 0) > 0 and res.get('duration') or 1)
- if not isinstance(ls, (basestring, pycompat.integer_types)) and len(ls) >= 2:
+ if not isinstance(ls, (pycompat.string_types, pycompat.integer_types)) and len(ls) >= 2:
res['start'] = ls[1]
res['stop'] = ls[2]
diff --git a/addons/calendar/models/ir_attachment.py b/addons/calendar/models/ir_attachment.py
index c98242db7e7..ae734ec59f4 100644
--- a/addons/calendar/models/ir_attachment.py
+++ b/addons/calendar/models/ir_attachment.py
@@ -5,6 +5,8 @@ from odoo import api, models
from odoo.addons.calendar.models.calendar import get_real_ids
+from odoo.tools import pycompat
+
class Attachment(models.Model):
@@ -16,13 +18,13 @@ class Attachment(models.Model):
args = list(args)
if any([leaf for leaf in args if leaf[0] == "res_model" and leaf[2] == 'calendar.event']):
for index in range(len(args)):
- if args[index][0] == "res_id" and isinstance(args[index][2], basestring):
+ if args[index][0] == "res_id" and isinstance(args[index][2], pycompat.string_types):
args[index] = (args[index][0], args[index][1], get_real_ids(args[index][2]))
return super(Attachment, self).search(args, offset=offset, limit=limit, order=order, count=count)
@api.multi
def write(self, vals):
""" When posting an attachment (new or not), convert the virtual ids in real ids. """
- if isinstance(vals.get('res_id'), basestring):
+ if isinstance(vals.get('res_id'), pycompat.string_types):
vals['res_id'] = get_real_ids(vals.get('res_id'))
return super(Attachment, self).write(vals)
diff --git a/addons/calendar/models/mail_message.py b/addons/calendar/models/mail_message.py
index 0dc4d24e364..26be5080ab5 100644
--- a/addons/calendar/models/mail_message.py
+++ b/addons/calendar/models/mail_message.py
@@ -5,6 +5,8 @@ from odoo import api, models
from odoo.addons.calendar.models.calendar import get_real_ids
+from odoo.tools import pycompat
+
class Message(models.Model):
@@ -16,7 +18,7 @@ class Message(models.Model):
args = list(args)
for index in range(len(args)):
if args[index][0] == "res_id":
- if isinstance(args[index][2], basestring):
+ if isinstance(args[index][2], pycompat.string_types):
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], [get_real_ids(x) for x in args[index][2]])
diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py
index 040ddd097d3..76f8a286205 100644
--- a/addons/crm/models/crm_lead.py
+++ b/addons/crm/models/crm_lead.py
@@ -298,9 +298,9 @@ class Lead(models.Model):
# stage change with new stage: update probability and date_closed
if vals.get('stage_id') and 'probability' not in vals:
vals.update(self._onchange_stage_id_values(vals.get('stage_id')))
- if vals.get('probability') >= 100 or not vals.get('active', True):
+ if vals.get('probability', 0) >= 100 or not vals.get('active', True):
vals['date_closed'] = fields.Datetime.now()
- elif 'probability' in vals and vals['probability'] < 100:
+ elif 'probability' in vals:
vals['date_closed'] = False
return super(Lead, self).write(vals)
diff --git a/addons/crm/wizard/base_partner_merge.py b/addons/crm/wizard/base_partner_merge.py
index 7751e0106ed..a91b9ec3dc1 100644
--- a/addons/crm/wizard/base_partner_merge.py
+++ b/addons/crm/wizard/base_partner_merge.py
@@ -2,56 +2,18 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from ast import literal_eval
-from email.utils import parseaddr
import functools
-try:
- from html.entities import entitydefs
-except ImportError:
- from htmlentitydefs import entitydefs # pylint: disable=deprecated-module
import itertools
import logging
-import operator
import psycopg2
-import re
-
-# Validation Library https://pypi.python.org/pypi/validate_email/1.1
-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, pycompat
+from odoo.tools import mute_logger
_logger = logging.getLogger('base.partner.merge')
-
-# http://www.php2python.com/wiki/function.html-entity-decode/
-def html_entity_decode_char(m, defs=entitydefs):
- try:
- return defs[m.group(1)]
- except KeyError:
- return m.group(0)
-
-
-def html_entity_decode(string):
- pattern = re.compile("&(\w+?);")
- return pattern.sub(html_entity_decode_char, string)
-
-
-def sanitize_email(email):
- assert isinstance(email, basestring) and email
-
- result = re.subn(r';|/|:', ',', html_entity_decode(email or ''))[0].split(',')
-
- emails = [parseaddr(email)[1]
- for item in result
- for email in item.split()]
-
- return [email.lower()
- for email in emails
- if validate_email(email)]
-
-
class MergePartnerLine(models.TransientModel):
_name = 'base.partner.merge.line'
@@ -271,7 +233,7 @@ class MergePartnerAutomatic(models.TransientModel):
return item
# get all fields that are not computed or x2many
values = dict()
- for column, field in pycompat.items(model_fields):
+ for column, field in model_fields.items():
if field.type not in ('many2many', 'one2many') and field.compute is None:
for item in itertools.chain(src_partners, [dst_partner]):
if item[column]:
@@ -409,7 +371,7 @@ class MergePartnerAutomatic(models.TransientModel):
"""
return any(
self.env[model].search_count([(field, 'in', aggr_ids)])
- for model, field in pycompat.items(models)
+ for model, field in models.items()
)
@api.model
diff --git a/addons/crm/wizard/validate_email.py b/addons/crm/wizard/validate_email.py
deleted file mode 100644
index 38de5743fca..00000000000
--- a/addons/crm/wizard/validate_email.py
+++ /dev/null
@@ -1,123 +0,0 @@
-# RFC 2822 - style email validation for Python
-# (c) 2012 Syrus Akbary
-# Extended from (c) 2011 Noel Bush
-# for support of mx and user check
-# This code is made available to you under the GNU LGPL v3.
-#
-# This module provides a single method, valid_email_address(),
-# which returns True or False to indicate whether a given address
-# is valid according to the 'addr-spec' part of the specification
-# given in RFC 2822. Ideally, we would like to find this
-# in some other library, already thoroughly tested and well-
-# maintained. The standard Python library email.utils
-# contains a parse_addr() function, but it is not sufficient
-# to detect many malformed addresses.
-#
-# This implementation aims to be faithful to the RFC, with the
-# exception of a circular definition (see comments below), and
-# with the omission of the pattern components marked as "obsolete".
-
-import re
-import smtplib
-import socket
-
-try:
- import DNS
- ServerError = DNS.ServerError
-except:
- DNS = None
- class ServerError(Exception): pass
-# All we are really doing is comparing the input string to one
-# gigantic regular expression. But building that regexp, and
-# ensuring its correctness, is made much easier by assembling it
-# from the "tokens" defined by the RFC. Each of these tokens is
-# tested in the accompanying unit test file.
-#
-# The section of RFC 2822 from which each pattern component is
-# derived is given in an accompanying comment.
-#
-# (To make things simple, every string below is given as 'raw',
-# even when it's not strictly necessary. This way we don't forget
-# when it is necessary.)
-#
-WSP = r'[ \t]' # see 2.2.2. Structured Header Field Bodies
-CRLF = r'(?:\r\n)' # see 2.2.3. Long Header Fields
-NO_WS_CTL = r'\x01-\x08\x0b\x0c\x0f-\x1f\x7f' # see 3.2.1. Primitive Tokens
-QUOTED_PAIR = r'(?:\\.)' # see 3.2.2. Quoted characters
-FWS = r'(?:(?:' + WSP + r'*' + CRLF + r')?' + \
- WSP + r'+)' # see 3.2.3. Folding white space and comments
-CTEXT = r'[' + NO_WS_CTL + \
- r'\x21-\x27\x2a-\x5b\x5d-\x7e]' # see 3.2.3
-CCONTENT = r'(?:' + CTEXT + r'|' + \
- QUOTED_PAIR + r')' # see 3.2.3 (NB: The RFC includes COMMENT here
- # as well, but that would be circular.)
-COMMENT = r'\((?:' + FWS + r'?' + CCONTENT + \
- r')*' + FWS + r'?\)' # see 3.2.3
-CFWS = r'(?:' + FWS + r'?' + COMMENT + ')*(?:' + \
- FWS + '?' + COMMENT + '|' + FWS + ')' # see 3.2.3
-ATEXT = r'[\w!#$%&\'\*\+\-/=\?\^`\{\|\}~]' # see 3.2.4. Atom
-ATOM = CFWS + r'?' + ATEXT + r'+' + CFWS + r'?' # see 3.2.4
-DOT_ATOM_TEXT = ATEXT + r'+(?:\.' + ATEXT + r'+)*' # see 3.2.4
-DOT_ATOM = CFWS + r'?' + DOT_ATOM_TEXT + CFWS + r'?' # see 3.2.4
-QTEXT = r'[' + NO_WS_CTL + \
- r'\x21\x23-\x5b\x5d-\x7e]' # see 3.2.5. Quoted strings
-QCONTENT = r'(?:' + QTEXT + r'|' + \
- QUOTED_PAIR + r')' # see 3.2.5
-QUOTED_STRING = CFWS + r'?' + r'"(?:' + FWS + \
- r'?' + QCONTENT + r')*' + FWS + \
- r'?' + r'"' + CFWS + r'?'
-LOCAL_PART = r'(?:' + DOT_ATOM + r'|' + \
- QUOTED_STRING + r')' # see 3.4.1. Addr-spec specification
-DTEXT = r'[' + NO_WS_CTL + r'\x21-\x5a\x5e-\x7e]' # see 3.4.1
-DCONTENT = r'(?:' + DTEXT + r'|' + \
- QUOTED_PAIR + r')' # see 3.4.1
-DOMAIN_LITERAL = CFWS + r'?' + r'\[' + \
- r'(?:' + FWS + r'?' + DCONTENT + \
- r')*' + FWS + r'?\]' + CFWS + r'?' # see 3.4.1
-DOMAIN = r'(?:' + DOT_ATOM + r'|' + \
- DOMAIN_LITERAL + r')' # see 3.4.1
-ADDR_SPEC = LOCAL_PART + r'@' + DOMAIN # see 3.4.1
-
-# A valid address will match exactly the 3.4.1 addr-spec.
-VALID_ADDRESS_REGEXP = '^' + ADDR_SPEC + '$'
-
-def validate_email(email, check_mx=False,verify=False):
-
- """Indicate whether the given string is a valid email address
- according to the 'addr-spec' portion of RFC 2822 (see section
- 3.4.1). Parts of the spec that are marked obsolete are *not*
- included in this test, and certain arcane constructions that
- depend on circular definitions in the spec may not pass, but in
- general this should correctly identify any email address likely
- to be in use as of 2011."""
- try:
- assert re.match(VALID_ADDRESS_REGEXP, email) is not None
- check_mx |= verify
- if check_mx:
- if not DNS: raise Exception('For check the mx records or check if the email exists you must have installed pyDNS python package')
- DNS.DiscoverNameServers()
- hostname = email[email.find('@')+1:]
- mx_hosts = DNS.mxlookup(hostname)
- for mx in mx_hosts:
- try:
- smtp = smtplib.SMTP()
- smtp.connect(mx[1])
- if not verify: return True
- status, _ = smtp.helo()
- if status != 250: continue
- smtp.mail('')
- status, _ = smtp.rcpt(email)
- if status != 250: return False
- break
- except smtplib.SMTPServerDisconnected: #Server not permits verify user
- break
- except smtplib.SMTPConnectError:
- continue
- except (AssertionError, ServerError):
- return False
- return True
-
-# import sys
-
-# sys.modules[__name__],sys.modules['validate_email_module'] = validate_email,sys.modules[__name__]
-# from validate_email_module import *
diff --git a/addons/document/models/ir_attachment.py b/addons/document/models/ir_attachment.py
index 53ae1f1f463..2bc6aee234d 100644
--- a/addons/document/models/ir_attachment.py
+++ b/addons/document/models/ir_attachment.py
@@ -1,30 +1,16 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import io
import logging
import PyPDF2
import xml.dom.minidom
import zipfile
-from StringIO import StringIO
-
from odoo import api, models
_logger = logging.getLogger(__name__)
FTYPES = ['docx', 'pptx', 'xlsx', 'opendoc', 'pdf']
-# Keep function in case it is necessary to do toUnicode(buf.encode('ascii', 'replace'))
-def toUnicode(s):
- try:
- return s.decode('utf-8')
- except UnicodeError:
- try:
- return s.decode('latin')
- except UnicodeError:
- try:
- return s.encode('ascii')
- except UnicodeError:
- return s
-
def textToString(element):
buff = u""
for node in element.childNodes:
@@ -41,7 +27,7 @@ class IrAttachment(models.Model):
def _index_docx(self, bin_data):
'''Index Microsoft .docx documents'''
buf = u""
- f = StringIO(bin_data)
+ f = io.BytesIO(bin_data)
if zipfile.is_zipfile(f):
try:
zf = zipfile.ZipFile(f)
@@ -57,7 +43,7 @@ class IrAttachment(models.Model):
'''Index Microsoft .pptx documents'''
buf = u""
- f = StringIO(bin_data)
+ f = io.BytesIO(bin_data)
if zipfile.is_zipfile(f):
try:
zf = zipfile.ZipFile(f)
@@ -75,7 +61,7 @@ class IrAttachment(models.Model):
'''Index Microsoft .xlsx documents'''
buf = u""
- f = StringIO(bin_data)
+ f = io.BytesIO(bin_data)
if zipfile.is_zipfile(f):
try:
zf = zipfile.ZipFile(f)
@@ -91,7 +77,7 @@ class IrAttachment(models.Model):
'''Index OpenDocument documents (.odt, .ods...)'''
buf = u""
- f = StringIO(bin_data)
+ f = io.BytesIO(bin_data)
if zipfile.is_zipfile(f):
try:
zf = zipfile.ZipFile(f)
@@ -107,8 +93,8 @@ class IrAttachment(models.Model):
'''Index PDF documents'''
buf = u""
- if bin_data.startswith('%PDF-'):
- f = StringIO(bin_data)
+ if bin_data.startswith(b'%PDF-'):
+ f = io.BytesIO(bin_data)
try:
pdf = PyPDF2.PdfFileReader(f, overwriteWarnings=False)
for page in pdf.pages:
diff --git a/addons/event/models/event.py b/addons/event/models/event.py
index 95310a67122..c5f7f43915a 100644
--- a/addons/event/models/event.py
+++ b/addons/event/models/event.py
@@ -5,7 +5,6 @@ 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
@@ -389,7 +388,7 @@ class EventRegistration(models.Model):
'partner_id': partner_id.id,
'event_id': event_id and event_id.id or False,
}
- data.update({key: value for key, value in pycompat.items(registration) if key in self._fields})
+ data.update({key: value for key, value in registration.items() if key in self._fields})
return data
@api.one
diff --git a/addons/fleet/models/fleet_vehicle_cost.py b/addons/fleet/models/fleet_vehicle_cost.py
index 12c6a7d7f2e..827863e3adc 100644
--- a/addons/fleet/models/fleet_vehicle_cost.py
+++ b/addons/fleet/models/fleet_vehicle_cost.py
@@ -6,8 +6,6 @@ from odoo.exceptions import UserError
from dateutil.relativedelta import relativedelta
-from odoo.tools import pycompat
-
class FleetVehicleCost(models.Model):
_name = 'fleet.vehicle.cost'
@@ -264,7 +262,7 @@ class FleetVehicleLogContract(models.Model):
res[contract.vehicle_id.id] = 1
Vehicle = self.env['fleet.vehicle']
- for vehicle, value in pycompat.items(res):
+ for vehicle, value in res.items():
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 56c447dd9d4..3c062401662 100644
--- a/addons/gamification/models/goal.py
+++ b/addons/gamification/models/goal.py
@@ -262,7 +262,7 @@ class Goal(models.Model):
for goal in self:
goals_by_definition.setdefault(goal.definition_id, []).append(goal)
- for definition, goals in pycompat.items(goals_by_definition):
+ for definition, goals in goals_by_definition.items():
goals_to_write = {}
if definition.computation_mode == 'manually':
for goal in goals:
@@ -307,9 +307,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 pycompat.items(subqueries):
+ for (start_date, end_date), query_goals in subqueries.items():
subquery_domain = list(general_domain)
- subquery_domain.append((field_name, 'in', list(set(pycompat.values(query_goals)))))
+ subquery_domain.append((field_name, 'in', list(set(query_goals.values()))))
if start_date:
subquery_domain.append((field_date_name, '>=', start_date))
if end_date:
@@ -353,7 +353,7 @@ class Goal(models.Model):
goals_to_write.update(goal._get_write_values(new_value))
- for goal, values in pycompat.items(goals_to_write):
+ for goal, values in goals_to_write.items():
if not values:
continue
goal.write(values)
diff --git a/addons/google_calendar/models/google_calendar.py b/addons/google_calendar/models/google_calendar.py
index 0303dbe5154..ac91835f530 100644
--- a/addons/google_calendar/models/google_calendar.py
+++ b/addons/google_calendar/models/google_calendar.py
@@ -12,7 +12,7 @@ import pytz
from werkzeug import urls
from odoo import api, fields, models, tools, _
-from odoo.tools import exception_to_unicode, pycompat
+from odoo.tools import exception_to_unicode
_logger = logging.getLogger(__name__)
@@ -25,15 +25,13 @@ 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 = {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)}
+ methods = {k: v for k, v in attrs.items() if callable(v)}
+ attrs = {k: v for k, v in attrs.items() if not callable(v)}
def init(self, **kw):
- for key, val in pycompat.items(attrs):
+ for key, val in attrs.items():
setattr(self, key, val)
- for key, val in pycompat.items(kw):
+ for key, val in kw.items():
assert key in attrs
setattr(self, key, val)
@@ -161,7 +159,7 @@ class SyncOperation(object):
def __init__(self, src, info, **kw):
self.src = src
self.info = info
- for key, val in pycompat.items(kw):
+ for key, val in kw.items():
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', pycompat.keys(all_event_from_google))
+ ('google_internal_event_id', 'in', all_event_from_google.keys())
])
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 pycompat.values(all_event_from_google):
+ for event in all_event_from_google.values():
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(pycompat.items(event_to_synchronize[base_event]), key=operator.itemgetter(0))
+ event_to_synchronize[base_event] = sorted(event_to_synchronize[base_event].items(), 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 ccfe751025b..d3bb213cb6a 100644
--- a/addons/hr/models/hr.py
+++ b/addons/hr/models/hr.py
@@ -1,13 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
+import base64
import logging
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__)
@@ -103,7 +102,7 @@ class Employee(models.Model):
@api.model
def _default_image(self):
image_path = get_module_resource('hr', 'static/src/img', 'default_image.png')
- return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64'))
+ return tools.image_resize_image_big(base64.b64encode(open(image_path, 'rb').read()))
# resource and user
name = fields.Char(related='resource_id.name', store=True, oldname='name_related')
@@ -250,7 +249,7 @@ class Employee(models.Model):
if auto_follow_fields is None:
auto_follow_fields = ['user_id']
user_field_lst = []
- for name, field in pycompat.items(self._fields):
+ for name, field in self._fields.items():
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 ce0054a5636..6699dddeb2a 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, pycompat
+from odoo.tools import email_split, float_is_zero
from odoo.addons import decimal_precision as dp
@@ -243,7 +243,7 @@ class HrExpense(models.Model):
expense.sheet_id.write({'account_move_id': move.id})
if expense.payment_mode == 'company_account':
expense.sheet_id.paid_expense_sheets()
- for move in pycompat.values(move_group_by_sheet):
+ for move in move_group_by_sheet.values():
move.post()
return True
diff --git a/addons/hr_payroll/models/hr_payslip.py b/addons/hr_payroll/models/hr_payslip.py
index fd30b7a1b23..2f310c3456e 100644
--- a/addons/hr_payroll/models/hr_payslip.py
+++ b/addons/hr_payroll/models/hr_payslip.py
@@ -11,8 +11,6 @@ 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):
_name = 'hr.payslip'
@@ -205,7 +203,7 @@ class HrPayslip(models.Model):
}
res.append(attendances)
- res.extend(pycompat.values(leaves))
+ res.extend(leaves.values())
return res
@api.model
@@ -369,7 +367,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 pycompat.items(result_dict)]
+ return list(result_dict.values())
# 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
diff --git a/addons/hr_payroll/report/report_payslip_details.py b/addons/hr_payroll/report/report_payslip_details.py
index 23043d2edb9..ffd5b9d913d 100644
--- a/addons/hr_payroll/report/report_payslip_details.py
+++ b/addons/hr_payroll/report/report_payslip_details.py
@@ -2,8 +2,6 @@
# 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):
_name = 'report.hr_payroll.report_payslipdetails'
@@ -38,9 +36,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 pycompat.items(result):
+ for payslip_id, lines_dict in result.items():
res.setdefault(payslip_id, [])
- for rule_categ_id, line_ids in pycompat.items(lines_dict):
+ for rule_categ_id, line_ids in lines_dict.items():
rule_categories = RuleCateg.browse(rule_categ_id)
lines = PayslipLine.browse(line_ids)
level = 0
@@ -70,9 +68,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 pycompat.items(result):
+ for payslip_id, lines_dict in result.items():
res.setdefault(payslip_id, [])
- for register, lines in pycompat.items(lines_dict):
+ for register, lines in lines_dict.items():
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 adec7b15f57..4e4a694e9eb 100644
--- a/addons/hr_recruitment/models/hr_recruitment.py
+++ b/addons/hr_recruitment/models/hr_recruitment.py
@@ -4,7 +4,6 @@
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
@@ -251,7 +250,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 pycompat.items(self._onchange_job_id_internal(job_id)['value']):
+ for key, value in self._onchange_job_id_internal(job_id)['value'].items():
if key not in vals:
vals[key] = value
if vals.get('user_id'):
diff --git a/addons/hw_escpos/escpos/escpos.py b/addons/hw_escpos/escpos/escpos.py
index e50537e0eeb..7159ac3b31e 100644
--- a/addons/hw_escpos/escpos/escpos.py
+++ b/addons/hw_escpos/escpos/escpos.py
@@ -12,6 +12,8 @@ from hashlib import md5
from PIL import Image
from xml.etree import ElementTree as ET
+from odoo.tools import pycompat
+
try:
import jcconv
except ImportError:
@@ -27,7 +29,7 @@ from .exceptions import *
def utfstr(stuff):
""" converts stuff to string and does without failing if stuff is a utf8 string """
- if isinstance(stuff,basestring):
+ if isinstance(stuff,pycompat.string_types):
return stuff
else:
return str(stuff)
@@ -431,7 +433,7 @@ class Escpos:
print('print_b64_img')
- id = md5(img).digest()
+ id = md5(img.encode('utf-8')).digest()
if id not in self.img_cache:
print('not in cache')
diff --git a/addons/im_livechat/models/im_livechat_channel.py b/addons/im_livechat/models/im_livechat_channel.py
index 1c72ba8725d..c2741fcef92 100644
--- a/addons/im_livechat/models/im_livechat_channel.py
+++ b/addons/im_livechat/models/im_livechat_channel.py
@@ -1,13 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
+import base64
import random
import re
from datetime import datetime, timedelta
from odoo import api, fields, models, modules, tools
-from odoo.tools import pycompat
-
class ImLivechatChannel(models.Model):
""" Livechat Channel
@@ -21,7 +19,7 @@ class ImLivechatChannel(models.Model):
def _default_image(self):
image_path = modules.get_module_resource('im_livechat', 'static/src/img', 'default.png')
- return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64'))
+ return tools.image_resize_image_big(base64.b64encode(open(image_path, 'rb').read()))
def _default_user_ids(self):
return [(6, 0, [self._uid])]
@@ -96,7 +94,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(pycompat.values(repartition))
+ total = sum(repartition.values())
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 32d66a0154c..c64bcd7108c 100644
--- a/addons/im_livechat/models/mail_channel.py
+++ b/addons/im_livechat/models/mail_channel.py
@@ -2,8 +2,6 @@
# 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):
_inherit = 'mail.channel.partner'
@@ -78,7 +76,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 list(pycompat.values(channel_infos_dict))
+ return list(channel_infos_dict.values())
@api.model
def channel_fetch_slot(self):
diff --git a/addons/l10n_be_intrastat/wizard/xml_decl.py b/addons/l10n_be_intrastat/wizard/xml_decl.py
index f7d01db30d1..b5486d963c9 100644
--- a/addons/l10n_be_intrastat/wizard/xml_decl.py
+++ b/addons/l10n_be_intrastat/wizard/xml_decl.py
@@ -6,6 +6,7 @@ from xml.etree import ElementTree as ET
from collections import namedtuple
from odoo import api, exceptions, fields, models, _
+from odoo.tools.pycompat import text_type
INTRASTAT_XMLNS = 'http://www.onegate.eu/2010-01-01'
@@ -264,18 +265,18 @@ class XmlDeclaration(models.TransientModel):
continue
numlgn += 1
item = ET.SubElement(datas, 'Item')
- self._set_Dim(item, 'EXSEQCODE', unicode(numlgn))
- self._set_Dim(item, 'EXTRF', unicode(linekey.EXTRF))
- self._set_Dim(item, 'EXCNT', unicode(linekey.EXCNT))
- self._set_Dim(item, 'EXTTA', unicode(linekey.EXTTA))
- self._set_Dim(item, 'EXREG', unicode(linekey.EXREG))
- self._set_Dim(item, 'EXTGO', unicode(linekey.EXGO))
+ self._set_Dim(item, 'EXSEQCODE', text_type(numlgn))
+ self._set_Dim(item, 'EXTRF', text_type(linekey.EXTRF))
+ self._set_Dim(item, 'EXCNT', text_type(linekey.EXCNT))
+ self._set_Dim(item, 'EXTTA', text_type(linekey.EXTTA))
+ self._set_Dim(item, 'EXREG', text_type(linekey.EXREG))
+ self._set_Dim(item, 'EXTGO', text_type(linekey.EXGO))
if extendedmode:
- self._set_Dim(item, 'EXTPC', unicode(linekey.EXTPC))
- self._set_Dim(item, 'EXDELTRM', unicode(linekey.EXDELTRM))
- self._set_Dim(item, 'EXTXVAL', unicode(round(amounts[0], 0)).replace(".", ","))
- self._set_Dim(item, 'EXWEIGHT', unicode(round(amounts[1], 0)).replace(".", ","))
- self._set_Dim(item, 'EXUNITS', unicode(round(amounts[2], 0)).replace(".", ","))
+ self._set_Dim(item, 'EXTPC', text_type(linekey.EXTPC))
+ self._set_Dim(item, 'EXDELTRM', text_type(linekey.EXDELTRM))
+ self._set_Dim(item, 'EXTXVAL', text_type(round(amounts[0], 0)).replace(".", ","))
+ self._set_Dim(item, 'EXWEIGHT', text_type(round(amounts[1], 0)).replace(".", ","))
+ self._set_Dim(item, 'EXUNITS', text_type(round(amounts[2], 0)).replace(".", ","))
if numlgn == 0:
#no datas
diff --git a/addons/l10n_fr_fec/wizard/account_fr_fec.py b/addons/l10n_fr_fec/wizard/account_fr_fec.py
index 215ed49cb4e..b8c4b571124 100644
--- a/addons/l10n_fr_fec/wizard/account_fr_fec.py
+++ b/addons/l10n_fr_fec/wizard/account_fr_fec.py
@@ -4,11 +4,11 @@
# Copyright (C) 2013-2015 Akretion (http://www.akretion.com)
import base64
-import csv
-import StringIO
+import io
from odoo import api, fields, models, _
from odoo.exceptions import Warning
+from odoo.tools import pycompat
class AccountFrFec(models.TransientModel):
@@ -86,24 +86,24 @@ class AccountFrFec(models.TransientModel):
# So it will be easier for the accountant to check the file before
# sending it to the fiscal administration
header = [
- 'JournalCode', # 0
- 'JournalLib', # 1
- 'EcritureNum', # 2
- 'EcritureDate', # 3
- 'CompteNum', # 4
- 'CompteLib', # 5
- 'CompAuxNum', # 6 We use partner.id
- 'CompAuxLib', # 7
- 'PieceRef', # 8
- 'PieceDate', # 9
- 'EcritureLib', # 10
- 'Debit', # 11
- 'Credit', # 12
- 'EcritureLet', # 13
- 'DateLet', # 14
- 'ValidDate', # 15
- 'Montantdevise', # 16
- 'Idevise', # 17
+ u'JournalCode', # 0
+ u'JournalLib', # 1
+ u'EcritureNum', # 2
+ u'EcritureDate', # 3
+ u'CompteNum', # 4
+ u'CompteLib', # 5
+ u'CompAuxNum', # 6 We use partner.id
+ u'CompAuxLib', # 7
+ u'PieceRef', # 8
+ u'PieceDate', # 9
+ u'EcritureLib', # 10
+ u'Debit', # 11
+ u'Credit', # 12
+ u'EcritureLet', # 13
+ u'DateLet', # 14
+ u'ValidDate', # 15
+ u'Montantdevise', # 16
+ u'Idevise', # 17
]
company = self.env.user.company_id
@@ -114,8 +114,8 @@ class AccountFrFec(models.TransientModel):
raise Warning(
_("FEC is for French companies only !"))
- fecfile = StringIO.StringIO()
- w = csv.writer(fecfile, delimiter='|')
+ fecfile = io.BytesIO()
+ w = pycompat.csv_writer(fecfile, delimiter='|')
w.writerow(header)
# INITIAL BALANCE
@@ -190,7 +190,7 @@ class AccountFrFec(models.TransientModel):
else:
listrow[11] = '0,00'
listrow[12] = str(-listrow_amount).replace('.', ',')
- w.writerow([s.encode("utf-8") for s in listrow])
+ w.writerow(listrow)
#if the unaffected earnings account wasn't in the selection yet: add it manually
if (not unaffected_earnings_line
and unaffected_earnings_results
@@ -201,7 +201,7 @@ class AccountFrFec(models.TransientModel):
if unaffected_earnings_account:
unaffected_earnings_results[4] = unaffected_earnings_account.code
unaffected_earnings_results[5] = unaffected_earnings_account.name
- w.writerow([s.encode("utf-8") for s in unaffected_earnings_results])
+ w.writerow(unaffected_earnings_results)
# LINES
sql_query = '''
@@ -266,8 +266,7 @@ class AccountFrFec(models.TransientModel):
sql_query, (self.date_from, self.date_to, company.id))
for row in self._cr.fetchall():
- listrow = list(row)
- w.writerow([s.encode("utf-8") for s in listrow])
+ w.writerow(list(row))
siren = company.vat[4:13]
end_date = self.date_to.replace('-', '')
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 001a5a782f8..0d26efe9bf5 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
@@ -5,8 +5,6 @@ from datetime import date
from odoo import api, models, _
from odoo.exceptions import UserError
-from odoo.tools import pycompat
-
class EmployeesYearlySalaryReport(models.AbstractModel):
_name = 'report.l10n_in_hr_payroll.report_hryearlysalary'
@@ -99,7 +97,7 @@ class EmployeesYearlySalaryReport(models.AbstractModel):
def salary_list(self, salaries):
cat_salary_all = []
- for category_name, amount in pycompat.items(salaries):
+ for category_name, amount in salaries.items():
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 9993272573f..23fec3c734c 100644
--- a/addons/l10n_lu/scripts/tax2csv.py
+++ b/addons/l10n_lu/scripts/tax2csv.py
@@ -1,18 +1,8 @@
from collections import OrderedDict
-import csv
import xlrd
from odoo.tools import pycompat
-def _e(s):
- if type(s) is unicode:
- return s.encode('utf8')
- elif s is None:
- return ''
- else:
- return str(s)
-
-
def _is_true(s):
return s not in ('F', 'False', 0, '', None, False)
@@ -56,9 +46,9 @@ class LuxTaxGenerator:
yield OrderedDict(pycompat.izip(keys, row))
def tax_codes_to_csv(self):
- writer = csv.writer(open('account.tax.code.template-%s.csv' %
+ writer = pycompat.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 +59,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(pycompat.imap(_e, pycompat.values(row)))
+ writer.writerow([pycompat.to_text(v) for v in row.values()])
# read taxes and add leaf tax codes
new_tax_codes = {} # id: parent_code
@@ -145,17 +135,19 @@ class LuxTaxGenerator:
for tax_code_id in sorted(new_tax_codes):
name, parent_code = new_tax_codes[tax_code_id]
- writer.writerow((tax_code_id,
- 'lu_tct_m' + parent_code,
- tax_code_id.replace('lu_tax_code_template_', ''),
- '1',
- '',
- _e(name),
- ''))
+ writer.writerow([
+ tax_code_id,
+ u'lu_tct_m' + parent_code,
+ tax_code_id.replace('lu_tax_code_template_', u''),
+ u'1',
+ u'',
+ pycompat.to_text(name),
+ u''
+ ])
def taxes_to_csv(self):
- writer = csv.writer(open('account.tax.template-%s.csv' %
- self.suffix, 'wb'))
+ writer = pycompat.csv_writer(open('account.tax.template-%s.csv' %
+ self.suffix, 'wb'))
taxes_iterator = self.iter_taxes()
keys = next(taxes_iterator)
writer.writerow(keys[3:] + ['sequence'])
@@ -168,17 +160,20 @@ class LuxTaxGenerator:
cur_seq = seq + 1000
else:
cur_seq = seq
- writer.writerow(list(pycompat.imap(_e, list(pycompat.values(row))[3:])) + [cur_seq])
+ writer.writerow([
+ pycompat.to_text(v)
+ for v in list(row.values())[3:]
+ ] + [cur_seq])
def fiscal_pos_map_to_csv(self):
- writer = csv.writer(open('account.fiscal.'
- 'position.tax.template-%s.csv' %
- self.suffix, 'wb'))
+ writer = pycompat.csv_writer(open('account.fiscal.'
+ 'position.tax.template-%s.csv' %
+ self.suffix, 'wb'))
fiscal_pos_map_iterator = self.iter_fiscal_pos_map()
keys = next(fiscal_pos_map_iterator)
writer.writerow(keys)
for row in fiscal_pos_map_iterator:
- writer.writerow(pycompat.imap(_e, pycompat.values(row)))
+ writer.writerow([pycompat.to_text(s) for s in row.values()])
if __name__ == '__main__':
diff --git a/addons/link_tracker/models/link_tracker.py b/addons/link_tracker/models/link_tracker.py
index 1d9a7cdf561..509fc21e153 100644
--- a/addons/link_tracker/models/link_tracker.py
+++ b/addons/link_tracker/models/link_tracker.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
+import base64
import datetime
import random
import re
@@ -12,7 +12,7 @@ from werkzeug import urls, utils
from odoo import models, fields, api, _
-from odoo.tools import ustr, pycompat
+from odoo.tools import ustr
URL_REGEX = r'(\bhref=[\'"](?!mailto:)([^\'"]+)[\'"])'
@@ -90,7 +90,7 @@ class link_tracker(models.Model):
@api.one
@api.depends('favicon')
def _compute_icon_src(self):
- self.icon_src = 'data:image/png;base64,' + self.favicon
+ self.icon_src = b'data:image/png;base64,' + self.favicon
@api.one
@api.depends('url')
@@ -123,7 +123,7 @@ class link_tracker(models.Model):
def _compute_favicon(self):
try:
icon = requests.get('http://www.google.com/s2/favicons', params={'domain': self.url}, timeout=5).content
- icon_base64 = icon.encode('base64').replace("\n", "")
+ icon_base64 = base64.b64encode(icon).replace(b"\n", b"").decode('ascii')
except:
icon_base64 = 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsSAAALEgHS3X78AAACiElEQVQ4EaVTzU8TURCf2tJuS7tQtlRb6UKBIkQwkRRSEzkQgyEc6lkOKgcOph78Y+CgjXjDs2i44FXY9AMTlQRUELZapVlouy3d7kKtb0Zr0MSLTvL2zb75eL838xtTvV6H/xELBptMJojeXLCXyobnyog4YhzXYvmCFi6qVSfaeRdXdrfaU1areV5KykmX06rcvzumjY/1ggkR3Jh+bNf1mr8v1D5bLuvR3qDgFbvbBJYIrE1mCIoCrKxsHuzK+Rzvsi29+6DEbTZz9unijEYI8ObBgXOzlcrx9OAlXyDYKUCzwwrDQx1wVDGg089Dt+gR3mxmhcUnaWeoxwMbm/vzDFzmDEKMMNhquRqduT1KwXiGt0vre6iSeAUHNDE0d26NBtAXY9BACQyjFusKuL2Ry+IPb/Y9ZglwuVscdHaknUChqLF/O4jn3V5dP4mhgRJgwSYm+gV0Oi3XrvYB30yvhGa7BS70eGFHPoTJyQHhMK+F0ZesRVVznvXw5Ixv7/C10moEo6OZXbWvlFAF9FVZDOqEABUMRIkMd8GnLwVWg9/RkJF9sA4oDfYQAuzzjqzwvnaRUFxn/X2ZlmGLXAE7AL52B4xHgqAUqrC1nSNuoJkQtLkdqReszz/9aRvq90NOKdOS1nch8TpL555WDp49f3uAMXhACRjD5j4ykuCtf5PP7Fm1b0DIsl/VHGezzP1KwOiZQobFF9YyjSRYQETRENSlVzI8iK9mWlzckpSSCQHVALmN9Az1euDho9Xo8vKGd2rqooA8yBcrwHgCqYR0kMkWci08t/R+W4ljDCanWTg9TJGwGNaNk3vYZ7VUdeKsYJGFNkfSzjXNrSX20s4/h6kB81/271ghG17l+rPTAAAAAElFTkSuQmCC'
@@ -165,7 +165,7 @@ class link_tracker(models.Model):
create_vals['url'] = VALIDATE_URL(vals['url'])
search_domain = []
- for fname, value in pycompat.items(create_vals):
+ for fname, value in create_vals.items():
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 ee8bba3492e..e9c13060fc3 100644
--- a/addons/lunch/models/lunch.py
+++ b/addons/lunch/models/lunch.py
@@ -10,8 +10,6 @@ from odoo import api, fields, models, _
from odoo.exceptions import AccessError, ValidationError
from odoo.addons import decimal_precision as dp
-from odoo.tools import pycompat
-
class LunchOrder(models.Model):
"""
@@ -26,10 +24,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 list(pycompat.values({
+ return list({
(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)]},
@@ -88,10 +86,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 = list(pycompat.values({
+ self.previous_order_ids = list({
(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/controllers/main.py b/addons/mail/controllers/main.py
index 95165441dfd..52ea655c078 100644
--- a/addons/mail/controllers/main.py
+++ b/addons/mail/controllers/main.py
@@ -7,14 +7,13 @@ import logging
import psycopg2
import werkzeug
-from operator import itemgetter
from werkzeug import url_encode
from odoo import api, http, registry, SUPERUSER_ID, _
from odoo.addons.web.controllers.main import binary_content
from odoo.exceptions import AccessError
from odoo.http import request
-from odoo.tools import consteq
+from odoo.tools import consteq, pycompat
_logger = logging.getLogger(__name__)
@@ -102,7 +101,7 @@ class MailController(http.Controller):
""" End-point to receive mail from an external SMTP server. """
dbs = req.jsonrequest.get('databases')
for db in dbs:
- message = dbs[db].decode('base64')
+ message = base64.b64decode(dbs[db])
try:
db_registry = registry(db)
with db_registry.cursor() as cr:
@@ -154,10 +153,10 @@ class MailController(http.Controller):
'default': subtype.default,
'internal': subtype.internal,
'followed': subtype.id in followers.mapped('subtype_ids').ids,
- 'parent_model': subtype.parent_id and subtype.parent_id.res_model or False,
+ 'parent_model': subtype.parent_id.res_model,
'id': subtype.id
} for subtype in subtypes]
- subtypes_list = sorted(subtypes_list, key=itemgetter('parent_model', 'res_model', 'internal', 'sequence'))
+ subtypes_list = sorted(subtypes_list, key=lambda it: (it['parent_model'] or '', it['res_model'] or '', it['internal'], it['sequence']))
return subtypes_list
@http.route('/mail/view', type='http', auth='none')
@@ -183,7 +182,7 @@ class MailController(http.Controller):
else:
# either a wrong message_id, either someone trying ids -> just go to messaging
return self._redirect_to_messaging()
- elif res_id and isinstance(res_id, basestring):
+ elif res_id and isinstance(res_id, pycompat.string_types):
res_id = int(res_id)
return self._redirect_to_record(model, res_id, access_token)
diff --git a/addons/mail/models/ir_model.py b/addons/mail/models/ir_model.py
index e2231e38f5d..01862352e96 100644
--- a/addons/mail/models/ir_model.py
+++ b/addons/mail/models/ir_model.py
@@ -3,6 +3,7 @@
from odoo import _, api, fields, models
from odoo.exceptions import UserError
+from odoo.tools import pycompat
class IrModel(models.Model):
@@ -41,7 +42,7 @@ class IrModel(models.Model):
model_class = super(IrModel, self)._instanciate(model_data)
if model_data.get('is_mail_thread') and model_class._name != 'mail.thread':
parents = model_class._inherit or []
- parents = [parents] if isinstance(parents, basestring) else parents
+ parents = [parents] if isinstance(parents, pycompat.string_types) else parents
model_class._inherit = parents + ['mail.thread']
return model_class
diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py
index fac6edfff98..31e03b99081 100644
--- a/addons/mail/models/mail_channel.py
+++ b/addons/mail/models/mail_channel.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
+import base64
from email.utils import formataddr
import re
@@ -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, pycompat
+from odoo.tools import ormcache
from odoo.tools.safe_eval import safe_eval
@@ -41,7 +41,7 @@ class Channel(models.Model):
def _get_default_image(self):
image_path = modules.get_module_resource('mail', 'static/src/img', 'groupdefault.png')
- return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64'))
+ return tools.image_resize_image_big(base64.b64encode(open(image_path, 'rb').read()))
@api.model
def default_get(self, fields):
@@ -619,7 +619,7 @@ class Channel(models.Model):
channel = channels_preview[message['id']]
del(channel['message_id'])
channel['last_message'] = message
- return list(pycompat.values(channels_preview))
+ return list(channels_preview.values())
#------------------------------------------------------
# Commands
diff --git a/addons/mail/models/mail_followers.py b/addons/mail/models/mail_followers.py
index a6bd60f82c9..3edcfa748c3 100644
--- a/addons/mail/models/mail_followers.py
+++ b/addons/mail/models/mail_followers.py
@@ -2,7 +2,6 @@
# 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):
@@ -42,7 +41,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 pycompat.values(partner_data)) and all(data for data in pycompat.values(channel_data)))
+ force_mode = force or (all(partner_data.values()) and all(channel_data.values()))
generic = []
specific = {}
existing = {} # {res_id: follower_ids}
@@ -71,13 +70,13 @@ class Followers(models.Model):
if force_mode:
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):
+ for pid, data in partner_data.items():
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 pycompat.items(channel_data):
+ for cid, data in channel_data.items():
if not data:
channel_data[cid] = default_subtypes.ids
diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py
index 3f206424e07..e723f71bdc6 100644
--- a/addons/mail/models/mail_mail.py
+++ b/addons/mail/models/mail_mail.py
@@ -13,7 +13,6 @@ 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__)
@@ -169,7 +168,7 @@ class MailMail(models.Model):
- else fallback on mail.email_to splitting """
self.ensure_one()
if partner:
- email_to = [formataddr((partner.name, partner.email))]
+ email_to = [formataddr((partner.name or 'False', partner.email or 'False'))]
else:
email_to = tools.email_split_and_format(self.email_to)
return email_to
@@ -205,7 +204,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 pycompat.items(groups):
+ for server_id, record_ids in groups.items():
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 2970933e704..182e3d6a19c 100644
--- a/addons/mail/models/mail_message.py
+++ b/addons/mail/models/mail_message.py
@@ -9,7 +9,6 @@ from email.utils import formataddr
from odoo import _, api, fields, models, modules, SUPERUSER_ID, tools
from odoo.exceptions import UserError, AccessError
from odoo.osv import expression
-from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
_image_dataurl = re.compile(r'(data:image/[a-z]+?);base64,([a-z0-9+/]{3,}=*)([\'"])', re.I)
@@ -282,7 +281,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 pycompat.items(message_tree):
+ for key, message in message_tree.items():
if message.author_id:
partners |= message.author_id
if message.subtype_id and message.partner_ids: # take notified people of message with a subtype
@@ -446,7 +445,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 pycompat.items(model_ids):
+ for doc_model, doc_dict in model_ids.items():
if not IrModelAccess.check(doc_model, 'read', False):
continue
allowed_ids |= self._find_allowed_model_wise(doc_model, doc_dict)
@@ -607,17 +606,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 pycompat.items(message_values)
+ author_ids = [mid for mid, message in message_values.items()
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 pycompat.items(message_values)
+ author_ids = [mid for mid, message in message_values.items()
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 pycompat.items(message_values)
+ parent_ids = [message.get('parent_id') for message in message_values.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
@@ -630,37 +629,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 pycompat.items(message_values)
+ notified_ids += [mid for mid, message in message_values.items()
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 pycompat.items(message_values) if message.get('notified')]
+ notified_ids = [mid for mid, message in message_values.items() if message.get('notified')]
elif operation == 'create':
- for doc_model, doc_ids in pycompat.items(model_record_ids):
+ for doc_model, doc_ids in model_record_ids.items():
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 pycompat.items(message_values)
+ notified_ids += [mid for mid, message in message_values.items()
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 pycompat.items(model_record_ids):
+ for model, doc_ids in model_record_ids.items():
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 pycompat.items(message_values)
+ document_related_ids += [mid for mid, message in message_values.items()
if message.get('model') == model and message.get('res_id') in mids.ids]
# Calculate remaining ids: if not void, raise an error
@@ -745,7 +744,7 @@ class Message(models.Model):
values['attachment_ids'].append((4, attachment.id))
data_to_url[key] = '/web/image/%s' % attachment.id
return '%s%s alt="%s"' % (data_to_url[key], match.group(3), name)
- values['body'] = _image_dataurl.sub(base64_to_boundary, values['body'])
+ values['body'] = _image_dataurl.sub(base64_to_boundary, tools.ustr(values['body']))
message = super(Message, self).create(values)
message._invalidate_documents()
diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py
index e9bf605c382..1bd92aff430 100644
--- a/addons/mail/models/mail_template.py
+++ b/addons/mail/models/mail_template.py
@@ -45,7 +45,7 @@ def format_tz(env, dt, tz=False, format=False):
return format_datetime(ts, format or 'medium', locale=env.context.get("lang") or 'en_US')
if format:
- return ts.strftime(format)
+ return pycompat.text_type(ts.strftime(format))
else:
lang = env.context.get("lang")
langs = env['res.lang']
@@ -54,9 +54,9 @@ def format_tz(env, dt, tz=False, format=False):
format_date = langs.date_format or '%B-%d-%Y'
format_time = langs.time_format or '%I-%M %p'
- fdate = ts.strftime(format_date).decode('utf-8')
- ftime = ts.strftime(format_time).decode('utf-8')
- return "%s %s%s" % (fdate, ftime, (' (%s)' % tz) if tz else '')
+ fdate = pycompat.text_type(ts.strftime(format_date))
+ ftime = pycompat.text_type(ts.strftime(format_time))
+ return u"%s %s%s" % (fdate, ftime, (u' (%s)' % tz) if tz else u'')
def format_amount(env, amount, currency):
fmt = "%.{0}f".format(currency.decimal_places)
@@ -306,8 +306,8 @@ class MailTemplate(models.Model):
# form a tree
root = lxml.html.fromstring(html)
if not len(root) and root.text is None and root.tail is None:
- html = '
%s
' % html
- root = lxml.html.fromstring(html)
+ html = u'
%s
' % html
+ root = lxml.html.fromstring(html, encoding='unicode')
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
base = urls.url_parse(base_url)
@@ -324,12 +324,12 @@ class MailTemplate(models.Model):
for node in root.iter():
if node.tag == 'a' and node.get('href'):
node.set('href', _process_link(node.get('href')))
- elif node.tag == 'img' and not node.get('src', 'data').startswith('data'):
+ elif node.tag == 'img' and not node.get('src', 'data').startswith(u'data'):
node.set('src', _process_link(node.get('src')))
- html = lxml.html.tostring(root, pretty_print=False, method='html')
+ html = lxml.html.tostring(root, pretty_print=False, method='html', encoding='unicode')
# this is ugly, but lxml/etree tostring want to put everything in a 'div' that breaks the editor -> remove that
- if html.startswith('
') and html.endswith('
'):
+ if html.startswith(u'
') and html.endswith(u'
'):
html = html[5:-6]
return html
@@ -379,7 +379,7 @@ class MailTemplate(models.Model):
'user': self.env.user,
'ctx': self._context, # context kw would clash with mako internals
}
- for res_id, record in pycompat.items(res_to_rec):
+ for res_id, record in res_to_rec.items():
variables['object'] = record
try:
render_result = template.render(variables)
@@ -391,7 +391,7 @@ class MailTemplate(models.Model):
results[res_id] = render_result
if post_process:
- for res_id, result in pycompat.items(results):
+ for res_id, result in results.items():
results[res_id] = self.render_post_process(result)
return multi_mode and results or results[res_ids[0]]
@@ -412,7 +412,7 @@ class MailTemplate(models.Model):
self.ensure_one()
langs = self.render_template(self.lang, self.model, res_ids)
- for res_id, lang in pycompat.items(langs):
+ for res_id, lang in langs.items():
if lang:
template = self.with_context(lang=lang)
else:
@@ -431,11 +431,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 pycompat.items(default_recipients):
+ for res_id, recipients in default_recipients.items():
results[res_id].pop('partner_to', None)
results[res_id].update(recipients)
- for res_id, values in pycompat.items(results):
+ for res_id, values in results.items():
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', ''))
@@ -474,11 +474,11 @@ class MailTemplate(models.Model):
# templates: res_id -> template; template -> res_ids
templates_to_res_ids = {}
- for res_id, template in pycompat.items(res_ids_to_templates):
+ for res_id, template in res_ids_to_templates.items():
templates_to_res_ids.setdefault(template, []).append(res_id)
results = dict()
- for template, template_res_ids in pycompat.items(templates_to_res_ids):
+ for template, template_res_ids in templates_to_res_ids.items():
Template = self.env['mail.template']
# generate fields value for all res_ids linked to the current template
if template.lang:
@@ -488,7 +488,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 pycompat.items(generated_field_values):
+ for res_id, field_value in generated_field_values.items():
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 0d23032d051..296f79bed14 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -238,7 +238,7 @@ class MailThread(models.AbstractModel):
# auto_subscribe: take values and defaults into account
create_values = dict(values)
- for key, val in pycompat.items(self._context):
+ for key, val in self._context.items():
if key.startswith('default_') and key[8:] not in create_values:
create_values[key[8:]] = val
thread.message_auto_subscribe(list(create_values), values=create_values)
@@ -373,7 +373,7 @@ class MailThread(models.AbstractModel):
options['display_log_button'] = is_employee
# save options on the node
node.set('options', repr(options))
- res['arch'] = etree.tostring(doc)
+ res['arch'] = etree.tostring(doc, encoding='unicode')
return res
# ------------------------------------------------------
@@ -388,7 +388,7 @@ class MailThread(models.AbstractModel):
always tracked fields and modified on_change fields
"""
tracked_fields = []
- for name, field in pycompat.items(self._fields):
+ for name, field in self._fields.items():
if getattr(field, 'track_visibility', False):
tracked_fields.append(name)
@@ -416,13 +416,13 @@ 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 pycompat.items(tracking)):
+ if not any(change for rec_id, (change, tracking_value_ids) in tracking.items()):
return True
templates = self._track_template(tracking)
- for field_name, (template, post_kwargs) in pycompat.items(templates):
+ for field_name, (template, post_kwargs) in templates.items():
if not template:
continue
- if isinstance(template, basestring):
+ if isinstance(template, pycompat.string_types):
self.message_post_with_view(template, **post_kwargs)
else:
self.message_post_with_template(template.id, **post_kwargs)
@@ -450,7 +450,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 pycompat.items(tracked_fields):
+ for col_name, col_info in tracked_fields.items():
track_visibility = getattr(self._fields[col_name], 'track_visibility', 'onchange')
initial_value = initial[col_name]
new_value = getattr(self, col_name)
@@ -569,7 +569,7 @@ class MailThread(models.AbstractModel):
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)))
- hm = hmac.new(str(secret), token, hashlib.sha1).hexdigest()
+ hm = hmac.new(secret.encode('utf-8'), token.encode('utf-8'), hashlib.sha1).hexdigest()
return hm
@api.multi
@@ -816,7 +816,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 pycompat.items(self.env):
+ for model_name, model in self.env.items():
if hasattr(model, "message_process") and hasattr(model, "message_post"):
ret_dict[model_name] = model._description
return ret_dict
@@ -1266,12 +1266,16 @@ class MailThread(models.AbstractModel):
# we don't know its encoding until we parse its headers and hence can't
# convert it to utf-8 for transport between the mailgate script and here.
if isinstance(message, xmlrpclib.Binary):
- message = str(message.data)
- # Warning: message_from_string doesn't always work correctly on unicode,
- # we must use utf-8 strings here :-(
- if isinstance(message, unicode):
+ message = bytes(message.data)
+ # message_from_string parses from a *native string*, except apparently
+ # sometimes message is ISO-8859-1 binary data or some shit and the
+ # straightforward version (pycompat.to_native) won't work right ->
+ # always encode message to bytes then use the relevant method
+ # depending on ~python version
+ if isinstance(message, pycompat.text_type):
message = message.encode('utf-8')
- msg_txt = email.message_from_string(message)
+ extract = getattr(email, 'message_from_bytes', email.message_from_string)
+ msg_txt = extract(message)
# parse the message, verify we are not in a loop by checking message_id is not duplicated
msg = self.message_parse(msg_txt, save_original=save_original)
@@ -1492,9 +1496,9 @@ class MailThread(models.AbstractModel):
'message_type': 'email',
}
if not isinstance(message, Message):
- if isinstance(message, unicode):
- # Warning: message_from_string doesn't always work correctly on unicode,
- # we must use utf-8 strings here :-(
+ # message_from_string works on a native str, so on py2 we need to
+ # encode incoming unicode strings
+ if pycompat.PY2 and not isinstance(message, str):
message = message.encode('utf-8')
message = email.message_from_string(message)
@@ -1723,11 +1727,11 @@ class MailThread(models.AbstractModel):
cid = info and info.get('cid')
else:
continue
- if isinstance(content, unicode):
+ if isinstance(content, pycompat.text_type):
content = content.encode('utf-8')
data_attach = {
'name': name,
- 'datas': base64.b64encode(str(content)),
+ 'datas': base64.b64encode(content),
'type': 'binary',
'datas_fname': name,
'description': name,
@@ -1920,7 +1924,7 @@ class MailThread(models.AbstractModel):
values['slug'] = slug
except ImportError:
values['slug'] = lambda self: self.id
- if isinstance(views_or_xmlid, basestring):
+ if isinstance(views_or_xmlid, pycompat.string_types):
views = self.env.ref(views_or_xmlid, raise_if_not_found=False)
else:
views = views_or_xmlid
@@ -2052,7 +2056,7 @@ class MailThread(models.AbstractModel):
if auto_follow_fields is None:
auto_follow_fields = ['user_id']
user_field_lst = []
- for name, field in pycompat.items(self._fields):
+ for name, field in self._fields.items():
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
@@ -2148,10 +2152,10 @@ class MailThread(models.AbstractModel):
for partner in to_add_users.mapped('partner_id'):
new_partners.setdefault(partner.id, None)
- for pid, subtypes in pycompat.items(new_partners):
+ for pid, subtypes in new_partners.items():
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 pycompat.items(new_channels):
+ for cid, subtypes in new_channels.items():
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 6379ddb8c42..9140959f89e 100644
--- a/addons/mail/models/res_partner.py
+++ b/addons/mail/models/res_partner.py
@@ -4,7 +4,6 @@
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
@@ -175,7 +174,7 @@ class Partner(models.Model):
emails = self.env['mail.mail']
recipients_nbr, recipients_max = 0, 50
- for email_type, recipient_template_values in pycompat.items(recipients):
+ for email_type, recipient_template_values in recipients.items():
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_features.py b/addons/mail/tests/test_mail_features.py
index 92613f55e51..8e2264ad6d3 100644
--- a/addons/mail/tests/test_mail_features.py
+++ b/addons/mail/tests/test_mail_features.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import base64
from odoo.addons.mail.tests.common import TestMail
from odoo.addons.mail.tests.test_mail_gateway import MAIL_TEMPLATE_PLAINTEXT
@@ -89,8 +90,8 @@ class TestMessagePost(TestMail):
_body, _body_alt = '
Test Body
', 'Test Body'
_subject = 'Test Subject'
_attachments = [
- ('List1', 'My first attachment'),
- ('List2', 'My second attachment')
+ ('List1', b'My first attachment'),
+ ('List2', b'My second attachment')
]
_attach_1 = self.env['ir.attachment'].sudo(self.user_employee).create({
'name': 'Attach1', 'datas_fname': 'Attach1',
@@ -129,8 +130,8 @@ class TestMessagePost(TestMail):
'message_post: all atttachments should be linked to the mail.test model')
self.assertEqual(set(msg.attachment_ids.mapped('res_id')), set([self.test_pigs.id]),
'message_post: all atttachments should be linked to the pigs group')
- self.assertEqual(set([x.decode('base64') for x in msg.attachment_ids.mapped('datas')]),
- set(['migration test', _attachments[0][1], _attachments[1][1]]))
+ self.assertEqual(set([base64.b64decode(x) for x in msg.attachment_ids.mapped('datas')]),
+ set([b'migration test', _attachments[0][1], _attachments[1][1]]))
self.assertTrue(set([_attach_1.id, _attach_2.id]).issubset(msg.attachment_ids.ids),
'message_post: mail.message attachments duplicated')
# notifications
diff --git a/addons/mail/tests/test_mail_message.py b/addons/mail/tests/test_mail_message.py
index db6dc9457a5..566a038d9ef 100644
--- a/addons/mail/tests/test_mail_message.py
+++ b/addons/mail/tests/test_mail_message.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
+import base64
import itertools
from odoo.addons.mail.tests.common import TestMail
@@ -215,7 +215,7 @@ class TestMailMessageAccess(TestMail):
def test_mail_message_access_read_notification(self):
attachment = self.env['ir.attachment'].create({
- 'datas': 'My attachment'.encode('base64'),
+ 'datas': base64.b64encode(b'My attachment'),
'name': 'doc.txt',
'datas_fname': 'doc.txt'})
# attach the attachment to the message
diff --git a/addons/mail/tests/test_mail_template.py b/addons/mail/tests/test_mail_template.py
index 60df04f0654..e5da1c628ae 100644
--- a/addons/mail/tests/test_mail_template.py
+++ b/addons/mail/tests/test_mail_template.py
@@ -17,13 +17,13 @@ class TestMailTemplate(TestMail):
'name': '_Test_First',
'datas_fname':
'first.txt',
- 'datas': base64.b64encode('My first attachment'),
+ 'datas': base64.b64encode(b'My first attachment'),
'res_model': 'res.partner',
'res_id': self.user_admin.partner_id.id
}, {
'name': '_Test_Second',
'datas_fname': 'second.txt',
- 'datas': base64.b64encode('My second attachment'),
+ 'datas': base64.b64encode(b'My second attachment'),
'res_model': 'res.partner',
'res_id': self.user_admin.partner_id.id
}]
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
index 76412ce041c..767462df892 100644
--- a/addons/mail/wizard/mail_compose_message.py
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -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 pycompat.items(message_values)
+ author_ids = [mid for mid, message in message_values.items()
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 pycompat.items(all_mail_values):
+ for res_id, mail_values in all_mail_values.items():
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 pycompat.items(values):
+ for fname, value in values.items():
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 7370633114e..6fea98870d8 100644
--- a/addons/marketing_campaign/models/marketing_campaign.py
+++ b/addons/marketing_campaign/models/marketing_campaign.py
@@ -9,7 +9,6 @@ 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
from odoo.addons import decimal_precision as dp
@@ -466,7 +465,7 @@ 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 pycompat.items(workitem_map):
+ for model, id_map in workitem_map.items():
Model = self.env[model]
condition_name[0] = Model._rec_name
condition = [('id', 'in', list(id_map)), condition_name]
diff --git a/addons/mass_mailing/controllers/main.py b/addons/mass_mailing/controllers/main.py
index 276056311b8..b2e77c2875e 100644
--- a/addons/mass_mailing/controllers/main.py
+++ b/addons/mass_mailing/controllers/main.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import base64
import werkzeug
@@ -37,7 +38,7 @@ class MassMailController(http.Controller):
request.env['mail.mail.statistics'].sudo().set_opened(mail_mail_ids=[mail_id])
response = werkzeug.wrappers.Response()
response.mimetype = 'image/gif'
- response.data = 'R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='.decode('base64')
+ response.data = base64.b64decode(b'R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==')
return response
diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py
index 44993bdc907..a10e6143c89 100644
--- a/addons/mass_mailing/models/mass_mailing.py
+++ b/addons/mass_mailing/models/mass_mailing.py
@@ -430,7 +430,7 @@ class MassMailing(models.Model):
secret = self.env["ir.config_parameter"].sudo().get_param(
"database.secret")
token = (self.env.cr.dbname, self.id, int(res_id), tools.ustr(email))
- return hmac.new(str(secret), repr(token), hashlib.sha512).hexdigest()
+ return hmac.new(secret.encode('utf-8'), repr(token).encode('utf-8'), hashlib.sha512).hexdigest()
def _compute_next_departure(self):
cron_next_call = self.env.ref('mass_mailing.ir_cron_mass_mailing_queue').sudo().nextcall
diff --git a/addons/membership/models/account_invoice.py b/addons/membership/models/account_invoice.py
index 273dd4290ba..73622ce0fb2 100644
--- a/addons/membership/models/account_invoice.py
+++ b/addons/membership/models/account_invoice.py
@@ -60,9 +60,9 @@ class AccountInvoiceLine(models.Model):
invoice_line.product_id.membership and \
not MemberLine.search([('account_invoice_line', '=', invoice_line.id)]):
# Product line is a membership product
- date_from = invoice_line.product_id.membership_date_from
- date_to = invoice_line.product_id.membership_date_to
- if invoice_line.invoice_id.date_invoice > date_from and invoice_line.invoice_id.date_invoice < date_to:
+ date_from = invoice_line.product_id.membership_date_from or '0000-00-00'
+ date_to = invoice_line.product_id.membership_date_to or '0000-00-00'
+ if date_from < (invoice_line.invoice_id.date_invoice or '0000-00-00') < date_to:
date_from = invoice_line.invoice_id.date_invoice
MemberLine.create({
'partner': invoice_line.invoice_id.partner_id.id,
diff --git a/addons/mrp/models/mrp_workcenter.py b/addons/mrp/models/mrp_workcenter.py
index 58226b2e9e8..cc12ec0060b 100644
--- a/addons/mrp/models/mrp_workcenter.py
+++ b/addons/mrp/models/mrp_workcenter.py
@@ -5,7 +5,6 @@ from dateutil import relativedelta
import datetime
from odoo import api, exceptions, fields, models, _
-from odoo.tools import pycompat
class MrpWorkcenter(models.Model):
@@ -74,7 +73,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 pycompat.items(result[workcenter.id]) if state not in ('done', 'cancel'))
+ workcenter.workorder_count = sum(count for state, count in result[workcenter.id].items() 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_repair/wizard/mrp_repair_make_invoice.py b/addons/mrp_repair/wizard/mrp_repair_make_invoice.py
index a659e45e962..03e89da5cd5 100644
--- a/addons/mrp_repair/wizard/mrp_repair_make_invoice.py
+++ b/addons/mrp_repair/wizard/mrp_repair_make_invoice.py
@@ -2,7 +2,6 @@
# 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):
@@ -25,7 +24,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', list(pycompat.values(new_invoice)))],
+ 'domain': [('id', 'in', list(new_invoice.values()))],
'name': 'Invoices',
'view_type': 'form',
'view_mode': 'tree,form',
diff --git a/addons/pad/models/pad.py b/addons/pad/models/pad.py
index 945e24f4308..d0c2055c739 100644
--- a/addons/pad/models/pad.py
+++ b/addons/pad/models/pad.py
@@ -10,7 +10,7 @@ import requests
from odoo import api, models, _
from odoo.exceptions import UserError
-from odoo.tools import html2plaintext, pycompat
+from odoo.tools import html2plaintext
from ..py_etherpad import EtherpadLiteClient
@@ -106,7 +106,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 pycompat.items(self._fields):
+ for k, field in self._fields.items():
if hasattr(field, 'pad_content_field') and k not in vals:
ctx = {
'model': self._name,
@@ -121,7 +121,7 @@ class PadCommon(models.AbstractModel):
def _set_pad_value(self, vals):
# Update the pad if the `pad_content_field` is modified
- for k, field in pycompat.items(self._fields):
+ for k, field in self._fields.items():
if hasattr(field, 'pad_content_field') and vals.get(field.pad_content_field) and self[k]:
company = self.env.user.sudo().company_id
myPad = EtherpadLiteClient(company.pad_key, company.pad_server + '/api')
@@ -129,7 +129,7 @@ class PadCommon(models.AbstractModel):
myPad.setText(path, (html2plaintext(vals[field.pad_content_field]).encode('utf-8')))
# Update the `pad_content_field` if the pad is modified
- for k, v in list(pycompat.items(vals)):
+ for k, v in vals.items():
field = self._fields[k]
if hasattr(field, 'pad_content_field'):
vals[field.pad_content_field] = self.pad_get_content(v)
@@ -139,7 +139,7 @@ class PadCommon(models.AbstractModel):
self.ensure_one()
if not default:
default = {}
- for k, field in pycompat.items(self._fields):
+ for k, field in self._fields.items():
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 623676c7a9c..2e36d891344 100644
--- a/addons/payment/models/payment_acquirer.py
+++ b/addons/payment/models/payment_acquirer.py
@@ -5,7 +5,7 @@ import logging
import datetime
from odoo import api, exceptions, fields, models, _
-from odoo.tools import consteq, float_round, image_resize_images, ustr, pycompat
+from odoo.tools import consteq, float_round, image_resize_images, ustr
from odoo.addons.base.module import module
from odoo.exceptions import ValidationError
@@ -169,7 +169,7 @@ class PaymentAcquirer(models.Model):
""" If the field has 'required_if_provider=""' attribute, then it
required if record.provider is . """
for acquirer in self:
- if any(getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k] for k, f in pycompat.items(self._fields)):
+ if any(getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k] for k, f in self._fields.items()):
return False
return True
@@ -583,7 +583,7 @@ class PaymentTransaction(models.Model):
token = '%s%s%s' % (self.callback_model_id.model,
self.callback_res_id,
self.sudo().callback_method)
- return hmac.new(str(secret), token, hashlib.sha256).hexdigest()
+ return hmac.new(secret.encode('utf-8'), token.encode('utf-8'), hashlib.sha256).hexdigest()
# --------------------------------------------------
# FORM RELATED METHODS
diff --git a/addons/payment_adyen/models/payment.py b/addons/payment_adyen/models/payment.py
index 87e51e619cb..dbc24ced0c0 100644
--- a/addons/payment_adyen/models/payment.py
+++ b/addons/payment_adyen/models/payment.py
@@ -15,8 +15,6 @@ 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__)
@@ -52,9 +50,9 @@ class AcquirerAdyen(models.Model):
def signParams(parms):
signing_string = ':'.join(
escapeVal(v)
- for v in chain(pycompat.keys(parms), pycompat.values(parms))
+ for v in chain(parms.keys(), parms.values())
)
- hm = hmac.new(hmac_key, signing_string, hashlib.sha256)
+ hm = hmac.new(hmac_key, signing_string.encode('utf-8'), hashlib.sha256)
return base64.b64encode(hm.digest())
assert inout in ('in', 'out')
@@ -81,7 +79,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(pycompat.items(raw_values), key=lambda t: t[0]))
+ raw_values_ordered = OrderedDict(sorted(raw_values.items(), 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 fcfab1e3ac2..7388a1a4329 100644
--- a/addons/payment_authorize/models/authorize_request.py
+++ b/addons/payment_authorize/models/authorize_request.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
+import io
import requests
from lxml import etree, objectify
-from StringIO import StringIO
from xml.etree import ElementTree as ET
from uuid import uuid4
@@ -21,7 +21,7 @@ def strip_ns(xml, ns):
:rtype: etree._Element
:return: the parsed xml string with the namespace prefix removed
"""
- it = ET.iterparse(StringIO(xml))
+ it = ET.iterparse(io.BytesIO(xml))
ns_prefix = '{%s}' % XMLNS
for _, el in it:
if el.tag.startswith(ns_prefix):
@@ -58,7 +58,7 @@ class AuthorizeAPI():
:param etree._Element data: etree data to process
"""
- data = etree.tostring(data, xml_declaration=True, encoding='utf-8')
+ data = etree.tostring(data, encoding='utf-8')
r = requests.post(self.url, data=data, headers={'Content-Type': 'text/xml'})
r.raise_for_status()
response = strip_ns(r.content, XMLNS)
diff --git a/addons/payment_authorize/models/payment.py b/addons/payment_authorize/models/payment.py
index bfc5bfd569a..caa15732e15 100644
--- a/addons/payment_authorize/models/payment.py
+++ b/addons/payment_authorize/models/payment.py
@@ -54,7 +54,7 @@ class PaymentAcquirerAuthorize(models.Model):
values['x_fp_timestamp'],
values['x_amount'],
values['x_currency_code']])
- return hmac.new(str(values['x_trans_key']), data, hashlib.md5).hexdigest()
+ return hmac.new(values['x_trans_key'].encode('utf-8'), data.encode('utf-8'), hashlib.md5).hexdigest()
@api.multi
def authorize_form_generate_values(self, values):
diff --git a/addons/payment_authorize/tests/test_authorize.py b/addons/payment_authorize/tests/test_authorize.py
index b02d3d81209..59fb401b091 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, pycompat
+from odoo.tools import mute_logger
@odoo.tests.common.at_install(True)
@@ -39,7 +39,7 @@ class AuthorizeForm(AuthorizeCommon):
values['x_fp_timestamp'],
values['x_amount'],
]) + '^'
- return hmac.new(str(values['x_trans_key']), data, hashlib.md5).hexdigest()
+ return hmac.new(values['x_trans_key'].encode('utf-8'), data.encode('utf-8'), hashlib.md5).hexdigest()
def test_10_Authorize_form_render(self):
self.assertEqual(self.authorize.environment, 'test', 'test without test environment')
@@ -91,11 +91,11 @@ 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 = list(pycompat.values(el.attrib))
+ values = list(el.attrib.values())
if values[1] in ['submit', 'x_fp_hash', 'return_url', 'x_state', 'x_ship_to_state']:
continue
self.assertEqual(
- unicode(values[2], "utf-8"),
+ values[2],
form_values[values[1]],
'Authorize: wrong value for input %s: received %s instead of %s' % (values[1], values[2], form_values[values[1]])
)
diff --git a/addons/payment_buckaroo/controllers/main.py b/addons/payment_buckaroo/controllers/main.py
index 5af6a454b47..40e4ae539e4 100644
--- a/addons/payment_buckaroo/controllers/main.py
+++ b/addons/payment_buckaroo/controllers/main.py
@@ -6,7 +6,6 @@ import werkzeug
from odoo import http
from odoo.http import request
-from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -27,6 +26,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 = {key.upper(): value for key, value in pycompat.items(post)}
+ post = {key.upper(): value for key, value in post.items()}
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 a07ca83318d..ec0cadfab01 100644
--- a/addons/payment_buckaroo/models/payment.py
+++ b/addons/payment_buckaroo/models/payment.py
@@ -8,7 +8,6 @@ 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__)
@@ -21,7 +20,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 {key.upper(): val for key, val in pycompat.items(data)}
+ return {key.upper(): val for key, val in data.items()}
class AcquirerBuckaroo(models.Model):
@@ -73,7 +72,7 @@ class AcquirerBuckaroo(models.Model):
del values[key]
break
- items = sorted(pycompat.items(values), key=lambda pair: pair[0].lower())
+ items = sorted(values.items(), key=lambda pair: pair[0].lower())
sign = ''.join('%s=%s' % (k, urls.url_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 9ac90f9478f..7a2adf37d45 100644
--- a/addons/payment_ogone/models/payment.py
+++ b/addons/payment_ogone/models/payment.py
@@ -1,4 +1,5 @@
# coding: utf-8
+import base64
import datetime
import logging
import time
@@ -14,7 +15,7 @@ from odoo import api, fields, models, _
from odoo.addons.payment.models.payment_acquirer import ValidationError
from odoo.addons.payment_ogone.controllers.main import OgoneController
from odoo.addons.payment_ogone.data import ogone
-from odoo.tools import float_round, DEFAULT_SERVER_DATE_FORMAT, pycompat
+from odoo.tools import float_round, DEFAULT_SERVER_DATE_FORMAT
from odoo.tools.float_utils import float_compare, float_repr
from odoo.tools.safe_eval import safe_eval
@@ -141,7 +142,7 @@ class PaymentAcquirerOgone(models.Model):
]
return key.upper() in keys
- items = sorted((k.upper(), v) for k, v in pycompat.items(values))
+ items = sorted((k.upper(), v) for k, v in values.items())
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()
@@ -469,7 +470,7 @@ class PaymentTxOgone(models.Model):
self.write({
'state': new_state,
'acquirer_reference': tree.get('PAYID'),
- 'html_3ds': str(tree.HTML_ANSWER).decode('base64')
+ 'html_3ds': base64.b64decode(tree.HTML_ANSWER.decode('ascii')),
})
elif status in self._ogone_wait_tx_status and tries > 0:
time.sleep(0.5)
diff --git a/addons/payment_payumoney/models/payment.py b/addons/payment_payumoney/models/payment.py
index 243ef5cef53..cbda9e64527 100644
--- a/addons/payment_payumoney/models/payment.py
+++ b/addons/payment_payumoney/models/payment.py
@@ -49,7 +49,7 @@ class PaymentAcquirerPayumoney(models.Model):
sign = ''.join('%s|' % (values.get(k) or '') for k in keys)
sign = self.payumoney_merchant_salt + sign + self.payumoney_merchant_key
- shasign = hashlib.sha512(sign).hexdigest()
+ shasign = hashlib.sha512(sign.encode('utf-8')).hexdigest()
return shasign
@api.multi
diff --git a/addons/payment_sips/models/payment.py b/addons/payment_sips/models/payment.py
index f01c59d1880..a6c9b390eae 100644
--- a/addons/payment_sips/models/payment.py
+++ b/addons/payment_sips/models/payment.py
@@ -66,7 +66,7 @@ class AcquirerSips(models.Model):
if self.environment == 'prod':
key = getattr(self, 'sips_secret')
- shasign = sha256(data + key)
+ shasign = sha256((data + key).encode('utf-8'))
return shasign.hexdigest()
@api.multi
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index 5e059a41cfc..9e730f9a2e5 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -8,7 +8,7 @@ import psycopg2
import pytz
from odoo import api, fields, models, tools, _
-from odoo.tools import float_is_zero, pycompat
+from odoo.tools import float_is_zero
from odoo.exceptions import UserError
from odoo.http import request
from odoo.addons import decimal_precision as dp
@@ -333,7 +333,7 @@ class PosOrder(models.Model):
# round tax lines per order
if rounding_method == 'round_globally':
- for group_key, group_value in pycompat.items(grouped_data):
+ for group_key, group_value in grouped_data.items():
if group_key[0] == 'tax':
for line in group_value:
line['credit'] = cur.round(line['credit'])
@@ -351,7 +351,7 @@ class PosOrder(models.Model):
order.write({'state': 'done', 'account_move': move.id})
all_lines = []
- for group_key, group_data in pycompat.items(grouped_data):
+ for group_key, group_data in grouped_data.items():
for value in group_data:
all_lines.append((0, 0, value),)
if move: # In case no order was changed
@@ -1032,7 +1032,7 @@ class ReportSaleDetails(models.AbstractModel):
'total_paid': user_currency.round(total),
'payments': payments,
'company_name': self.env.user.company_id.name,
- 'taxes': list(pycompat.values(taxes)),
+ 'taxes': list(taxes.values()),
'products': sorted([{
'product_id': product.id,
'product_name': product.name,
@@ -1041,7 +1041,7 @@ class ReportSaleDetails(models.AbstractModel):
'price_unit': price_unit,
'discount': discount,
'uom': product.uom_id.name
- } for (product, price_unit, discount), qty in pycompat.items(products_sold)], key=lambda l: l['product_name'])
+ } for (product, price_unit, discount), qty in products_sold.items()], key=lambda l: l['product_name'])
}
@api.multi
diff --git a/addons/product/models/product.py b/addons/product/models/product.py
index 638e898aed8..c550e063709 100644
--- a/addons/product/models/product.py
+++ b/addons/product/models/product.py
@@ -158,7 +158,7 @@ class ProductProduct(models.Model):
quantity = self._context.get('quantity', 1.0)
# Support context pricelists specified as display_name or ID for compatibility
- if isinstance(pricelist_id_or_name, basestring):
+ if isinstance(pricelist_id_or_name, pycompat.string_types):
pricelist_name_search = self.env['product.pricelist'].name_search(pricelist_id_or_name, operator='=', limit=1)
if pricelist_name_search:
pricelist = self.env['product.pricelist'].browse([pricelist_name_search[0][0]])
diff --git a/addons/product/models/product_pricelist.py b/addons/product/models/product_pricelist.py
index a0457f021ba..392447809f4 100644
--- a/addons/product/models/product_pricelist.py
+++ b/addons/product/models/product_pricelist.py
@@ -90,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 pycompat.items(subres):
+ for product_id, price in subres.items():
results.setdefault(product_id, {})
results[product_id][pricelist.id] = price
return results
@@ -254,11 +254,11 @@ class Pricelist(models.Model):
self.ensure_one()
return {
product_id: res_tuple[0]
- for product_id, res_tuple in pycompat.items(self._compute_price_rule(
+ for product_id, res_tuple in self._compute_price_rule(
list(pycompat.izip(products, quantities, partners)),
date=date,
uom_id=uom_id
- ))
+ ).items()
}
def get_product_price(self, product, quantity, partner, date=False, uom_id=False):
@@ -280,7 +280,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 {key: price[0] for key, price in pycompat.items(self.price_rule_get(prod_id, qty, partner=partner))}
+ return {key: price[0] for key, price in self.price_rule_get(prod_id, qty, partner=partner).items()}
@api.multi
def price_rule_get_multi(self, products_by_qty_by_partner):
diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py
index ccfbcf28b96..c9a2411f173 100644
--- a/addons/product/models/product_template.py
+++ b/addons/product/models/product_template.py
@@ -171,7 +171,7 @@ class ProductTemplate(models.Model):
quantity = self._context.get('quantity', 1.0)
# Support context pricelists specified as display_name or ID for compatibility
- if isinstance(pricelist_id_or_name, basestring):
+ if isinstance(pricelist_id_or_name, pycompat.string_types):
pricelist_data = self.env['product.pricelist'].name_search(pricelist_id_or_name, operator='=', limit=1)
if pricelist_data:
pricelist = self.env['product.pricelist'].browse(pricelist_data[0][0])
diff --git a/addons/product_expiry/models/production_lot.py b/addons/product_expiry/models/production_lot.py
index 89d2a7569ff..6be9a2865dd 100644
--- a/addons/product_expiry/models/production_lot.py
+++ b/addons/product_expiry/models/production_lot.py
@@ -2,7 +2,6 @@
# 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):
@@ -54,5 +53,5 @@ class StockProductionLot(models.Model):
@api.onchange('product_id')
def _onchange_product(self):
dates_dict = self._get_dates()
- for field, value in pycompat.items(dates_dict):
+ for field, value in dates_dict.items():
setattr(self, field, value)
diff --git a/addons/product_margin/models/product_product.py b/addons/product_margin/models/product_product.py
index f203a9e146b..3dd735b26a4 100644
--- a/addons/product_margin/models/product_product.py
+++ b/addons/product_margin/models/product_product.py
@@ -4,7 +4,6 @@
import time
from odoo import api, fields, models
-from odoo.tools import pycompat
class ProductProduct(models.Model):
@@ -139,6 +138,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 pycompat.items(res[val.id]):
+ for k, v in res[val.id].items():
setattr(val, k, v)
return res
diff --git a/addons/project/models/project.py b/addons/project/models/project.py
index cfe3f91aff5..8e20f1bc029 100644
--- a/addons/project/models/project.py
+++ b/addons/project/models/project.py
@@ -500,7 +500,7 @@ class Task(models.Model):
_check_rec(eview)
- res['arch'] = etree.tostring(eview)
+ res['arch'] = etree.tostring(eview, encoding='unicode')
# replace reference of 'Hours' to 'Day(s)'
for f in res['fields']:
diff --git a/addons/purchase/models/account_invoice.py b/addons/purchase/models/account_invoice.py
index c1c62f32da6..350e8ad8684 100644
--- a/addons/purchase/models/account_invoice.py
+++ b/addons/purchase/models/account_invoice.py
@@ -146,7 +146,6 @@ class AccountInvoice(models.Model):
# reference_account_id is the stock input account
reference_account_id = i_line.product_id.product_tmpl_id.get_product_accounts(fiscal_pos=fpos)['stock_input'].id
diff_res = []
- account_prec = inv.company_id.currency_id.decimal_places
# calculate and write down the possible price difference between invoice price and product price
for line in res:
if line.get('invl_id', 0) == i_line.id and reference_account_id == line['account_id']:
@@ -180,13 +179,13 @@ class AccountInvoice(models.Model):
if child.type_tax_use != 'none':
tax_ids.append((4, child.id, None))
price_before = line.get('price', 0.0)
- line.update({'price': round(valuation_price_unit * line['quantity'], account_prec)})
+ line.update({'price': company_currency.round(valuation_price_unit * line['quantity'])})
diff_res.append({
'type': 'src',
'name': i_line.name[:64],
- 'price_unit': round(price_unit - valuation_price_unit, account_prec),
+ 'price_unit': company_currency.round(price_unit - valuation_price_unit),
'quantity': line['quantity'],
- 'price': round(price_before - line.get('price', 0.0), account_prec),
+ 'price': company_currency.round(price_before - line.get('price', 0.0)),
'account_id': acc,
'product_id': line['product_id'],
'uom_id': line['uom_id'],
diff --git a/addons/rating/models/rating.py b/addons/rating/models/rating.py
index 78369403d2d..f4bd17b48f3 100644
--- a/addons/rating/models/rating.py
+++ b/addons/rating/models/rating.py
@@ -1,12 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
+import base64
import uuid
from odoo import api, fields, models, tools, _
from odoo.modules.module import get_resource_path
-from odoo.tools import pycompat
RATING_LIMIT_SATISFIED = 7
RATING_LIMIT_OK = 3
@@ -70,7 +69,7 @@ class Rating(models.Model):
for rating in self:
try:
image_path = get_resource_path('rating', 'static/src/img', 'rating_%s.png' % (int(rating.rating),))
- rating.rating_image = open(image_path, 'rb').read().encode('base64')
+ rating.rating_image = base64.b64decode(open(image_path, 'rb').read())
except (IOError, OSError):
rating.rating_image = False
@@ -275,7 +274,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(pycompat.values(values))
+ rating_number = sum(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 915c97848ec..8a4f86d3b96 100644
--- a/addons/rating_project/models/project.py
+++ b/addons/rating_project/models/project.py
@@ -3,7 +3,6 @@
from datetime import timedelta
-from odoo.tools import pycompat
from odoo import api, fields, models, _
from odoo.tools.safe_eval import safe_eval
@@ -80,13 +79,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(pycompat.values(activity)) if sum(pycompat.values(activity)) else -1
+ project.percentage_satisfaction_project = activity['great'] * 100 / sum(activity.values()) if sum(activity.values()) 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(pycompat.values(activity)) if sum(pycompat.values(activity)) else -1
+ self.percentage_satisfaction_task = activity['great'] * 100 / sum(activity.values()) if sum(activity.values()) 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 8116c0f50f1..cd3d541d1ef 100644
--- a/addons/rating_project_issue/models/project_issue.py
+++ b/addons/rating_project_issue/models/project_issue.py
@@ -4,7 +4,6 @@
from datetime import timedelta
from odoo import api, fields, models
-from odoo.tools import pycompat
from odoo.tools.safe_eval import safe_eval
@@ -63,11 +62,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(pycompat.values(activity_task))
+ activity_sum = sum(activity_task.values())
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(pycompat.values(activity_issue))
+ activity_sum += sum(activity_issue.values())
project.percentage_satisfaction_project = activity_great * 100 / activity_sum if activity_sum else -1
@api.one
@@ -75,7 +74,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(pycompat.values(activity)) if sum(pycompat.values(activity)) else -1
+ self.percentage_satisfaction_issue = activity['great'] * 100 / sum(activity.values()) if sum(activity.values()) 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 cd26abaabc8..b50faa855fc 100644
--- a/addons/resource/models/resource.py
+++ b/addons/resource/models/resource.py
@@ -420,8 +420,12 @@ class ResourceCalendar(models.Model):
dtstart=start_dt,
until=end_dt,
byweekday=self._get_weekdays()):
- start_time = day.date() == start_dt.date() and start_dt.time() or datetime.time.min
- end_time = day.date() == end_dt.date() and end_dt.time() or datetime.time.max
+ start_time = datetime.time.min
+ if day.date() == start_dt.date():
+ start_time = start_dt.time()
+ end_time = datetime.time.max
+ if day.date() == end_dt.date() and end_dt.time() != datetime.time():
+ end_time = end_dt.time()
intervals = self._get_day_work_intervals(
day.date(),
@@ -445,8 +449,12 @@ class ResourceCalendar(models.Model):
dtstart=start_dt,
until=end_dt,
byweekday=self._get_weekdays()):
- start_time = day.date() == start_dt.date() and start_dt.time() or datetime.time.min
- end_time = day.date() == end_dt.date() and end_dt.time() or datetime.time.max
+ start_time = datetime.time.min
+ if day.date() == start_dt.date():
+ start_time = start_dt.time()
+ end_time = datetime.time.max
+ if day.date() == end_dt.date() and end_dt.time() != datetime.time():
+ end_time = end_dt.time()
intervals = self._get_day_leave_intervals(
day.date(),
diff --git a/addons/resource/models/resource_mixin.py b/addons/resource/models/resource_mixin.py
index 8b922dd5ff8..9c21f7f935f 100644
--- a/addons/resource/models/resource_mixin.py
+++ b/addons/resource/models/resource_mixin.py
@@ -5,7 +5,6 @@ from datetime import timedelta
from odoo import api, fields, models
-
class ResourceMixin(models.AbstractModel):
_name = "resource.mixin"
_description = 'Resource Mixin'
@@ -70,7 +69,7 @@ class ResourceMixin(models.AbstractModel):
for day_intervals in calendar._iter_leave_intervals(from_datetime, to_datetime, self.resource_id.id):
theoric_hours = self.get_day_work_hours_count(day_intervals[0][0].date(), calendar=calendar)
leave_time = sum((interval[1] - interval[0] for interval in day_intervals), timedelta())
- days_count += round((leave_time.total_seconds()/3600 / theoric_hours) * 4) / 4
+ days_count += round((leave_time.total_seconds() / 3600 / theoric_hours) * 4) / 4
return days_count
def iter_leaves(self, from_datetime, to_datetime, calendar=None):
diff --git a/addons/sale/models/account_invoice.py b/addons/sale/models/account_invoice.py
index 5163f4baa6c..c4ad56d1277 100644
--- a/addons/sale/models/account_invoice.py
+++ b/addons/sale/models/account_invoice.py
@@ -3,7 +3,6 @@
from itertools import groupby
from odoo import api, fields, models, _
-from odoo.tools import pycompat
class AccountInvoice(models.Model):
@@ -58,7 +57,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 pycompat.items(line._fields):
+ for name, field in line._fields.items():
if name == 'sale_line_ids':
result[i][2][name] = [(6, 0, line[name].ids)]
line[name] = False
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index 7e554c6af08..8fe5623e339 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -8,7 +8,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, pycompat
+from odoo.tools import float_is_zero, float_compare, DEFAULT_SERVER_DATETIME_FORMAT
from odoo.tools.misc import formatLang
from odoo.addons import decimal_precision as dp
@@ -400,7 +400,7 @@ class SaleOrder(models.Model):
if not invoices:
raise UserError(_('There is no invoicable line.'))
- for invoice in pycompat.values(invoices):
+ for invoice in invoices.values():
if not invoice.invoice_line_ids:
raise UserError(_('There is no invoicable line.'))
# If invoice is negative, do a refund invoice instead
@@ -417,7 +417,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 pycompat.values(invoices)]
+ return [inv.id for inv in invoices.values()]
@api.multi
def action_draft(self):
@@ -557,7 +557,7 @@ class SaleOrder(models.Model):
if tax.include_base_amount:
base_tax += tax.compute_all(price_reduce + base_tax, quantity=1, product=line.product_id,
partner=self.partner_shipping_id)['taxes'][0]['amount']
- res = sorted(pycompat.items(res), key=lambda l: l[0].sequence)
+ res = sorted(res.items(), 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 733bd74bc2a..96f2f896f44 100644
--- a/addons/sale/models/sale_analytic.py
+++ b/addons/sale/models/sale_analytic.py
@@ -3,7 +3,6 @@
from odoo import api, fields, models, _
from odoo.exceptions import UserError
-from odoo.tools import pycompat
class SaleOrderLine(models.Model):
@@ -41,7 +40,7 @@ class SaleOrderLine(models.Model):
qty = d['unit_amount']
lines[line] += qty
- for line, qty in pycompat.items(lines):
+ for line, qty in lines.items():
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 84dbee6a3fd..2b6c082b150 100644
--- a/addons/sale/tests/test_sale_order.py
+++ b/addons/sale/tests/test_sale_order.py
@@ -2,7 +2,6 @@
# 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
@@ -18,10 +17,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 pycompat.items(self.products)],
+ '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.values()],
'pricelist_id': self.env.ref('product.list0').id,
})
- self.assertEqual(so.amount_total, sum([2 * p.list_price for (k, p) in pycompat.items(self.products)]), 'Sale: total amount is wrong')
+ self.assertEqual(so.amount_total, sum([2 * p.list_price for p in self.products.values()]), 'Sale: total amount is wrong')
# send quotation
so.force_quotation_send()
@@ -36,7 +35,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 pycompat.items(self.products)]), 'Sale: invoice total amount is wrong')
+ self.assertEqual(inv.amount_total, sum([2 * p.list_price if p.invoice_policy == 'order' else 0 for p in self.products.values()]), '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')
@@ -47,7 +46,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 pycompat.items(self.products)]), '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 p in self.products.values()]), '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
@@ -72,7 +71,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 pycompat.items(self.products)],
+ '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.values()],
'pricelist_id': self.env.ref('product.list0').id,
})
diff --git a/addons/sale_stock/tests/test_sale_stock.py b/addons/sale_stock/tests/test_sale_stock.py
index 5a4755d2c16..3dd6d5103ca 100644
--- a/addons/sale_stock/tests/test_sale_stock.py
+++ b/addons/sale_stock/tests/test_sale_stock.py
@@ -3,7 +3,6 @@
from odoo.addons.sale.tests.test_sale_common import TestSale
from odoo.exceptions import UserError
-from odoo.tools import pycompat
class TestSaleStock(TestSale):
@@ -17,7 +16,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 pycompat.items(self.products)],
+ '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.values()],
'pricelist_id': self.env.ref('product.list0').id,
'picking_policy': 'direct',
})
@@ -74,7 +73,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 pycompat.items(self.products)],
+ '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.values()],
'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 8ac7fc638d7..8d5e419fb90 100644
--- a/addons/stock/models/procurement.py
+++ b/addons/stock/models/procurement.py
@@ -9,8 +9,7 @@ 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, pycompat
+from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_compare, float_round
import logging
@@ -311,7 +310,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 pycompat.items(location_data):
+ for location_id, location_data in location_data.items():
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/product.py b/addons/stock/models/product.py
index 43cbe1e2e4d..693f5c0119e 100644
--- a/addons/stock/models/product.py
+++ b/addons/stock/models/product.py
@@ -161,7 +161,7 @@ class Product(models.Model):
if self.env.context.get('location', False):
if isinstance(self.env.context['location'], pycompat.integer_types):
location_ids = [self.env.context['location']]
- elif isinstance(self.env.context['location'], basestring):
+ elif isinstance(self.env.context['location'], pycompat.string_types):
domain = [('complete_name', 'ilike', self.env.context['location'])]
if self.env.context.get('force_company', False):
domain += [('company_id', '=', self.env.context['force_company'])]
@@ -172,7 +172,7 @@ class Product(models.Model):
if self.env.context.get('warehouse', False):
if isinstance(self.env.context['warehouse'], pycompat.integer_types):
wids = [self.env.context['warehouse']]
- elif isinstance(self.env.context['warehouse'], basestring):
+ elif isinstance(self.env.context['warehouse'], pycompat.string_types):
domain = [('name', 'ilike', self.env.context['warehouse'])]
if self.env.context.get('force_company', False):
domain += [('company_id', '=', self.env.context['force_company'])]
diff --git a/addons/stock/models/stock_inventory.py b/addons/stock/models/stock_inventory.py
index 866f3e2104c..bb14b839d32 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, pycompat
+from odoo.tools import float_utils
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 pycompat.items(product_data) if item[1] is None]:
+ for void_field in [item[0] for item in product_data.items() 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 5eab237011e..3c27e5ba6ec 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -10,7 +10,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, pycompat
+from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.tools.float_utils import float_compare, float_round, float_is_zero
@@ -595,7 +595,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 pycompat.items(to_assign):
+ for moves in to_assign.values():
moves.assign_picking()
self._push_apply()
return self
@@ -767,7 +767,7 @@ class StockMove(models.Model):
grouped_move_lines_out[k] = sum(self.env['stock.move.line'].concat(*list(g)).mapped('product_qty'))
available_move_lines = {key: grouped_move_lines_in[key] - grouped_move_lines_out.get(key, 0) for key in grouped_move_lines_in.keys()}
# pop key if the quantity available amount to 0
- available_move_lines = dict((k, v) for k, v in available_move_lines.iteritems() if v)
+ available_move_lines = dict((k, v) for k, v in available_move_lines.items() if v)
if not available_move_lines:
continue
diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py
index 536ac2a5e6d..de736ef0bcf 100644
--- a/addons/stock/models/stock_picking.py
+++ b/addons/stock/models/stock_picking.py
@@ -7,7 +7,7 @@ import time
from itertools import groupby
from odoo import api, fields, models, _
-from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, pycompat
+from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.tools.float_utils import float_compare, float_round
from odoo.addons.procurement.models import procurement
from odoo.exceptions import UserError
@@ -665,7 +665,7 @@ class Picking(models.Model):
self.ensure_one()
moves = self.env['stock.move']
for move_line in self.move_line_ids:
- for product, remaining_qty in pycompat.items(move_line._get_remaining_prod_quantities()):
+ for product, remaining_qty in move_line._get_remaining_prod_quantities().items():
if float_compare(remaining_qty, 0, precision_rounding=product.uom_id.rounding) > 0:
vals = self._prepare_values_extra_move(move_line, product, remaining_qty)
moves |= moves.create(vals)
diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py
index 5609f5be6a6..5e99ebc841e 100644
--- a/addons/stock/models/stock_quant.py
+++ b/addons/stock/models/stock_quant.py
@@ -5,7 +5,6 @@ from psycopg2 import OperationalError
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
-from odoo.tools import pycompat
from odoo.osv import expression
@@ -175,7 +174,7 @@ class StockQuant(models.Model):
if quant.quantity == 0 and quant.reserved_quantity == 0:
quant.unlink()
break
- except OperationalError, e:
+ except OperationalError as e:
if e.pgcode == '55P03': # could not obtain the lock
continue
else:
@@ -274,7 +273,7 @@ class QuantPackage(models.Model):
@api.multi
def name_get(self):
- return list(pycompat.items(self._compute_complete_name()))
+ return list(self._compute_complete_name().items())
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 1f7e44f23ea..d8bd64253c6 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, pycompat
+from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
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 pycompat.items(sub_locations):
+ for field_name, values in sub_locations.items():
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 pycompat.items(data):
+ for field_name in data:
data[field_name].update(create_data[field_name])
- for picking_type, values in pycompat.items(data):
+ for picking_type, values in data.items():
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
@@ -598,8 +598,8 @@ 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 pycompat.items(picking_type_values):
- getattr(self, field_name).write(values)
+ for field_name, values in picking_type_values.items():
+ self[field_name].write(values)
@api.multi
def _update_name_and_code(self, new_name=False, new_code=False):
diff --git a/addons/stock/wizard/make_procurement.py b/addons/stock/wizard/make_procurement.py
index 369995e7a15..2fa3db75b5d 100644
--- a/addons/stock/wizard/make_procurement.py
+++ b/addons/stock/wizard/make_procurement.py
@@ -2,7 +2,6 @@
# 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):
@@ -51,8 +50,8 @@ class MakeProcurement(models.TransientModel):
@api.onchange('product_id')
def onchange_product_id(self):
if self.product_id:
- for key, value in pycompat.items(self.onchange_product_id_dict(self.product_id.id)):
- setattr(self, key, value)
+ for key, value in self.onchange_product_id_dict(self.product_id.id).items():
+ self[key] = value
@api.model
def create(self, values):
diff --git a/addons/stock_account/models/account_invoice.py b/addons/stock_account/models/account_invoice.py
index 6291da2922f..281d15de0e3 100644
--- a/addons/stock_account/models/account_invoice.py
+++ b/addons/stock_account/models/account_invoice.py
@@ -94,7 +94,7 @@ class AccountInvoiceLine(models.Model):
price = company_currency.with_context(date=self.invoice_id.date_invoice).compute(price_unit * self.quantity, self.invoice_id.currency_id)
else:
price = price_unit * self.quantity
- return round(price, self.invoice_id.currency_id.decimal_places)
+ return self.invoice_id.currency_id.round(price)
def get_invoice_line_account(self, type, product, fpos, company):
if company.anglo_saxon_accounting and type in ('in_invoice', 'in_refund') and product and product.type == 'product':
diff --git a/addons/stock_landed_costs/models/stock_landed_cost.py b/addons/stock_landed_costs/models/stock_landed_cost.py
index aeb6e42366f..5fd5ce8f63b 100644
--- a/addons/stock_landed_costs/models/stock_landed_cost.py
+++ b/addons/stock_landed_costs/models/stock_landed_cost.py
@@ -7,7 +7,6 @@ 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 StockMove(models.Model):
@@ -123,7 +122,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 pycompat.items(val_to_cost_lines)):
+ for cost_line, val_amount in val_to_cost_lines.items()):
return False
return True
@@ -204,9 +203,8 @@ class LandedCost(models.Model):
towrite_dict[valuation.id] = value
else:
towrite_dict[valuation.id] += value
- if towrite_dict:
- for key, value in pycompat.items(towrite_dict):
- AdjustementLines.browse(key).write({'additional_landed_cost': value})
+ for key, value in towrite_dict.items():
+ AdjustementLines.browse(key).write({'additional_landed_cost': value})
return True
diff --git a/addons/survey/controllers/main.py b/addons/survey/controllers/main.py
index 553fc5ad4b2..150503f07da 100644
--- a/addons/survey/controllers/main.py
+++ b/addons/survey/controllers/main.py
@@ -174,7 +174,7 @@ class WebsiteSurvey(http.Controller):
answer_tag = "%s_%s" % (answer_tag, 'comment')
answer_value = answer.value_text
elif answer.answer_type == 'number':
- answer_value = answer.value_number.__str__()
+ answer_value = str(answer.value_number)
elif answer.answer_type == 'date':
answer_value = answer.value_date
elif answer.answer_type == 'suggestion' and not answer.value_suggested_row:
diff --git a/addons/survey/models/survey.py b/addons/survey/models/survey.py
index 84692c63f9d..7bcf53f67ab 100644
--- a/addons/survey/models/survey.py
+++ b/addons/survey/models/survey.py
@@ -15,8 +15,6 @@ 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__)
@@ -26,7 +24,7 @@ def dict_keys_startswith(dictionary, string):
.. note::
This function uses dictionary comprehensions (Python >= 2.7)
"""
- return {k: v for k, v in pycompat.items(dictionary) if k.startswith(string)}
+ return {k: v for k, v in dictionary.items() if k.startswith(string)}
class SurveyStage(models.Model):
@@ -240,7 +238,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': list(pycompat.values(answers)), 'comments': comments}
+ result_summary = {'answers': list(answers.values()), 'comments': comments}
# Calculate and return statistics for matrix
if question.type == 'matrix':
@@ -640,7 +638,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(not answer.strip() for answer in pycompat.values(answer_candidates)) and answer_candidates:
+ if all(not answer.strip() for answer in answer_candidates.values()) 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):
diff --git a/addons/survey/tests/test_survey.py b/addons/survey/tests/test_survey.py
index 699aac0cde3..b5f9897438b 100644
--- a/addons/survey/tests/test_survey.py
+++ b/addons/survey/tests/test_survey.py
@@ -13,8 +13,6 @@ 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):
@@ -179,7 +177,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 pycompat.items(urltypes):
+ for urltype, urltxt in urltypes.items():
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))
@@ -211,8 +209,8 @@ 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 pycompat.items(expected_values):
- self.assertEqual(getattr(answer, field), value, msg="Unable to answer the survey. Expected behaviour of %s is not proper." % (field))
+ for field, value in expected_values.items():
+ self.assertEqual(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):
question = self.env['survey.question'].sudo(self.survey_manager).create({
@@ -226,9 +224,7 @@ class TestSurvey(TransactionCase):
lines = [line.value_suggested.id for line in question.user_input_line_ids]
answers = [{'text': label.value, 'count': lines.count(label.id), 'answer_id': label.id} for label in question.labels_ids]
prp_result = self.env['survey.survey'].prepare_result(question)['answers']
- answers.sort()
- prp_result.sort()
- self.assertEqual(prp_result, answers, msg="Statistics of simple, multiple choice questions are different from expectation")
+ self.assertItemsEqual(prp_result, answers, msg="Statistics of simple, multiple choice questions are different from expectation")
def test_11_survey_result_matrix(self):
question = self.env['survey.question'].sudo(self.survey_manager).create({
@@ -267,7 +263,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 pycompat.items(actions):
+ for action, val in actions.items():
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/survey/wizard/survey_email_compose_message.py b/addons/survey/wizard/survey_email_compose_message.py
index fb36d839c57..f23d836bc30 100644
--- a/addons/survey/wizard/survey_email_compose_message.py
+++ b/addons/survey/wizard/survey_email_compose_message.py
@@ -8,6 +8,7 @@ from werkzeug import urls
from odoo import api, fields, models, _
from odoo.exceptions import UserError
+from odoo.tools import pycompat
emails_split = re.compile(r"[;,\n\r]+")
email_validator = re.compile(r"[^@]+@[^@]+\.[^@]+")
@@ -119,7 +120,7 @@ class SurveyMailComposeMessage(models.TransientModel):
if wizard.public != 'email_private':
return None
else:
- token = uuid.uuid4().__str__()
+ token = pycompat.text_type(uuid.uuid4())
# create response with token
survey_user_input = SurveyUserInput.create({
'survey_id': wizard.survey_id.id,
diff --git a/addons/utm/models/utm.py b/addons/utm/models/utm.py
index 5cb04d51425..b73329c1011 100644
--- a/addons/utm/models/utm.py
+++ b/addons/utm/models/utm.py
@@ -3,6 +3,7 @@
from odoo import api, fields, models, SUPERUSER_ID
from odoo.http import request
+from odoo.tools import pycompat
class UtmMedium(models.Model):
@@ -72,7 +73,7 @@ class UtmMixin(models.AbstractModel):
# ir_http dispatch saves the url params in a cookie
value = request.httprequest.cookies.get(cookie_name)
# if we receive a string for a many2one, we search/create the id
- if field.type == 'many2one' and isinstance(value, basestring) and value:
+ if field.type == 'many2one' and isinstance(value, pycompat.string_types) and value:
Model = self.env[field.comodel_name]
records = Model.search([('name', '=', value)], limit=1)
if not records:
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index 9a29c2ec3e8..abf019371dc 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -1,13 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
import babel.messages.pofile
import base64
-import csv
import datetime
import functools
import glob
import hashlib
import imghdr
+import io
import itertools
import jinja2
import json
@@ -22,8 +23,8 @@ import werkzeug.utils
import werkzeug.wrappers
import zlib
from xml.etree import ElementTree
-from cStringIO import StringIO
from werkzeug import url_decode
+from werkzeug import iri_to_uri
import odoo
@@ -117,12 +118,11 @@ def ensure_db(redirect='/web/database/selector'):
# Thus, we redirect the user to the same page but with the session cookie set.
# This will force using the database route dispatcher...
r = request.httprequest
- url_redirect = r.base_url
+ url_redirect = werkzeug.urls.url_parse(r.base_url)
if r.query_string:
- # Can't use werkzeug.wrappers.BaseRequest.url with encoded hashes:
- # https://github.com/amigrave/werkzeug/commit/b4a62433f2f7678c234cdcac6247a869f90a7eb7
- url_redirect += '?' + r.query_string
- response = werkzeug.utils.redirect(url_redirect, 302)
+ # in P3, request.query_string is bytes, the rest is text, can't mix them
+ query_string = iri_to_uri(r.query_string)
+ url_redirect = url_redirect.replace(query=query_string)
request.session.db = db
abort_and_redirect(url_redirect)
@@ -963,7 +963,7 @@ class Binary(http.Controller):
def force_contenttype(self, headers, contenttype='image/png'):
dictheaders = dict(headers)
dictheaders['Content-Type'] = contenttype
- return list(pycompat.items(dictheaders))
+ return list(dictheaders.items())
@http.route(['/web/content',
'/web/content/',
@@ -1136,8 +1136,8 @@ class Binary(http.Controller):
""", (uid,))
row = cr.fetchone()
if row and row[0]:
- image_base64 = str(row[0]).decode('base64')
- image_data = StringIO(image_base64)
+ image_base64 = base64.b64decode(row[0])
+ image_data = io.BytesIO(image_base64)
imgext = '.' + (imghdr.what(None, h=image_base64) or 'png')
response = http.send_file(image_data, filename=imgname + imgext, mtime=row[1])
else:
@@ -1216,7 +1216,7 @@ class Export(http.Controller):
else:
fields['.id'] = fields.pop('id', {'string': 'ID'})
- fields_sequence = sorted(pycompat.items(fields),
+ fields_sequence = sorted(fields.items(),
key=lambda field: odoo.tools.ustr(field[1].get('string', '')))
records = []
@@ -1227,7 +1227,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 pycompat.values(field.get('states', {}))):
+ for attrs in field.get('states', {}).values()):
continue
if not field.get('exportable', True):
continue
@@ -1319,7 +1319,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 pycompat.items(self.fields_info(model, export_fields)))
+ for k, v in self.fields_info(model, export_fields).items())
class ExportFormat(object):
raw_data = False
@@ -1387,32 +1387,22 @@ class CSVExport(ExportFormat, http.Controller):
return base + '.csv'
def from_data(self, fields, rows):
- fp = StringIO()
- writer = csv.writer(fp, quoting=csv.QUOTE_ALL)
+ fp = io.BytesIO()
+ writer = pycompat.csv_writer(fp, quoting=1)
- writer.writerow([name.encode('utf-8') for name in fields])
+ writer.writerow(fields)
for data in rows:
row = []
for d in data:
- if isinstance(d, unicode):
- try:
- d = d.encode('utf-8')
- except UnicodeError:
- pass
- if d is False: d = None
-
# Spreadsheet apps tend to detect formulas on leading =, + and -
- if type(d) is str and d.startswith(('=', '-', '+')):
+ if isinstance(d, pycompat.string_types) and d.startswith(('=', '-', '+')):
d = "'" + d
- row.append(d)
+ row.append(pycompat.to_text(d))
writer.writerow(row)
- fp.seek(0)
- data = fp.read()
- fp.close()
- return data
+ return fp.getvalue()
class ExcelExport(ExportFormat, http.Controller):
# Excel needs raw data to correctly handle numbers and date values
@@ -1445,7 +1435,7 @@ class ExcelExport(ExportFormat, http.Controller):
for row_index, row in enumerate(rows):
for cell_index, cell_value in enumerate(row):
cell_style = base_style
- if isinstance(cell_value, basestring):
+ if isinstance(cell_value, pycompat.string_types):
cell_value = re.sub("\r", " ", cell_value)
elif isinstance(cell_value, datetime.datetime):
cell_style = datetime_style
@@ -1453,7 +1443,7 @@ class ExcelExport(ExportFormat, http.Controller):
cell_style = date_style
worksheet.write(row_index + 1, cell_index, cell_value, cell_style)
- fp = StringIO()
+ fp = io.BytesIO()
workbook.save(fp)
fp.seek(0)
data = fp.read()
diff --git a/addons/web_diagram/controllers/main.py b/addons/web_diagram/controllers/main.py
index 56b6c76eefe..d29b60df40e 100644
--- a/addons/web_diagram/controllers/main.py
+++ b/addons/web_diagram/controllers/main.py
@@ -2,7 +2,6 @@
# 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
@@ -46,7 +45,7 @@ class DiagramView(http.Controller):
isolate_nodes[blnk_node['id']] = blnk_node
y = [
t['y']
- for t in pycompat.values(nodes)
+ for t in nodes.values()
if t['x'] == 20
if t['y']
]
@@ -99,11 +98,11 @@ class DiagramView(http.Controller):
color='white',
options={}
)
- for color, expr in pycompat.items(bgcolors):
+ for color, expr in bgcolors.items():
if safe_eval(expr, act):
n['color'] = color
- for shape, expr in pycompat.items(shapes):
+ for shape, expr in shapes.items():
if safe_eval(expr, act):
n['shape'] = shape
diff --git a/addons/web_editor/controllers/main.py b/addons/web_editor/controllers/main.py
index 5a3eca776ef..50a9540323a 100644
--- a/addons/web_editor/controllers/main.py
+++ b/addons/web_editor/controllers/main.py
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-import cStringIO
+import base64
import io
import json
import logging
@@ -14,11 +13,11 @@ from lxml import etree, html
from odoo.http import request
from odoo import http, tools
+from odoo.tools import pycompat
from odoo.modules.module import get_resource_path, get_module_path
logger = logging.getLogger(__name__)
-
class Web_Editor(http.Controller):
#------------------------------------------------------
# Backend snippet
@@ -40,7 +39,7 @@ class Web_Editor(http.Controller):
debug=request.debug)
for k in kwargs:
- if isinstance(kwargs[k], basestring) and kwargs[k].isdigit():
+ if isinstance(kwargs[k], pycompat.string_types) and kwargs[k].isdigit():
kwargs[k] = int(kwargs[k])
trans = dict(
@@ -96,7 +95,7 @@ class Web_Editor(http.Controller):
font_obj = ImageFont.truetype(addons_path + font, size)
# if received character is not a number, keep old behaviour (icon is character)
- icon = unichr(int(icon)) if icon.isdigit() else icon
+ icon = pycompat.unichr(int(icon)) if icon.isdigit() else icon
# Determine the dimensions of the icon
image = Image.new("RGBA", (size, size), color=(0, 0, 0, 0))
@@ -165,7 +164,7 @@ class Web_Editor(http.Controller):
for c_file in request.httprequest.files.getlist('upload'):
data = c_file.read()
try:
- image = Image.open(cStringIO.StringIO(data))
+ image = Image.open(io.BytesIO(data))
w, h = image.size
if w*h > 42e6: # Nokia Lumia 1020 photo resolution
raise ValueError(
@@ -178,7 +177,7 @@ class Web_Editor(http.Controller):
attachment = Attachments.create({
'name': c_file.filename,
- 'datas': data.encode('base64'),
+ 'datas': base64.b64encode(data),
'datas_fname': c_file.filename,
'public': True,
'res_model': 'ir.ui.view',
@@ -187,7 +186,7 @@ class Web_Editor(http.Controller):
uploads += attachments.read(['name', 'mimetype', 'checksum', 'url'])
except Exception as e:
logger.exception("Failed to upload image to attachment")
- message = unicode(e)
+ message = pycompat.text_type(e)
return """'
+ replacement = u''
view.save(replacement, xpath='/t/p/h1')
self.assertIn(
- replacement.replace('&', '&'),
+ replacement.replace(u'&', u'&'),
view.arch,
'inline script should be escaped server side'
)
self.assertIn(
replacement,
- view.render(),
+ view.render().decode('utf-8'),
'inline script should not be escaped when rendering'
)
# common text nodes should be be escaped client side
- replacement = 'world & <b>cie'
+ replacement = u'world & <b>cie'
view.save(replacement, xpath='/t/p')
self.assertIn(replacement, view.arch, 'common text node should not be escaped server side')
self.assertIn(
replacement,
- view.render().replace('&', '&'),
+ view.render().decode('utf-8').replace(u'&', u'&'),
'text node characters wrongly unescaped when rendering'
)
@@ -225,7 +224,8 @@ class TestViewSaving(common.TransactionCase):
node = html.tostring(h.SPAN(
"Acme Corporation",
- attrs(model='res.company', id=company_id, field="name", expression='bob', type='char')))
+ attrs(model='res.company', id=company_id, field="name", expression='bob', type='char')),
+ encoding='unicode')
View = self.env['ir.ui.view']
View.browse(company_id).save(value=node)
self.assertEqual(company.name, "Acme Corporation")
diff --git a/addons/website_blog/controllers/main.py b/addons/website_blog/controllers/main.py
index c400b8b792f..a7e930d8e9f 100644
--- a/addons/website_blog/controllers/main.py
+++ b/addons/website_blog/controllers/main.py
@@ -288,8 +288,8 @@ class WebsiteBlog(http.Controller):
"id": message.id,
"author_name": message.author_id.name,
"author_image": message.author_id.image and \
- ("data:image/png;base64,%s" % message.author_id.image) or \
- '/website_blog/static/src/img/anonymous.png',
+ (b"data:image/png;base64,%s" % message.author_id.image) or \
+ b'/website_blog/static/src/img/anonymous.png',
"date": message.date,
'body': html2plaintext(message.body),
'website_published' : message.website_published,
diff --git a/addons/website_blog/models/website_blog.py b/addons/website_blog/models/website_blog.py
index 22456302949..979ae14c5d3 100644
--- a/addons/website_blog/models/website_blog.py
+++ b/addons/website_blog/models/website_blog.py
@@ -209,7 +209,7 @@ class BlogPost(models.Model):
def write(self, vals):
self.ensure_one()
if 'website_published' in vals and 'published_date' not in vals:
- if self.published_date <= fields.Datetime.now():
+ if (self.published_date or '') <= fields.Datetime.now():
vals['published_date'] = vals['website_published'] and fields.Datetime.now()
result = super(BlogPost, self).write(vals)
self._check_for_publication(vals)
diff --git a/addons/website_crm_partner_assign/controllers/main.py b/addons/website_crm_partner_assign/controllers/main.py
index 3c18c7cf470..6e300e66cd0 100644
--- a/addons/website_crm_partner_assign/controllers/main.py
+++ b/addons/website_crm_partner_assign/controllers/main.py
@@ -13,7 +13,6 @@ 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
@@ -151,7 +150,7 @@ class WebsiteAccount(website_account):
'pager': pager,
'searchbar_sortings': searchbar_sortings,
'sortby': sortby,
- 'searchbar_filters': OrderedDict(sorted(pycompat.items(searchbar_filters))),
+ 'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
'filterby': filterby,
})
return request.render("website_crm_partner_assign.portal_my_opportunities", values)
diff --git a/addons/website_crm_partner_assign/models/crm_lead.py b/addons/website_crm_partner_assign/models/crm_lead.py
index f1f002d5dd0..b73be955c42 100644
--- a/addons/website_crm_partner_assign/models/crm_lead.py
+++ b/addons/website_crm_partner_assign/models/crm_lead.py
@@ -5,7 +5,6 @@ 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):
@@ -45,7 +44,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 pycompat.items(salesmans_leads):
+ for salesman_id, leads_ids in salesmans_leads.items():
leads = self.browse(leads_ids)
leads.write({'user_id': salesman_id})
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 cb2fbf3ba03..5aa8073192f 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,7 +3,6 @@
from odoo import api, fields, models, _
from odoo.exceptions import UserError
-from odoo.tools import pycompat
class CrmLeadForwardToPartner(models.TransientModel):
@@ -86,7 +85,7 @@ class CrmLeadForwardToPartner(models.TransientModel):
else:
partners_leads[partner.id] = {'partner': partner, 'leads': [lead_details]}
- for partner_id, partner_leads in pycompat.items(partners_leads):
+ for partner_id, partner_leads in partners_leads.items():
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 d6b10b37dc1..e7318a64abf 100644
--- a/addons/website_customer/controllers/main.py
+++ b/addons/website_customer/controllers/main.py
@@ -73,7 +73,7 @@ class WebsiteCustomer(http.Controller):
'country_id_count': 0,
'country_id': (country.id, country.name)
})
- countries.sort(key=lambda d: d['country_id'] and d['country_id'][1])
+ countries.sort(key=lambda d: (d['country_id'] or (0, ""))[1])
countries.insert(0, {
'country_id_count': country_count,
diff --git a/addons/website_event/controllers/main.py b/addons/website_event/controllers/main.py
index cd020a74372..5aed9a25ffa 100644
--- a/addons/website_event/controllers/main.py
+++ b/addons/website_event/controllers/main.py
@@ -9,7 +9,6 @@ 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):
@@ -80,7 +79,7 @@ class WebsiteEventController(http.Controller):
def dom_without(without):
domain = [('state', "in", ['draft', 'confirm', 'done'])]
- for key, search in pycompat.items(domain_search):
+ for key, search in domain_search.items():
if key != without:
domain += search
return domain
@@ -234,16 +233,16 @@ class WebsiteEventController(http.Controller):
''' Process data posted from the attendee details form. '''
registrations = {}
global_values = {}
- for key, value in pycompat.items(details):
+ for key, value in details.items():
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 pycompat.items(global_values):
- for registration in pycompat.values(registrations):
+ for key, value in global_values.items():
+ for registration in registrations.values():
registration[key] = value
- return list(pycompat.values(registrations))
+ return list(registrations.values())
@http.route(['/event//registration/confirm'], type='http', auth="public", methods=['POST'], website=True)
def registration_confirm(self, event, **post):
diff --git a/addons/website_event_questions/controllers/main.py b/addons/website_event_questions/controllers/main.py
index e709e0b3d06..684999a26ff 100644
--- a/addons/website_event_questions/controllers/main.py
+++ b/addons/website_event_questions/controllers/main.py
@@ -3,8 +3,6 @@
from odoo.addons.website_event.controllers.main import WebsiteEventController
-from odoo.tools import pycompat
-
class WebsiteEvent(WebsiteEventController):
@@ -13,7 +11,7 @@ class WebsiteEvent(WebsiteEventController):
registrations = super(WebsiteEvent, self)._process_registration_details(details)
for registration in registrations:
answer_ids = []
- for key, value in pycompat.items(registration):
+ for key, value in registration.items():
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 324fa1026ed..7ccb7f9e4b5 100644
--- a/addons/website_event_sale/controllers/main.py
+++ b/addons/website_event_sale/controllers/main.py
@@ -4,7 +4,6 @@
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):
@@ -16,7 +15,7 @@ class WebsiteEventSaleController(WebsiteEventController):
def _process_tickets_details(self, data):
ticket_post = {}
- for key, value in pycompat.items(data):
+ for key, value in data.items():
if not key.startswith('nb_register') or '-' not in key:
continue
items = key.split('-')
diff --git a/addons/website_event_track/controllers/main.py b/addons/website_event_track/controllers/main.py
index 3fb37210589..f11aa0bea13 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, pycompat
+from odoo.tools import html_escape as escape, html2plaintext
class WebsiteEventTrackController(http.Controller):
@@ -68,14 +68,14 @@ class WebsiteEventTrackController(http.Controller):
@http.route(['''/event//agenda'''], type='http', auth="public", website=True)
def event_agenda(self, event, tag=None, **post):
days_tracks = collections.defaultdict(lambda: [])
- for track in event.track_ids.sorted(lambda track: (track.date, bool(track.location_id))):
+ for track in event.track_ids.sorted(lambda track: (track.date or '', bool(track.location_id))):
if not track.date:
continue
days_tracks[track.date[:10]].append(track)
days = {}
tracks_by_days = {}
- for day, tracks in pycompat.items(days_tracks):
+ for day, tracks in days_tracks.items():
tracks_by_days[day] = tracks
days[day] = self._prepare_calendar(event, tracks)
diff --git a/addons/website_event_track/views/event_track_templates.xml b/addons/website_event_track/views/event_track_templates.xml
index c8282180215..14eee014c6f 100644
--- a/addons/website_event_track/views/event_track_templates.xml
+++ b/addons/website_event_track/views/event_track_templates.xml
@@ -15,7 +15,7 @@