[MERGE] P3: apply text/io model correction & final fixes

Closes #16919
This commit is contained in:
Olivier Dony
2017-08-20 23:27:44 +02:00
250 changed files with 1722 additions and 1848 deletions
+1 -1
View File
@@ -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, _
@@ -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
+11 -12
View File
@@ -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))
+5 -5
View File
@@ -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:
+1 -3
View File
@@ -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),
+3 -3
View File
@@ -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
@@ -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,
@@ -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...)
+2 -4
View File
@@ -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:
+3 -3
View File
@@ -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 = '<span>%s</span>' % message_description
for name, values in pycompat.items(tracked_values):
for name, values in tracked_values.items():
message += '<div> &nbsp; &nbsp; &bull; <b>%s</b>: ' % name
message += '%s</div>' % values
return message
+1 -1
View File
@@ -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
@@ -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):
+2 -1
View File
@@ -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)))
@@ -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
+1 -2
View File
@@ -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")
-2
View File
@@ -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
@@ -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
@@ -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
@@ -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:
@@ -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
+14 -20
View File
@@ -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,
}]
+1 -3
View File
@@ -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()))
+37 -39
View File
@@ -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({
@@ -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
+1 -1
View File
@@ -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,
+4 -8
View File
@@ -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')
+4 -2
View File
@@ -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")
+20 -17
View File
@@ -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]
+4 -2
View File
@@ -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)
+3 -1
View File
@@ -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]])
+2 -2
View File
@@ -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)
+3 -41
View File
@@ -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
-123
View File
@@ -1,123 +0,0 @@
# RFC 2822 - style email validation for Python
# (c) 2012 Syrus Akbary <me@syrusakbary.com>
# Extended from (c) 2011 Noel Bush <noel@aitools.org>
# 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 *
+7 -21
View File
@@ -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:
+1 -2
View File
@@ -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
+1 -3
View File
@@ -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'})
+4 -4
View File
@@ -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)
@@ -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 !
+3 -4
View File
@@ -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
+2 -2
View File
@@ -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
+2 -4
View File
@@ -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
@@ -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')),
@@ -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'):
+4 -2
View File
@@ -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')
@@ -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
+1 -3
View File
@@ -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):
+12 -11
View File
@@ -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
+25 -26
View File
@@ -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('-', '')
@@ -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)
+22 -27
View File
@@ -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__':
+5 -5
View File
@@ -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)
+4 -6
View File
@@ -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 = {}
+5 -6
View File
@@ -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)
+2 -1
View File
@@ -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
+4 -4
View File
@@ -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
+3 -4
View File
@@ -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
+2 -3
View File
@@ -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
+12 -13
View File
@@ -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()
+17 -17
View File
@@ -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 = '<div>%s</div>' % html
root = lxml.html.fromstring(html)
html = u'<div>%s</div>' % 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('<div>') and html.endswith('</div>'):
if html.startswith(u'<div>') and html.endswith(u'</div>'):
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']):
+27 -23
View File
@@ -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))
+1 -2
View File
@@ -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
+5 -4
View File
@@ -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 = '<p>Test Body</p>', '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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
}]
+3 -3
View File
@@ -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
@@ -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]
+2 -1
View File
@@ -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
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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,
+1 -2
View File
@@ -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)
@@ -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',
+5 -5
View File
@@ -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')
+3 -3
View File
@@ -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="<provider>"' attribute, then it
required if record.provider is <provider>. """
for acquirer in self:
if any(getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k] for k, f in 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
+3 -5
View File
@@ -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)
@@ -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)
+1 -1
View File
@@ -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):
@@ -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]])
)
+1 -2
View File
@@ -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)
+2 -3
View File
@@ -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)
+4 -3
View File
@@ -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)
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+5 -5
View File
@@ -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
+1 -1
View File
@@ -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]])
+4 -4
View File
@@ -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):
+1 -1
View File
@@ -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])
@@ -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)
@@ -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
+1 -1
View File
@@ -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']:
+3 -4
View File
@@ -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'],
+3 -4
View File
@@ -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,
+2 -3
View File
@@ -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)
@@ -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)
+12 -4
View File
@@ -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(),
+1 -2
View File
@@ -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):
+1 -2
View File
@@ -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
+4 -4
View File
@@ -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
+1 -2
View File
@@ -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
+5 -6
View File
@@ -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,
})
+2 -3
View File
@@ -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',
})

Some files were not shown because too many files have changed in this diff Show More