[FIX] builtins removed from Python 3

* Reverse wrapper courtesy of @rco-odoo's original P3 branch
* thin compat module stripped down from werkzeug (to augment as needed)

issue 8530
This commit is contained in:
xmo-odoo
2017-04-27 13:59:33 +02:00
committed by GitHub
parent de4a2216b9
commit 2e6a589f41
73 changed files with 464 additions and 199 deletions
+3 -3
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
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, pycompat
from odoo.exceptions import UserError, ValidationError
from odoo import api, fields, models, _
@@ -383,7 +383,7 @@ class AccountJournal(models.Model):
account_code_prefix = company.bank_account_code_prefix or ''
else:
account_code_prefix = company.cash_account_code_prefix or company.bank_account_code_prefix or ''
for num in xrange(1, 100):
for num in pycompat.range(1, 100):
new_code = str(account_code_prefix.ljust(code_digits - 1, '0')) + str(num)
rec = self.env['account.account'].search([('code', '=', new_code), ('company_id', '=', company.id)], limit=1)
if not rec:
@@ -412,7 +412,7 @@ class AccountJournal(models.Model):
if not vals.get('code'):
journal_code_base = (vals['type'] == 'cash' and 'CSH' or 'BNK')
journals = self.env['account.journal'].search([('code', 'like', journal_code_base + '%'), ('company_id', '=', company_id)])
for num in xrange(1, 100):
for num in pycompat.range(1, 100):
# journal_code has a maximal size of 5, hence we can enforce the boundary num < 100
journal_code = journal_code_base + str(num)
if journal_code not in journals.mapped('code'):
@@ -2,7 +2,7 @@
from odoo import api, fields, models, _
from odoo.osv import expression
from odoo.tools import float_is_zero
from odoo.tools import float_is_zero, pycompat
from odoo.tools import float_compare, float_round
from odoo.tools.misc import formatLang
from odoo.exceptions import UserError, ValidationError
@@ -874,10 +874,10 @@ class AccountBankStatementLine(models.Model):
for aml_dict in counterpart_aml_dicts:
if aml_dict['move_line'].reconciled:
raise UserError(_('A selected move line was already reconciled.'))
if isinstance(aml_dict['move_line'], (int, long)):
if isinstance(aml_dict['move_line'], pycompat.integer_types):
aml_dict['move_line'] = aml_obj.browse(aml_dict['move_line'])
for aml_dict in (counterpart_aml_dicts + new_aml_dicts):
if aml_dict.get('tax_ids') and aml_dict['tax_ids'] and isinstance(aml_dict['tax_ids'][0], (int, long)):
if aml_dict.get('tax_ids') and isinstance(aml_dict['tax_ids'][0], pycompat.integer_types):
# Transform the value in the format required for One2many and Many2many fields
aml_dict['tax_ids'] = map(lambda id: (4, id, None), aml_dict['tax_ids'])
+2 -1
View File
@@ -8,6 +8,7 @@ from datetime import datetime, timedelta
from urlparse import urljoin
from odoo import api, fields, models, _
from odoo.tools import pycompat
class SignupError(Exception):
@@ -16,7 +17,7 @@ class SignupError(Exception):
def random_token():
# the token has an entropy of about 120 bits (6 bits/char * 20 chars)
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
return ''.join(random.SystemRandom().choice(chars) for i in xrange(20))
return ''.join(random.SystemRandom().choice(chars) for _ in pycompat.range(20))
def now(**kwargs):
dt = datetime.now() + timedelta(**kwargs)
+24 -21
View File
@@ -17,7 +17,7 @@ import uuid
from odoo import api, fields, models
from odoo import tools
from odoo.tools.translate import _
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, pycompat
from odoo.exceptions import UserError, ValidationError
@@ -47,7 +47,7 @@ def calendar_id2real_id(calendar_id=None, with_date=False):
def get_real_ids(ids):
if isinstance(ids, (basestring, int, long)):
if isinstance(ids, (basestring, pycompat.integer_types)):
return calendar_id2real_id(ids)
if isinstance(ids, (list, tuple)):
@@ -62,6 +62,15 @@ def is_calendar_id(record_id):
return len(str(record_id).split('-')) != 1
SORT_ALIASES = {
'start': 'sort_start',
'start_date': 'sort_start',
'start_datetime': 'sort_start',
}
def sort_remap(f):
return SORT_ALIASES.get(f, f)
class Contacts(models.Model):
_name = 'calendar.contacts'
@@ -1062,23 +1071,17 @@ class Meeting(models.Model):
continue
result_data.append(meeting.get_search_fields(order_fields, r_date=r_start_date))
if order_fields:
uniq = lambda it: collections.OrderedDict((id(x), x) for x in it).values()
def comparer(left, right):
for fn, mult in comparers:
result = cmp(fn(left), fn(right))
if result:
return mult * result
return 0
sort_params = [key.split()[0] if key[-4:].lower() != 'desc' else '-%s' % key.split()[0] for key in (order or self._order).split(',')]
sort_params = uniq([comp if comp not in ['start', 'start_date', 'start_datetime'] else 'sort_start' for comp in sort_params])
sort_params = uniq([comp if comp not in ['-start', '-start_date', '-start_datetime'] else '-sort_start' for comp in sort_params])
comparers = [((itemgetter(col[1:]), -1) if col[0] == '-' else (itemgetter(col), 1)) for col in sort_params]
ids = [r['id'] for r in sorted(result_data, cmp=comparer)]
return ids
# seq of (field, should_reverse)
sort_spec = list(tools.unique(
(sort_remap(key.split()[0]), key.lower().endswith(' desc'))
for key in (order or self._order).split(',')
))
def key(record):
return [
tools.Reverse(record[name]) if desc else record[name]
for name, desc in sort_spec
]
return [r['id'] for r in sorted(result_data, key=key)]
@api.multi
def _rrule_serialize(self):
@@ -1154,7 +1157,7 @@ class Meeting(models.Model):
data['final_date'] = rule._until and rule._until.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
#repeat weekly
if rule._byweekday:
for i in xrange(0, 7):
for i in pycompat.range(0, 7):
if i in rule._byweekday:
data[day_list[i]] = True
data['rrule_type'] = 'weekly'
@@ -1466,7 +1469,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, int, long)) and len(ls) >= 2:
if not isinstance(ls, (basestring, pycompat.integer_types)) and len(ls) >= 2:
res['start'] = ls[1]
res['stop'] = ls[2]
+47
View File
@@ -97,3 +97,50 @@ class TestCalendar(TransactionCase):
self.assertEqual(calendar_event_sprint_review.month_by, 'day', 'rrule_type should be mothly')
self.assertEqual(calendar_event_sprint_review.byday, '1', 'rrule_type should be mothly')
self.assertEqual(calendar_event_sprint_review.week_list, 'MO', 'rrule_type should be mothly')
def test_event_order(self):
""" check the ordering of events when searching """
def create_event(name, date):
return self.CalendarEvent.create({
'name': name,
'start': date + ' 12:00:00',
'stop': date + ' 14:00:00',
'duration': 2.0,
})
foo1 = create_event('foo', '2011-04-01')
foo2 = create_event('foo', '2011-06-01')
bar1 = create_event('bar', '2011-05-01')
bar2 = create_event('bar', '2011-06-01')
domain = [('id', 'in', (foo1 + foo2 + bar1 + bar2).ids)]
# sort them by name only
events = self.CalendarEvent.search(domain, order='name')
self.assertEqual(events.mapped('name'), ['bar', 'bar', 'foo', 'foo'])
events = self.CalendarEvent.search(domain, order='name desc')
self.assertEqual(events.mapped('name'), ['foo', 'foo', 'bar', 'bar'])
# sort them by start date only
events = self.CalendarEvent.search(domain, order='start')
self.assertEqual(events.mapped('start'), (foo1 + bar1 + foo2 + bar2).mapped('start'))
events = self.CalendarEvent.search(domain, order='start desc')
self.assertEqual(events.mapped('start'), (foo2 + bar2 + bar1 + foo1).mapped('start'))
# sort them by name then start date
events = self.CalendarEvent.search(domain, order='name asc, start asc')
self.assertEqual(list(events), [bar1, bar2, foo1, foo2])
events = self.CalendarEvent.search(domain, order='name asc, start desc')
self.assertEqual(list(events), [bar2, bar1, foo2, foo1])
events = self.CalendarEvent.search(domain, order='name desc, start asc')
self.assertEqual(list(events), [foo1, foo2, bar1, bar2])
events = self.CalendarEvent.search(domain, order='name desc, start desc')
self.assertEqual(list(events), [foo2, foo1, bar2, bar1])
# sort them by start date then name
events = self.CalendarEvent.search(domain, order='start asc, name asc')
self.assertEqual(list(events), [foo1, bar1, bar2, foo2])
events = self.CalendarEvent.search(domain, order='start asc, name desc')
self.assertEqual(list(events), [foo1, bar1, foo2, bar2])
events = self.CalendarEvent.search(domain, order='start desc, name asc')
self.assertEqual(list(events), [bar2, foo2, bar1, foo1])
events = self.CalendarEvent.search(domain, order='start desc, name desc')
self.assertEqual(list(events), [foo2, bar2, bar1, foo1])
+3 -2
View File
@@ -7,6 +7,7 @@ from datetime import date, datetime, timedelta
from odoo import api, fields, models, _, exceptions
from odoo.osv import expression
from odoo.tools import pycompat
from odoo.tools.safe_eval import safe_eval
_logger = logging.getLogger(__name__)
@@ -281,7 +282,7 @@ class Goal(models.Model):
safe_eval(code, cxt, mode="exec", nocopy=True)
# the result of the evaluated codeis put in the 'result' local variable, propagated to the context
result = cxt.get('result')
if result is not None and isinstance(result, (float, int, long)):
if result is not None and isinstance(result, (float, pycompat.integer_types)):
goals_to_write.update(goal._get_write_values(result))
else:
_logger.error(
@@ -322,7 +323,7 @@ class Goal(models.Model):
for goal in [g for g in goals if g.id in query_goals]:
for user_value in user_values:
queried_value = field_name in user_value and user_value[field_name] or False
if isinstance(queried_value, tuple) and len(queried_value) == 2 and isinstance(queried_value[0], (int, long)):
if isinstance(queried_value, tuple) and len(queried_value) == 2 and isinstance(queried_value[0], pycompat.integer_types):
queried_value = queried_value[0]
if queried_value == query_goals[goal.id]:
new_value = user_value.get(field_name+'_count', goal.current)
+2 -2
View File
@@ -74,12 +74,12 @@ class TestPayslipFlow(TestPayslipBase):
# I print the payslip report
data, format = render_report(self.env.cr, self.env.uid, richard_payslip.ids, 'hr_payroll.report_payslip', {}, {})
if config.get('test_report_directory'):
file(os.path.join(config['test_report_directory'], 'hr_payroll-payslip.'+ format), 'wb+').write(data)
open(os.path.join(config['test_report_directory'], 'hr_payroll-payslip.'+ format), 'wb+').write(data)
# I print the payslip details report
data, format = render_report(self.env.cr, self.env.uid, richard_payslip.ids, 'hr_payroll.report_payslipdetails', {}, {})
if config.get('test_report_directory'):
file(os.path.join(config['test_report_directory'], 'hr_payroll-payslipdetails.'+ format), 'wb+').write(data)
open(os.path.join(config['test_report_directory'], 'hr_payroll-payslipdetails.'+ format), 'wb+').write(data)
# I print the contribution register report
context = {'model': 'hr.contribution.register', 'active_ids': [self.ref('hr_payroll.hr_houserent_register')]}
+1 -2
View File
@@ -168,8 +168,7 @@ class StyleStack:
def to_escpos(self):
""" converts the current style to an escpos command string """
cmd = ''
ordered_cmds = self.cmds.keys()
ordered_cmds.sort(lambda x,y: cmp(self.cmds[x]['_order'], self.cmds[y]['_order']))
ordered_cmds = sorted(self.cmds.keys(), key=lambda x: self.cmds[x]['_order'])
for style in ordered_cmds:
cmd += self.cmds[style][self.get(style)]
return cmd
+2 -1
View File
@@ -4,6 +4,7 @@
import base64
from odoo import api, models
from odoo.tools import pycompat
class MailTemplate(models.Model):
@@ -20,7 +21,7 @@ class MailTemplate(models.Model):
rslt = super(MailTemplate, self).generate_email(res_ids, fields)
multi_mode = True
if isinstance(res_ids, (int, long)):
if isinstance(res_ids, pycompat.integer_types):
res_ids = [res_ids]
multi_mode = False
@@ -40,4 +40,4 @@ class TestPaymentAdvice(TestPaymentAdviceBase):
# In order to test the PDF report defined on a Payment Advice, we will print a Print Advice Report when NEFT is checked
data, format = render_report(self.env.cr, self.env.uid, payment_advice.ids, 'l10n_in_hr_payroll.report_payrolladvice', {}, {})
if config.get('test_report_directory'):
file(os.path.join(config['test_report_directory'], 'l10n_in_hr_payroll_summary_report' + format), 'wb+').write(data)
open(os.path.join(config['test_report_directory'], 'l10n_in_hr_payroll_summary_report' + format), 'wb+').write(data)
+7 -5
View File
@@ -7,6 +7,8 @@ import copy
import datetime
import dateutil.relativedelta as relativedelta
import logging
import functools
import lxml
import urlparse
@@ -15,6 +17,7 @@ from urllib import urlencode, quote as quote
from odoo import _, api, fields, models, tools
from odoo import report as odoo_report
from odoo.exceptions import UserError
from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -90,10 +93,9 @@ try:
'max': max,
'sum': sum,
'filter': filter,
'reduce': reduce,
'reduce': functools.reduce,
'map': map,
'round': round,
'cmp': cmp,
# dateutil.relativedelta is an old-style class and cannot be directly
# instanciated wihtin a jinja2 expression, so a lambda "proxy" is
@@ -340,7 +342,7 @@ class MailTemplate(models.Model):
:param int res_ids: list of ids of document records those mails are related to.
"""
multi_mode = True
if isinstance(res_ids, (int, long)):
if isinstance(res_ids, pycompat.integer_types):
multi_mode = False
res_ids = [res_ids]
@@ -385,7 +387,7 @@ class MailTemplate(models.Model):
@api.multi
def get_email_template(self, res_ids):
multi_mode = True
if isinstance(res_ids, (int, long)):
if isinstance(res_ids, pycompat.integer_types):
res_ids = [res_ids]
multi_mode = False
@@ -450,7 +452,7 @@ class MailTemplate(models.Model):
"""
self.ensure_one()
multi_mode = True
if isinstance(res_ids, (int, long)):
if isinstance(res_ids, pycompat.integer_types):
res_ids = [res_ids]
multi_mode = False
if fields is None:
+2 -1
View File
@@ -22,6 +22,7 @@ from lxml import etree
from werkzeug import url_encode
from odoo import _, api, exceptions, fields, models, tools
from odoo.tools import pycompat
from odoo.tools.safe_eval import safe_eval
@@ -1811,7 +1812,7 @@ class MailThread(models.AbstractModel):
partner_ids.add(partner_id[1])
if isinstance(partner_id, (list, tuple)) and partner_id[0] == 6 and len(partner_id) == 3:
partner_ids |= set(partner_id[2])
elif isinstance(partner_id, (int, long)):
elif isinstance(partner_id, pycompat.integer_types):
partner_ids.add(partner_id)
else:
pass # we do not manage anything else
+6 -5
View File
@@ -4,6 +4,8 @@
import logging
import threading
from odoo.tools.misc import split_every
from odoo import _, api, fields, models, registry, SUPERUSER_ID
from odoo.osv import expression
@@ -107,9 +109,8 @@ class Partner(models.Model):
@api.model
def _notify_send(self, body, subject, recipients, **mail_values):
emails = self.env['mail.mail']
recipients_nbr, recipients_max = len(recipients), 50
email_chunks = [recipients[x:x + recipients_max] for x in xrange(0, len(recipients), recipients_max)]
for email_chunk in email_chunks:
recipients_nbr = len(recipients)
for email_chunk in split_every(50, recipients.ids):
# TDE FIXME: missing message parameter. So we will find mail_message_id
# in the mail_values and browse it. It should already be in the
# cache so should not impact performances.
@@ -117,9 +118,9 @@ class Partner(models.Model):
message = self.env['mail.message'].browse(mail_message_id) if mail_message_id else None
if message and message.model and message.res_id and message.model in self.env and hasattr(self.env[message.model], 'message_get_recipient_values'):
tig = self.env[message.model].browse(message.res_id)
recipient_values = tig.message_get_recipient_values(notif_message=message, recipient_ids=email_chunk.ids)
recipient_values = tig.message_get_recipient_values(notif_message=message, recipient_ids=email_chunk)
else:
recipient_values = self.env['mail.thread'].message_get_recipient_values(notif_message=None, recipient_ids=email_chunk.ids)
recipient_values = self.env['mail.thread'].message_get_recipient_values(notif_message=None, recipient_ids=email_chunk)
create_values = {
'body_html': body,
'subject': subject,
+2 -1
View File
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, api, exceptions, fields, models
from odoo.tools import pycompat
class Users(models.Model):
@@ -97,7 +98,7 @@ class Users(models.Model):
current_pids.append(partner_id[1])
elif isinstance(partner_id, (list, tuple)) and partner_id[0] == 6 and len(partner_id) == 3:
current_pids.append(partner_id[2])
elif isinstance(partner_id, (int, long)):
elif isinstance(partner_id, pycompat.integer_types):
current_pids.append(partner_id)
if user_pid not in current_pids:
partner_ids.append(user_pid)
+3 -2
View File
@@ -5,6 +5,7 @@ import base64
import re
from odoo import _, api, fields, models, SUPERUSER_ID, tools
from odoo.tools import pycompat
from odoo.tools.safe_eval import safe_eval
@@ -434,7 +435,7 @@ class MailComposer(models.TransientModel):
"""
self.ensure_one()
multi_mode = True
if isinstance(res_ids, (int, long)):
if isinstance(res_ids, pycompat.integer_types):
multi_mode = False
res_ids = [res_ids]
@@ -484,7 +485,7 @@ class MailComposer(models.TransientModel):
""" Call email_template.generate_email(), get fields relevant for
mail.compose.message, transform email_cc and email_to into partner_ids """
multi_mode = True
if isinstance(res_ids, (int, long)):
if isinstance(res_ids, pycompat.integer_types):
multi_mode = False
res_ids = [res_ids]
+1 -1
View File
@@ -344,7 +344,7 @@ class PaymentTxOgone(models.Model):
'USERID': account.ogone_userid,
'PSWD': account.ogone_password,
'ORDERID': reference,
'AMOUNT': long(self.amount * 100),
'AMOUNT': self.amount * 100,
'CURRENCY': self.currency_id.name,
'OPERATION': 'SAL',
'ECI': 2, # Recurring (from MOTO)
+3 -1
View File
@@ -9,6 +9,8 @@ from odoo.osv import expression
import odoo.addons.decimal_precision as dp
from odoo.tools import pycompat
class ProductCategory(models.Model):
_name = "product.category"
@@ -184,7 +186,7 @@ class ProductProduct(models.Model):
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]])
elif isinstance(pricelist_id_or_name, (int, long)):
elif isinstance(pricelist_id_or_name, pycompat.integer_types):
pricelist = self.env['product.pricelist'].browse(pricelist_id_or_name)
if pricelist:
+2 -1
View File
@@ -8,6 +8,7 @@ import odoo.addons.decimal_precision as dp
from odoo import api, fields, models, tools, _
from odoo.exceptions import ValidationError, except_orm
from odoo.tools import pycompat
class ProductTemplate(models.Model):
@@ -164,7 +165,7 @@ class ProductTemplate(models.Model):
# Support context pricelists specified as display_name or ID for compatibility
if isinstance(pricelist_id_or_name, basestring):
pricelist = self.env['product.pricelist'].name_search(pricelist_id_or_name, operator='=', limit=1)
elif isinstance(pricelist_id_or_name, (int, long)):
elif isinstance(pricelist_id_or_name, pycompat.integer_types):
pricelist = self.env['product.pricelist'].browse(pricelist_id_or_name)
if pricelist:
+1 -1
View File
@@ -577,7 +577,7 @@ class Report(models.Model):
writer = PdfFileWriter()
streams = [] # We have to close the streams *after* PdfFilWriter's call to write()
for document in documents:
pdfreport = file(document, 'rb')
pdfreport = open(document, 'rb')
streams.append(pdfreport)
reader = PdfFileReader(pdfreport)
for page in range(0, reader.getNumPages()):
@@ -31,4 +31,4 @@ class RepoortIntrastatTest(common.TransactionCase):
def test_00_create_pdf(self):
data, report_format = self.env['ir.actions.report.xml'].render_report(self.invoice.ids, 'report_intrastat.report_intrastatinvoice', {})
if tools.config['test_report_directory']:
file(os.path.join(tools.config['test_report_directory'], 'report_intrastat-intrastat_report.' + report_format), 'wb+').write(data)
open(os.path.join(tools.config['test_report_directory'], 'report_intrastat-intrastat_report.' + report_format), 'wb+').write(data)
+3 -7
View File
@@ -4,6 +4,8 @@
import babel.dates
from datetime import datetime, timedelta, date, time
from dateutil import rrule
from dateutil.relativedelta import relativedelta
from odoo.fields import Date, Datetime
@@ -349,13 +351,7 @@ class TestWorkDays(TestResourceCommon):
]
})
self._days = [
date.fromordinal(o)
for o in xrange(
WAR_START.toordinal(),
WAR_END.toordinal() + 1
)
]
self._days = [dt.date() for dt in rrule.rrule(rrule.DAILY, dtstart=WAR_START, until=WAR_END)]
def test_trivial_calendar_no_leaves(self):
""" If leaves are not involved, only calendar attendances (basic
+4 -4
View File
@@ -56,11 +56,11 @@ class AccountInvoice(models.Model):
def _refund_cleanup_lines(self, lines):
result = super(AccountInvoice, self)._refund_cleanup_lines(lines)
if self.env.context.get('mode') == 'modify':
for i in xrange(0, len(lines)):
for name, field in lines[i]._fields.iteritems():
for i, line in enumerate(lines):
for name, field in line._fields.iteritems():
if name == 'sale_line_ids':
result[i][2][name] = [(6, 0, lines[i][name].ids)]
lines[i][name] = False
result[i][2][name] = [(6, 0, line[name].ids)]
line[name] = False
return result
@api.multi
+3 -2
View File
@@ -4,6 +4,7 @@
from collections import defaultdict
from datetime import datetime
from dateutil.relativedelta import relativedelta
from odoo.tools.misc import split_every
from psycopg2 import OperationalError
from odoo import api, fields, models, registry, _
@@ -239,9 +240,9 @@ class ProcurementOrder(models.Model):
# Search all confirmed stock_moves and try to assign them
confirmed_moves = self.env['stock.move'].search([('state', '=', 'confirmed')], limit=None, order='priority desc, date_expected asc')
for x in xrange(0, len(confirmed_moves.ids), 100):
for moves_chunk in split_every(100, confirmed_moves.ids):
# TDE CLEANME: muf muf
self.env['stock.move'].browse(confirmed_moves.ids[x:x + 100]).action_assign()
self.env['stock.move'].browse(moves_chunk).action_assign()
if use_new_cursor:
self._cr.commit()
if use_new_cursor:
+4 -3
View File
@@ -4,6 +4,7 @@
from odoo import api, fields, models, _
from odoo.addons import decimal_precision as dp
from odoo.exceptions import UserError
from odoo.tools import pycompat
from odoo.tools.float_utils import float_round
from datetime import datetime
import operator as py_operator
@@ -180,7 +181,7 @@ class Product(models.Model):
location_ids = []
if self.env.context.get('location', False):
if isinstance(self.env.context['location'], (int, long)):
if isinstance(self.env.context['location'], pycompat.integer_types):
location_ids = [self.env.context['location']]
elif isinstance(self.env.context['location'], basestring):
domain = [('complete_name', 'ilike', self.env.context['location'])]
@@ -191,7 +192,7 @@ class Product(models.Model):
location_ids = self.env.context['location']
else:
if self.env.context.get('warehouse', False):
if isinstance(self.env.context['warehouse'], (int, long)):
if isinstance(self.env.context['warehouse'], pycompat.integer_types):
wids = [self.env.context['warehouse']]
elif isinstance(self.env.context['warehouse'], basestring):
domain = [('name', 'ilike', self.env.context['warehouse'])]
@@ -326,7 +327,7 @@ class Product(models.Model):
@api.model
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
res = super(Product, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)
if self._context.get('location') and isinstance(self._context['location'], (int, long)):
if self._context.get('location') and isinstance(self._context['location'], pycompat.integer_types):
location = self.env['stock.location'].browse(self._context['location'])
fields = res.get('fields')
if fields:
+2 -1
View File
@@ -7,6 +7,7 @@ from lxml import etree, html
from odoo.exceptions import AccessError
from odoo import api, models
from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -121,7 +122,7 @@ class IrUiView(models.Model):
def _view_obj(self, view_id):
if isinstance(view_id, basestring):
return self.env.ref(view_id)
elif isinstance(view_id, (int, long)):
elif isinstance(view_id, pycompat.integer_types):
return self.browse(view_id)
# assume it's already a view object (WTF?)
return view_id
+3 -2
View File
@@ -10,6 +10,7 @@ from odoo import tools
from odoo.addons.website.models import website
from odoo.http import request
from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -63,7 +64,7 @@ class View(models.Model):
return views.filter_duplicate()
else:
return self.env.ref(view_id)
elif isinstance(view_id, (int, long)):
elif isinstance(view_id, pycompat.integer_types):
return self.browse(view_id)
# assume it's already a view object (WTF?)
@@ -72,7 +73,7 @@ class View(models.Model):
@api.model
@tools.ormcache_context('self._uid', 'xml_id', keys=('website_id',))
def get_view_id(self, xml_id):
if 'website_id' in self._context and not isinstance(xml_id, (int, long)):
if 'website_id' in self._context and not isinstance(xml_id, pycompat.integer_types):
domain = [('key', '=', xml_id), '|', ('website_id', '=', self._context['website_id']), ('website_id', '=', False)]
view = self.search(domain, order='website_id', limit=1)
if not view:
+4 -5
View File
@@ -19,7 +19,7 @@ except ImportError:
from odoo import api, fields, models
from odoo import tools
from odoo.tools import ustr
from odoo.tools import ustr, pycompat
from odoo.http import request
from odoo.tools.translate import _
@@ -421,7 +421,7 @@ class Website(models.Model):
@api.model
def get_template(self, template):
View = self.env['ir.ui.view']
if isinstance(template, (int, long)):
if isinstance(template, pycompat.integer_types):
view_id = template
else:
if '.' not in template:
@@ -486,7 +486,7 @@ class Website(models.Model):
'num': pmax
},
"pages": [
{'url': get_url(page), 'num': page} for page in xrange(pmin, pmax+1)
{'url': get_url(page), 'num': page} for page in pycompat.range(pmin, pmax+1)
]
}
@@ -544,8 +544,7 @@ class Website(models.Model):
values = [{}]
convitems = converters.items()
# converters with a domain are processed after the other ones
gd = lambda x: hasattr(x[1], 'domain') and (x[1].domain != '[]')
convitems.sort(lambda x, y: cmp(gd(x), gd(y)))
convitems.sort(key=lambda x: hasattr(x[1], 'domain') and (x[1].domain != '[]'))
for (i, (name, converter)) in enumerate(convitems):
newval = []
for val in values:
+3 -1
View File
@@ -10,6 +10,8 @@ import lxml.html
import odoo
import re
from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -38,7 +40,7 @@ class Crawler(odoo.tests.HttpCase):
_logger.info("%s %s", msg, url)
r = self.url_open(url)
code = r.getcode()
self.assertIn(code, xrange(200, 300), "%s Fetching %s returned error response (%d)" % (msg, url, code))
self.assertIn(code, pycompat.range(200, 300), "%s Fetching %s returned error response (%d)" % (msg, url, code))
if r.info().gettype() == 'text/html':
doc = lxml.html.fromstring(r.read())
+132 -17
View File
@@ -16,13 +16,28 @@ with both.
Here are a few useful steps or reminders to make Python 2 code compatible
with Python 3.
.. important::
This is not a general-purpose guide for porting Python 2 to Python 3, it's
a guide to write 2/3-compatible Odoo code. It does not go through all the
changes in Python but rather through issues which have been found in the
standard Odoo distribution in order to show how to evolve such code such
that it works on both Python 2 and Python 3.
References/useful documents:
* `What's new in Python 3? <https://docs.python.org/3.0/whatsnew/3.0.html>`_
covers many of the changes between Python 2 and Python 3, though it is
missing a number of changes which `were backported to Python 2.7 <https://docs.python.org/2.7/whatsnew/2.7.html#python-3-1-features>`_
as well as :ref:`some feature reintroductions <p3support>` of later Python 3
revisions
* `How do I port to Python 3? <https://eev.ee/blog/2016/07/31/python-faq-how-do-i-port-to-python-3/>`_
* `Python-Future <http://python-future.org/index.html>`_
* `Porting Python 2 code to Python 3 <https://docs.python.org/3/howto/pyporting.html>`_
* `Porting to Python 3: A Guide <http://lucumr.pocoo.org/2010/2/11/porting-to-python-3-a-guide/>`_ (a bit outdated but useful for the extensive comments on strings and IO)
.. _p3support:
Versions Support
================
@@ -37,16 +52,17 @@ features whereas:
* Python 3.4 has no specific compatibility improvement but is the lowest P3
version for PyLint
* Python 3.3 reintroduced the "u" prefix for proper (unicode) strings
* Python 3.2 made ``range`` views more list-like and reintroduced ``callable``
* Python 3.2 made ``range`` views more list-like (backported to 2.7)and
reintroduced ``callable``
.. warning::
Python 3 adds plenty of great features (keyword-only parameters,
generator delegation, pathlib, ...), do not use them until Python 2
support is dropped
While Python 3 adds plenty of great features (keyword-only parameters,
generator delegation, pathlib, ...), you must *not *use them in Odoo
until Python 2 support is dropped
Fixes
=====
Moved and removed
=================
Exception Handlers
------------------
@@ -87,33 +103,132 @@ or::
This form was implemented in Python 2.5 and is thus compatible across the
board.
Removed Operators
-----------------
Operators & keywords
--------------------
.. important::
.. important:: The backtick operator ```foo``` must be converted to an
explicit call to the ``repr()`` builtin
* The backtick operator ``\`foo\``` must be converted to an explicit call
to the ``repr()`` builtin
* The ``<>`` operator must be replaced by ``!=``
.. important:: The ``<>`` operator must be replaced by ``!=``
These two operators were long recommended against/deprecated in Python 2,
Python 3 removed them from the language.
Removed/renamed builtins
------------------------
.. _changed-exec:
.. important:: ``exec`` is now a builtin
In Python 2, ``exec`` is a statement/keyword. Much like ``print``, it's been
converted to a builtin function in Python 3. However because the Python 2
version can take a tuple parameter it is easy to convert the odd ``exec``
statement to the following cross-language forms::
exec(source)
exec(source, globals)
exec(source, globals, locals)
builtins
--------
``cmp``
#######
The ``cmp`` builtin function has been removed from Python 3.
* Most of its uses are in ``cmp=`` parameters to sort functions where it can
usually be replaced by a key function.
* Other uses found were obtaining the sign of an item (``cmp(item, 0)``), this
can be replicated using the standard library's ``math.copysign`` e.g.
``math.copysign(1, item)`` will return ``1.0`` if ``item`` is positive and
``-1.0`` if ``item`` is negative.
``execfile``
############
``execfile(path)`` has been removed completely from Python 3 but it is
trivially replaceable in all cases by::
exec(open(path, 'rb').open())
of a variant thereof (see :ref:`exec changes <changed-exec>` for details)
``file``
########
The ``file`` builtin has been removed in Python 3. Generally, it can just
be replaced by the ``open`` builtin, although you may want to use ``io.open``
which is more flexible and better handles the binary/text dichotomy,
:ref:`a big issue in cross-version Python <changed-strings>`.
.. note::
In Python 3, the ``open`` builtin is actually an alias for ``io.open``.
``long``
########
In Python 2, integers can be either ``int`` or ``long``. Python 3 unifies this
under the single ``int`` type.
.. important::
* the ``L`` suffix for integer literals must be removed
* calls to ``long`` must be replaced by calls to ``int``
* ``(int, long)`` for type-checking purposes must be replaced by
:py:data:`odoo.tools.pycompat.integer_types`
* the ``L`` suffix on numbers is unsupported in Python 3, and unnecessary in
Python 2 as "overflowing" integer literals will implicitly instantiate long.
* in Python 2, a call to ``int()`` will implicitly create a ``long`` object if
necessary.
* type-testing is the last and bigger issue as in Python 2 ``long`` is not a
subtype of ``int`` (nor the reverse), and ``isinstance(value, (int, long))``
is thus generally necessary to catch all integrals.
For that case, Odoo 11 now provides a compatibility module with an
:py:data:`~odoo.tools.pycompat.integer_types` definition which can be used
for type-testing.
It is a tuple of types so when used with ``isinstance`` it can be provided
directly or inside an other tuple alongside other types e.g.
``isinstance(value, (BaseModel, integer_types))``.
However when used with ``type`` directly (which should be avoided) you
should use the ``in`` operator, and if you need other types you need to
concatenate ``integer_types`` to an other tuple.
``reduce``
##########
In Python 3, ``reduce`` has been demoted from builtin to ``functools.reduce``.
However this is because many (if not most) uses of ``reduce`` can be replaced
by ``sum``, ``all``, ``any`` or a list comprehension for a more readable and
faster result.
However this is because *most uses of ``reduce`` can be replaced by ``sum``,
``all``, ``any``* or a list comprehension for a more readable and faster
result.
It is easy enough to just add ``from functools import reduce`` to the file
and compatible with Python 2.6 and later, but consider whether you get better
code by replacing it with some other method altogether.
``xrange``
##########
In Python 3, ``range()`` behaves the same as Python 3's ``xrange``. For
cross-versions code you can:
* just use ``range()`` everywhere and ignore the allocation cost of a list in
Python 2 (often not an issue)
* conditionally alias ``xrange`` to ``range`` in Python 3 and use that
* use a combination of ``itertools.count`` and ``takewhile`` for a
cross-compatible lazy increasing sequence of numbers
.. warning::
In the *rare* cases where you need conditional code (code which applies
for one version of python and not the other), use ``sys.version_info``
e.g. ``sys.version_info() >= (3,)`` for Python3+ code or
``sys.version_info() < (3,)`` for Python 2 code.
Removed/renamed methods
-----------------------
+2 -1
View File
@@ -10,6 +10,7 @@ from odoo import api, fields, models, SUPERUSER_ID, tools, _
from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.modules.registry import Registry
from odoo.osv import expression
from odoo.tools import pycompat
from odoo.tools.safe_eval import safe_eval
_logger = logging.getLogger(__name__)
@@ -1055,7 +1056,7 @@ class IrModelAccess(models.Model):
else:
model_name = model
if isinstance(group_ids, (int, long)):
if isinstance(group_ids, pycompat.integer_types):
group_ids = [group_ids]
query = """ SELECT 1 FROM ir_model_access a
+2 -1
View File
@@ -4,6 +4,7 @@ import ast
from urlparse import urlparse
from lxml import html
from odoo.tools import pycompat
from .qweb import QWeb, Contextifier
from .assetsbundle import AssetsBundle
from lxml import etree
@@ -90,7 +91,7 @@ class IrQWeb(models.AbstractModel, QWeb):
view = self.env['ir.ui.view'].browse(view_id)
return view.inherit_id is not None
if isinstance(name, (int, long)) or is_child_view(name):
if isinstance(name, pycompat.integer_types) or is_child_view(name):
for node in etree.fromstring(template):
if node.get('t-name'):
node.set('t-name', str(name))
+4 -1
View File
@@ -11,6 +11,9 @@ from werkzeug.utils import escape as _escape
from itertools import chain, izip, tee
import __builtin__
from functools import reduce
from odoo.tools import pycompat
builtin_defaults = {name: getattr(__builtin__, name) for name in dir(__builtin__)}
try:
@@ -160,7 +163,7 @@ def foreach_iterator(base_ctx, enum, name):
if not enum:
return
if isinstance(enum, int):
enum = xrange(enum)
enum = pycompat.range(enum)
size = None
if isinstance(enum, Sized):
ctx["%s_size" % name] = size = len(enum)
+2 -1
View File
@@ -8,6 +8,7 @@ from difflib import get_close_matches
from odoo import api, fields, models, tools, SUPERUSER_ID, _
from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.modules import get_module_path, get_module_resource
from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -395,7 +396,7 @@ class IrTranslation(models.Model):
if isinstance(types, basestring):
types = (types,)
if res_id:
if isinstance(res_id, (int, long)):
if isinstance(res_id, pycompat.integer_types):
res_id = (res_id,)
else:
res_id = tuple(res_id)
+2 -2
View File
@@ -133,9 +133,9 @@ class IrUiMenu(models.Model):
if not self._context.get('ir.ui.menu.full_list'):
menus = menus._filter_visible_menus()
if offset:
menus = menus[long(offset):]
menus = menus[offset:]
if limit:
menus = menus[:long(limit)]
menus = menus[:limit]
return len(menus) if count else menus
@api.multi
+3 -3
View File
@@ -22,7 +22,7 @@ from odoo.exceptions import ValidationError
from odoo.http import request
from odoo.modules.module import get_resource_from_path, get_resource_path
from odoo.osv import orm
from odoo.tools import config, graph, ConstantMapping, SKIPPED_ELEMENT_TYPES
from odoo.tools import config, graph, ConstantMapping, SKIPPED_ELEMENT_TYPES, pycompat
from odoo.tools.convert import _fix_multiple_roots
from odoo.tools.parse_version import parse_version
from odoo.tools.safe_eval import safe_eval
@@ -944,7 +944,7 @@ actual arch.
view ID or an XML ID. Note that this method may be overridden for other
kinds of template values.
"""
if isinstance(template, (int, long)):
if isinstance(template, pycompat.integer_types):
return template
if '.' not in template:
raise ValueError('Invalid template id: %r' % template)
@@ -1045,7 +1045,7 @@ actual arch.
@api.multi
def render(self, values=None, engine='ir.qweb'):
assert isinstance(self.id, (int, long))
assert isinstance(self.id, pycompat.integer_types)
qcontext = dict(
env=self.env,
+2 -1
View File
@@ -5,6 +5,7 @@ from operator import itemgetter
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import pycompat
TYPE2FIELD = {
'char': 'value_text',
@@ -70,7 +71,7 @@ class Property(models.Model):
if field == 'value_reference':
if isinstance(value, models.BaseModel):
value = '%s,%d' % (value._name, value.id)
elif isinstance(value, (int, long)):
elif isinstance(value, pycompat.integer_types):
field_id = values.get('fields_id')
if not field_id:
if not prop:
+3 -3
View File
@@ -2,7 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
from odoo.tools import mute_logger
from odoo.tools import mute_logger, pycompat
from odoo.tests import common
from odoo.exceptions import AccessError
@@ -69,8 +69,8 @@ class TestAPI(common.TransactionCase):
self.cr.execute("SELECT COUNT(*) FROM res_partner WHERE active")
count1 = self.cr.fetchone()[0]
count2 = self.env['res.partner'].search([], count=True)
self.assertIsInstance(count1, (int, long))
self.assertIsInstance(count2, (int, long))
self.assertIsInstance(count1, pycompat.integer_types)
self.assertIsInstance(count2, pycompat.integer_types)
self.assertEqual(count1, count2)
@mute_logger('odoo.models')
+1 -2
View File
@@ -214,9 +214,8 @@ class TestExpression(TransactionCase):
# create new company with partners, and partners with no company
company2 = self.env['res.company'].create({'name': 'Acme 2'})
for i in xrange(4):
for i in range(4):
Partner.create({'name': 'P of Acme %s' % i, 'company_id': company2.id})
for i in xrange(4):
Partner.create({'name': 'P of All %s' % i, 'company_id': False})
# check if many2one works with negative empty list
+6 -7
View File
@@ -4,7 +4,7 @@
from math import log10
from odoo.tests.common import TransactionCase
from odoo.tools import float_compare, float_is_zero, float_repr, float_round, float_split_str
from odoo.tools import float_compare, float_is_zero, float_repr, float_round, float_split_str, pycompat
class TestFloatPrecision(TransactionCase):
@@ -98,14 +98,13 @@ class TestFloatPrecision(TransactionCase):
precisions = [2, 2, 2, 2, 2, 2, 3, 4]
# Note: max precision for double floats is 53 bits of precision or
# 17 significant decimal digits
for magnitude in range(7):
for i in xrange(len(fractions)):
frac, exp, prec = fractions[i], expecteds[i], precisions[i]
for magnitude in pycompat.range(7):
for frac, exp, prec in zip(fractions, expecteds, precisions):
for sign in [-1,1]:
for x in xrange(0,10000,97):
n = x * 10**magnitude
for x in pycompat.range(0, 10000, 97):
n = x * 10 ** magnitude
f = sign * (n + frac)
f_exp = ('-' if f != 0 and sign == -1 else '') + str(n) + exp
f_exp = ('-' if f != 0 and sign == -1 else '') + str(n) + exp
try_round(f, f_exp, digits=prec)
def try_zero(amount, expected):
+2 -2
View File
@@ -154,7 +154,7 @@ class TestIrSequenceGenerate(unittest.TestCase):
self.assertTrue(seq)
with environment() as env:
for i in xrange(1, 10):
for i in range(1, 10):
n = env['ir.sequence'].next_by_code('test_sequence_type_5')
self.assertEqual(n, str(i))
@@ -169,7 +169,7 @@ class TestIrSequenceGenerate(unittest.TestCase):
self.assertTrue(seq)
with environment() as env:
for i in xrange(1, 10):
for i in range(1, 10):
n = env['ir.sequence'].next_by_code('test_sequence_type_6')
self.assertEqual(n, str(i))
@@ -116,16 +116,16 @@ class TestIrSequenceDateRangeChangeImplementation(SingleTransactionCase):
seq = self.env['ir.sequence']
seq16 = self.env['ir.sequence'].with_context({'ir_sequence_date': january(16)})
for i in xrange(1, 5):
for i in range(1, 5):
n = seq.next_by_code('test_sequence_date_range_3')
self.assertEqual(n, str(i))
for i in xrange(1, 5):
for i in range(1, 5):
n = seq16.next_by_code('test_sequence_date_range_3')
self.assertEqual(n, str(i))
for i in xrange(1, 5):
for i in range(1, 5):
n = seq.next_by_code('test_sequence_date_range_4')
self.assertEqual(n, str(i))
for i in xrange(1, 5):
for i in range(1, 5):
n = seq16.next_by_code('test_sequence_date_range_4')
self.assertEqual(n, str(i))
+2 -2
View File
@@ -16,14 +16,14 @@ class TestCountingStream(unittest.TestCase):
self.assertEqual(s.index, 0)
def test_single(self):
s = misc.CountingStream(xrange(1))
s = misc.CountingStream(range(1))
self.assertEqual(s.index, -1)
self.assertEqual(next(s, None), 0)
self.assertIsNone(next(s, None))
self.assertEqual(s.index, 1)
def test_full(self):
s = misc.CountingStream(xrange(42))
s = misc.CountingStream(range(42))
for _ in s:
pass
self.assertEqual(s.index, 42)
+2 -2
View File
@@ -5,7 +5,7 @@ from collections import defaultdict
from odoo.exceptions import AccessError, MissingError
from odoo.tests.common import TransactionCase
from odoo.tools import mute_logger
from odoo.tools import mute_logger, pycompat
class TestORM(TransactionCase):
@@ -193,7 +193,7 @@ class TestInherits(TransactionCase):
""" `default_get` cannot return a dictionary or a new id """
defaults = self.env['res.users'].default_get(['partner_id'])
if 'partner_id' in defaults:
self.assertIsInstance(defaults['partner_id'], (bool, int, long))
self.assertIsInstance(defaults['partner_id'], (bool, pycompat.integer_types))
def test_create(self):
""" creating a user should automatically create a new partner """
+2 -1
View File
@@ -3,6 +3,7 @@
from odoo import exceptions
from odoo.tests.common import TransactionCase
from odoo.tools import pycompat
class TestResConfig(TransactionCase):
@@ -42,7 +43,7 @@ class TestResConfig(TransactionCase):
self.assertIsInstance(res, tuple)
self.assertEqual(len(res), 2, "The result should contain 2 elements")
self.assertIsInstance(res[0], basestring)
self.assertIsInstance(res[1], (int, long))
self.assertIsInstance(res[1], pycompat.integer_types)
# Check returned values
self.assertEqual(res[0], self.expected_path)
+3 -1
View File
@@ -2,8 +2,10 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
import sys
from odoo import models, api
from odoo.tools import pycompat
class m(models.Model):
""" This model exposes a few methods that will consume between 'almost no
@@ -37,7 +39,7 @@ class m(models.Model):
t0 = time.clock()
t1 = time.clock()
while t1 - t0 < seconds:
for i in xrange(10000000):
for i in pycompat.range(10000000):
x = i * i
t1 = time.clock()
return True
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from odoo.tests import common
from odoo.tools import pycompat
ANSWER_TO_ULTIMATE_QUESTION = 42
@@ -16,7 +17,7 @@ class TestAttributes(common.TransactionCase):
self.assertTrue(hasattr(instance, 'unknown'))
# Is it the right type ?
self.assertIsInstance(instance.unknown, (int, long))
self.assertIsInstance(instance.unknown, pycompat.integer_types)
# Is it the right value, in case of, we don't know ;-)
self.assertEqual(instance.unknown, ANSWER_TO_ULTIMATE_QUESTION)
@@ -25,6 +25,21 @@ class TestPyLint(TransactionCase):
'E0601', # using variable before assignment
'W0123', # eval used
'W0101', # unreachable code
'long-suffix',
'apply-builtin',
'cmp-builtin',
'coerce-builtin',
'execfile-builtin',
'input-builtin',
'intern-builtin',
'long-builtin',
'raw_input-builtin',
'reload-builtin',
'xrange-builtin',
# TODO: enable once report has been removed
# 'file-builtin',
# 'reduce-builtin',
]
def _skip_test(self, reason):
+2 -3
View File
@@ -15,8 +15,7 @@ import xmlrpclib
import psycopg2
from odoo.sql_db import LazyCursor
from odoo.tools import float_precision, float_repr, float_round, frozendict, \
html_sanitize, human_size, pg_varchar, ustr, OrderedSet
from odoo.tools import float_precision, float_repr, float_round, frozendict, html_sanitize, human_size, pg_varchar, ustr, OrderedSet, pycompat
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
from odoo.tools.translate import html_translate, _
@@ -1656,7 +1655,7 @@ class Binary(Field):
def convert_to_cache(self, value, record, validate=True):
if isinstance(value, buffer):
return str(value)
if isinstance(value, (int, long)) and \
if isinstance(value, pycompat.integer_types) and \
(record._context.get('bin_size') or
record._context.get('bin_size_' + self.name)):
# If the client requests only the size of the field, we return that
+3 -16
View File
@@ -621,10 +621,10 @@ class JsonRequest(WebRequest):
# We need then to manage http sessions manually.
response['session_id'] = self.session.sid
mime = 'application/javascript'
body = "%s(%s);" % (self.jsonp, json.dumps(response),)
body = "%s(%s);" % (self.jsonp, json.dumps(response, default=ustr),)
else:
mime = 'application/json'
body = json.dumps(response)
body = json.dumps(response, default=ustr)
return Response(
body, headers=[('Content-Type', mime),
@@ -695,7 +695,7 @@ def serialize_exception(e):
"name": type(e).__module__ + "." + type(e).__name__ if type(e).__module__ else type(e).__name__,
"debug": traceback.format_exc(),
"message": ustr(e),
"arguments": to_jsonable(e.args),
"arguments": e.args,
"exception_type": "internal_error"
}
if isinstance(e, odoo.exceptions.UserError):
@@ -716,19 +716,6 @@ def serialize_exception(e):
tmp["exception_type"] = "except_orm"
return tmp
def to_jsonable(o):
if isinstance(o, str) or isinstance(o,unicode) or isinstance(o, int) or isinstance(o, long) \
or isinstance(o, bool) or o is None or isinstance(o, float):
return o
if isinstance(o, list) or isinstance(o, tuple):
return [to_jsonable(x) for x in o]
if isinstance(o, dict):
tmp = {}
for k, v in o.items():
tmp[u"%s" % k] = to_jsonable(v)
return tmp
return ustr(o)
class HttpRequest(WebRequest):
""" Handler for the ``http`` request type.
+5 -4
View File
@@ -41,6 +41,7 @@ from lxml import etree
from lxml.builder import E
import odoo
from odoo.tools import pycompat
from . import SUPERUSER_ID
from . import api
from . import tools
@@ -173,7 +174,7 @@ class NewId(object):
def __nonzero__(self):
return False
IdType = (int, long, str, unicode, NewId)
IdType = pycompat.integer_types + (str, unicode, NewId)
# maximum number of prefetched records
@@ -1391,7 +1392,7 @@ class BaseModel(object):
provided domain <reference/orm/domains>`.
"""
res = self.search(args, count=True)
return res if isinstance(res, (int, long)) else len(res)
return res if isinstance(res, pycompat.integer_types) else len(res)
@api.model
@api.returns('self',
@@ -1536,7 +1537,7 @@ class BaseModel(object):
# override defaults with the provided values, never allow the other way around
defaults = self.default_get(list(missing_defaults))
for name, value in defaults.iteritems():
if self._fields[name].type == 'many2many' and value and isinstance(value[0], (int, long)):
if self._fields[name].type == 'many2many' and value and isinstance(value[0], pycompat.integer_types):
# convert a list of ids into a list of commands
defaults[name] = [(6, 0, value)]
elif self._fields[name].type == 'one2many' and value and isinstance(value[0], dict):
@@ -3862,7 +3863,7 @@ class BaseModel(object):
"""
ids, new_ids = [], []
for i in self._ids:
(ids if isinstance(i, (int, long)) else new_ids).append(i)
(ids if isinstance(i, pycompat.integer_types) else new_ids).append(i)
if not ids:
return self
query = """SELECT id FROM "%s" WHERE id IN %%s""" % self._table
+1 -1
View File
@@ -165,7 +165,7 @@ class Node(object):
for attr in ('init', 'update', 'demo'):
if hasattr(self, attr):
setattr(node, attr, True)
self.children.sort(lambda x, y: cmp(x.name, y.name))
self.children.sort(key=lambda x: x.name)
return node
def __setattr__(self, name, value):
+2 -1
View File
@@ -137,7 +137,8 @@ class MigrationManager(object):
try:
fp, fname = tools.file_open(pyfile, pathinfo=True)
if not isinstance(fp, file):
# FIXME: imp.load_source removed in P3, and so is the ``file`` object...
if not isinstance(fp, file):# pylint: disable=file-builtin
# imp.load_source need a real file object, so we create
# one from the file-like object we get from file_open
fp2 = os.tmpfile()
+1 -1
View File
@@ -78,7 +78,7 @@ class AddonsHook(object):
# execute source in context of module *after* putting everything in
# sys.modules, so recursive import works
execfile(modfile, new_mod.__dict__)
exec(open(modfile, 'rb').read(), new_mod.__dict__)
# people import openerp.addons and expect openerp.addons.<module> to work
setattr(odoo.addons, addon_name, new_mod)
+2 -1
View File
@@ -121,6 +121,7 @@ from functools import partial
from zlib import crc32
import odoo.modules
from odoo.tools import pycompat
from ..models import MAGIC_COLUMNS, BaseModel
import odoo.tools as tools
@@ -700,7 +701,7 @@ class expression(object):
names = [value]
elif value and isinstance(value, (tuple, list)) and all(isinstance(item, basestring) for item in value):
names = value
elif isinstance(value, (int, long)):
elif isinstance(value, pycompat.integer_types):
return [value]
if names:
return list({
+3 -2
View File
@@ -10,6 +10,7 @@ import time
import odoo
from odoo.exceptions import UserError, ValidationError, QWebException
from odoo.models import check_method_name
from odoo.tools import pycompat
from odoo.tools.translate import translate
from odoo.tools.translate import _
@@ -63,7 +64,7 @@ def check(f):
uid = 1
if args and isinstance(args[0], (long, int)):
if args and isinstance(args[0], pycompat.integer_types):
uid = args[0]
lang = ctx and ctx.get('lang')
@@ -83,7 +84,7 @@ def check(f):
if args and len(args) > 1:
# TODO self doesn't exist, but was already wrong before (it was not a registry but just the object_service.
obj = self.get(args[1])
if len(args) > 3 and isinstance(args[3], (long, int, list)):
if len(args) > 3 and isinstance(args[3], (pycompat.integer_types, list)):
ids = args[3]
else:
ids = []
+1 -1
View File
@@ -863,7 +863,7 @@ def _reexec(updated_modules=None):
def load_test_file_yml(registry, test_file):
with registry.cursor() as cr:
odoo.tools.convert_yaml_import(cr, 'base', file(test_file), 'test', {}, 'init')
odoo.tools.convert_yaml_import(cr, 'base', open(test_file, 'rb'), 'test', {}, 'init')
if config['test_commit']:
_logger.info('test %s has been commited', test_file)
cr.commit()
+4 -4
View File
@@ -21,6 +21,8 @@ import psycopg2.extensions
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_REPEATABLE_READ
from psycopg2.pool import PoolError
from .tools import pycompat
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
_logger = logging.getLogger(__name__)
@@ -255,10 +257,8 @@ class Cursor(object):
sum = 0
if sqllogs[type]:
sqllogitems = sqllogs[type].items()
sqllogitems.sort(key=lambda k: k[1][1])
_logger.debug("SQL LOG %s:", type)
sqllogitems.sort(lambda x, y: cmp(x[1][0], y[1][0]))
for r in sqllogitems:
for r in sorted(sqllogitems, key=lambda k: k[1]):
delay = timedelta(microseconds=r[1][1])
_logger.debug("table: %s: %s/%s", r[0], delay, r[1][0])
sum += r[1][1]
@@ -469,7 +469,7 @@ class LazyCursor(object):
if cr is None:
from odoo import registry
cr = self._cursor = registry(self.dbname).cursor()
for _ in xrange(self._depth):
for _ in pycompat.range(self._depth):
cr.__enter__()
return getattr(cr, name)
+1 -1
View File
@@ -367,7 +367,7 @@ class HttpCase(TransactionCase):
_logger.info('phantomjs unlink localstorage %s', i)
os.unlink(i)
try:
phantom = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=None)
phantom = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=None, close_fds=True)
except OSError:
raise unittest.SkipTest("PhantomJS not found")
result = False
+1 -1
View File
@@ -549,7 +549,7 @@ class configmanager(object):
if not rc_exists and not os.path.exists(os.path.dirname(self.rcfile)):
os.makedirs(os.path.dirname(self.rcfile))
try:
p.write(file(self.rcfile, 'w'))
p.write(open(self.rcfile, 'w'))
if not rc_exists:
os.chmod(self.rcfile, 0o600)
except IOError:
+3 -3
View File
@@ -890,10 +890,10 @@ def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=Fa
if idref is None:
idref={}
if isinstance(xmlfile, file):
xml_filename = xmlfile.name
else:
if isinstance(xmlfile, basestring):
xml_filename = xmlfile
else:
xml_filename = xmlfile.name
obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate, xml_filename=xml_filename)
obj.parse(doc.getroot(), mode=mode)
return True
+9 -7
View File
@@ -4,6 +4,9 @@
from __future__ import print_function
import math
from odoo.tools import pycompat
def _float_check_precision(precision_digits=None, precision_rounding=None):
assert (precision_digits is not None or precision_rounding is not None) and \
not (precision_digits and precision_rounding),\
@@ -54,7 +57,7 @@ def float_round(value, precision_digits=None, precision_rounding=None, rounding_
epsilon_magnitude = math.log(abs(normalized_value), 2)
epsilon = 2**(epsilon_magnitude-53)
if rounding_method == 'HALF-UP':
normalized_value += cmp(normalized_value,0) * epsilon
normalized_value += math.copysign(epsilon, normalized_value)
rounded_value = round(normalized_value) # round to integer
# TIE-BREAKING: UP (for ceiling operations)
@@ -66,7 +69,7 @@ def float_round(value, precision_digits=None, precision_rounding=None, rounding_
# restored.
elif rounding_method == 'UP':
sign = cmp(normalized_value, 0)
sign = math.copysign(1.0, normalized_value)
normalized_value -= sign*epsilon
rounded_value = math.ceil(abs(normalized_value))*sign # ceil to integer
@@ -214,14 +217,13 @@ if __name__ == "__main__":
fractions = [.0, .015, .01499, .675, .67499, .4555, .4555, .45555]
expecteds = ['.00', '.02', '.01', '.68', '.67', '.46', '.456', '.4556']
precisions = [2, 2, 2, 2, 2, 2, 3, 4]
for magnitude in range(7):
for i in xrange(len(fractions)):
frac, exp, prec = fractions[i], expecteds[i], precisions[i]
for magnitude in pycompat.range(7):
for frac, exp, prec in zip(fractions, expecteds, precisions):
for sign in [-1,1]:
for x in xrange(0,10000,97):
for x in pycompat.range(0, 10000, 97):
n = x * 10**magnitude
f = sign * (n + frac)
f_exp = ('-' if f != 0 and sign == -1 else '') + str(n) + exp
f_exp = ('-' if f != 0 and sign == -1 else '') + str(n) + exp
try_round(f, f_exp, precision_digits=prec)
stop = time.time()
+1 -1
View File
@@ -634,7 +634,7 @@ class graph(object):
self.init_order(self.start, self.result[self.start]['x'])
for level in self.levels:
self.levels[level].sort(lambda x, y: cmp(self.result[x]['y'], self.result[y]['y']))
self.levels[level].sort(key=lambda x: self.result[x]['y'])
self.order_heuristic()
self.process_order()
+2 -2
View File
@@ -280,6 +280,6 @@ if __name__=="__main__":
assert len(sys.argv)==3, 'Usage to Test: image.py SRC.png DEST.png'
img = file(sys.argv[1],'rb').read().encode('base64')
img = open(sys.argv[1],'rb').read().encode('base64')
new = image_resize_image(img, (128,100))
file(sys.argv[2], 'wb').write(new.decode('base64'))
open(sys.argv[2], 'wb').write(new.decode('base64'))
+1 -1
View File
@@ -125,7 +125,7 @@ class ReceiverEmail2Event(object):
os.mkdir(newdir)
counter += 1
fd = file(os.path.join(newdir, filename), 'w')
fd = open(os.path.join(newdir, filename), 'w')
fd.write(part.get_payload(decode=1))
fd.close()
else:
+42 -4
View File
@@ -27,6 +27,7 @@ from cStringIO import StringIO
from collections import defaultdict, Iterable, Mapping, MutableSet, OrderedDict
from itertools import islice, izip, groupby, repeat
from lxml import etree
from which import which
from threading import local
import traceback
@@ -41,6 +42,7 @@ except ImportError:
from config import config
from cache import *
from .parse_version import parse_version
from . import pycompat
import odoo
# get_encodings, ustr and exception_to_unicode were originally from tools.misc.
@@ -279,7 +281,7 @@ def flatten(list):
return r
def reverse_enumerate(l):
"""Like enumerate but in the other sens
"""Like enumerate but in the other direction
Usage::
>>> a = ['a', 'b', 'c']
@@ -295,7 +297,7 @@ def reverse_enumerate(l):
File "<stdin>", line 1, in <module>
StopIteration
"""
return izip(xrange(len(l)-1, -1, -1), reversed(l))
return izip(pycompat.range(len(l)-1, -1, -1), reversed(l))
def partition(pred, elems):
""" Return a pair equivalent to:
@@ -654,8 +656,11 @@ def posix_to_ldml(fmt, locale):
def split_every(n, iterable, piece_maker=tuple):
"""Splits an iterable into length-n pieces. The last piece will be shorter
if ``n`` does not evenly divide the iterable length.
@param ``piece_maker``: function to build the pieces
from the slices (tuple,list,...)
:param int n: maximum size of each generated chunk
:param Iterable iterable: iterable to chunk into pieces
:param piece_maker: callable taking an iterable and collecting each
chunk from its slice, *must consume the entire slice*.
"""
iterator = iter(iterable)
piece = piece_maker(islice(iterator, n))
@@ -990,6 +995,39 @@ class LastOrderedSet(OrderedSet):
OrderedSet.discard(self, elem)
OrderedSet.add(self, elem)
def unique(it):
""" "Uniquifier" for the provided iterable: will output each element of
the iterable once.
The iterable's elements must be hashahble.
:param Iterable it:
:rtype: Iterator
"""
seen = set()
for e in it:
if e not in seen:
seen.add(e)
yield e
class Reverse(object):
""" Wraps a value and reverses its ordering, useful in key functions when
mixing ascending and descending sort on non-numeric data as the
``reverse`` parameter can not do piecemeal reordering.
"""
__slots__ = ['val']
def __init__(self, val):
self.val = val
def __eq__(self, other): return self.val == other.val
def __ne__(self, other): return self.val != other.val
def __ge__(self, other): return self.val <= other.val
def __gt__(self, other): return self.val < other.val
def __le__(self, other): return self.val >= other.val
def __lt__(self, other): return self.val > other.val
@contextmanager
def ignore(*exc):
try:
+31
View File
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# to remove if we decide to add a dependency on six or future
# very strongly inspired by https://github.com/pallets/werkzeug/blob/master/werkzeug/_compat.py
import sys
PY2 = sys.version_info[0]
if PY2:
# pylint: disable=long-builtin,xrange-builtin
integer_types = (int, long)
range = xrange
# noinspection PyUnresolvedReferences
from itertools import imap, izip, ifilter
def implements_iterator(cls):
cls.next = cls.__next__
del cls.__next__
return cls
else:
integer_types = (int,)
range = range
imap = map
izip = zip
ifilter = filter
def implements_iterator(cls):
return cls
+5 -4
View File
@@ -16,11 +16,14 @@ condition/math builtins.
# - safe_eval in tryton http://hg.tryton.org/hgwebdir.cgi/trytond/rev/bbb5f73319ad
from opcode import HAVE_ARGUMENT, opmap
import functools
from psycopg2 import OperationalError
from types import CodeType
import logging
import werkzeug
from odoo.tools import pycompat
from .misc import ustr
import odoo
@@ -226,7 +229,6 @@ _BUILTINS = {
'bool': bool,
'int': int,
'float': float,
'long': long,
'enumerate': enumerate,
'dict': dict,
'list': list,
@@ -236,7 +238,7 @@ _BUILTINS = {
'min': min,
'max': max,
'sum': sum,
'reduce': reduce,
'reduce': functools.reduce,
'filter': filter,
'round': round,
'len': len,
@@ -246,11 +248,10 @@ _BUILTINS = {
'any': any,
'ord': ord,
'chr': chr,
'cmp': cmp,
'divmod': divmod,
'isinstance': isinstance,
'range': range,
'xrange': xrange,
'xrange': pycompat.range,
'zip': zip,
'Exception': Exception,
}
+4 -2
View File
@@ -11,6 +11,8 @@ import odoo
import odoo.report
import odoo.tools as tools
import logging
from odoo.tools import pycompat
from odoo.tools.safe_eval import safe_eval
from subprocess import Popen, PIPE
import os
@@ -45,7 +47,7 @@ def try_report(cr, uid, rname, ids, data=None, context=None, our_module=None, re
raise ValueError("Report %s produced an empty result!" % rname)
if tools.config['test_report_directory']:
file(os.path.join(tools.config['test_report_directory'], rname+ '.'+res_format), 'wb+').write(res_data)
open(os.path.join(tools.config['test_report_directory'], rname+ '.'+res_format), 'wb+').write(res_data)
_logger.debug("Have a %s report for %s, will examine it", res_format, rname)
if res_format == 'pdf':
@@ -128,7 +130,7 @@ def try_report_action(cr, uid, action_id, active_model=None, active_ids=None,
action = env.ref(action_id)
act_model, act_id = action._name, action.id
else:
assert isinstance(action_id, (long, int))
assert isinstance(action_id, pycompat.integer_types)
act_model = 'ir.action.act_window' # assume that
act_id = action_id
act_xmlid = '<%s>' % act_id
+3 -3
View File
@@ -20,7 +20,7 @@ from babel.messages import extract
from lxml import etree
import odoo
from odoo.tools import config
from odoo.tools import config, pycompat
from odoo.tools.misc import file_open, get_iso_codes, SKIPPED_ELEMENT_TYPES
from odoo.tools.osutil import walksymlinks
from odoo import sql_db, SUPERUSER_ID
@@ -679,7 +679,7 @@ def trans_export(lang, modules, buffer, format, cr):
tmpmoddir = join(tmpdir, mod, 'i18n')
os.makedirs(tmpmoddir)
pofilename = (lang if lang else mod) + ".po" + ('t' if not lang else '')
buf = file(join(tmpmoddir, pofilename), 'w')
buf = open(join(tmpmoddir, pofilename), 'w')
_process('po', [mod], modrows, buf, lang)
buf.close()
@@ -1106,7 +1106,7 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
if not res_id:
return
if isinstance(res_id, (int, long)) or \
if isinstance(res_id, pycompat.integer_types) or \
(isinstance(res_id, basestring) and res_id.isdigit()):
dic['res_id'] = int(res_id)
if module_name:
+1 -1
View File
@@ -8,7 +8,7 @@ from setuptools import find_packages, setup
from os.path import join, dirname
execfile(join(dirname(__file__), 'odoo', 'release.py')) # Load release variables
exec(open(join(dirname(__file__), 'odoo', 'release.py'), 'rb').read()) # Load release variables
lib_name = 'odoo'
+1 -1
View File
@@ -22,7 +22,7 @@ from tempfile import NamedTemporaryFile
#----------------------------------------------------------
# Utils
#----------------------------------------------------------
execfile(join(dirname(__file__), '..', 'odoo', 'release.py'))
exec(open(join(dirname(__file__), '..', 'odoo', 'release.py'), 'rb').read())
version = version.split('-')[0]
docker_version = version.replace('+', '')
timestamp = time.strftime("%Y%m%d", time.gmtime())
+1 -1
View File
@@ -11,7 +11,7 @@ import sys
from os.path import dirname, join, split
execfile(join(dirname(__file__), '..', 'server', 'odoo', 'release.py'))
exec(open(join(dirname(__file__), '..', 'server', 'odoo', 'release.py'), 'rb').read())
class OdooService(win32serviceutil.ServiceFramework):
+1 -1
View File
@@ -6,7 +6,7 @@ import py2exe
from distutils.core import setup
execfile(os.path.join(os.path.dirname(__file__), '..', '..', 'odoo', 'release.py'))
exec(open(os.path.join(os.path.dirname(__file__), '..', '..', 'odoo', 'release.py'), 'rb').read())
def generate_files():