summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Morel <xmo@odoo.com>2017-05-02 12:07:30 +0200
committerOlivier Dony <odo@openerp.com>2017-08-20 23:25:54 +0200
commit7dd062f83552fa8ff96224c29d60bafc001d73d3 (patch)
treed507e27e9d5daa91981e04b5aa81a802a9854a8d
parent3824b5dcc13678d3985ad45ac21dcf9ac9202c20 (diff)
[FIX] P3: text model types
* remove references to basestring & unicode (use relevant pycompat helpers) * remove some str calls (either entirely or replaced by relevant helper, either text or native) * use better API to avoid unnecessary conversions * remove some XML declarations in views
-rw-r--r--addons/anonymization/models/anonymization.py3
-rw-r--r--addons/base_import/models/base_import.py6
-rw-r--r--addons/board/models/board.py3
-rw-r--r--addons/bus/controllers/main.py6
-rw-r--r--addons/calendar/models/calendar.py17
-rw-r--r--addons/calendar/models/ir_attachment.py6
-rw-r--r--addons/calendar/models/mail_message.py4
-rw-r--r--addons/crm/wizard/base_partner_merge.py2
-rw-r--r--addons/document/models/ir_attachment.py13
-rw-r--r--addons/hw_escpos/escpos/escpos.py4
-rw-r--r--addons/l10n_be_intrastat/wizard/xml_decl.py23
-rw-r--r--addons/l10n_lu/scripts/tax2csv.py9
-rw-r--r--addons/mail/controllers/main.py4
-rw-r--r--addons/mail/models/ir_model.py3
-rw-r--r--addons/mail/models/mail_thread.py27
-rw-r--r--addons/payment_authorize/models/authorize_request.py2
-rw-r--r--addons/payment_authorize/tests/test_authorize.py2
-rw-r--r--addons/product/models/product.py2
-rw-r--r--addons/product/models/product_template.py2
-rw-r--r--addons/stock/models/product.py4
-rw-r--r--addons/survey/controllers/main.py2
-rw-r--r--addons/survey/wizard/survey_email_compose_message.py3
-rw-r--r--addons/utm/models/utm.py3
-rw-r--r--addons/web/controllers/main.py4
-rw-r--r--addons/web_editor/controllers/main.py4
-rw-r--r--addons/web_editor/models/ir_qweb.py15
-rw-r--r--addons/web_editor/models/ir_ui_view.py2
-rw-r--r--addons/website/controllers/main.py2
-rw-r--r--addons/website/models/ir_ui_view.py2
-rw-r--r--addons/website/models/website.py9
-rw-r--r--doc/_extensions/github_link.py4
-rw-r--r--doc/_extensions/odoo_ext/translator.py10
-rw-r--r--odoo/addons/base/ir/ir_fields.py10
-rw-r--r--odoo/addons/base/ir/ir_model.py9
-rw-r--r--odoo/addons/base/ir/ir_qweb/__init__.py2
-rw-r--r--odoo/addons/base/ir/ir_qweb/fields.py27
-rw-r--r--odoo/addons/base/ir/ir_qweb/ir_qweb.py2
-rw-r--r--odoo/addons/base/ir/ir_qweb/qweb.py55
-rw-r--r--odoo/addons/base/ir/ir_translation.py2
-rw-r--r--odoo/addons/base/ir/ir_ui_view.py10
-rw-r--r--odoo/addons/base/ir/ir_values.py6
-rw-r--r--odoo/addons/base/module/module.py2
-rw-r--r--odoo/addons/base/res/res_users.py6
-rw-r--r--odoo/addons/base/tests/test_func.py4
-rw-r--r--odoo/addons/base/tests/test_qweb.py2
-rw-r--r--odoo/addons/base/tests/test_res_config.py4
-rw-r--r--odoo/addons/test_impex/models.py7
-rw-r--r--odoo/addons/test_impex/tests/test_export.py4
-rw-r--r--odoo/addons/test_new_api/tests/test_new_fields.py6
-rw-r--r--odoo/fields.py45
-rw-r--r--odoo/http.py18
-rw-r--r--odoo/loglevels.py19
-rw-r--r--odoo/models.py46
-rw-r--r--odoo/osv/expression.py29
-rw-r--r--odoo/osv/orm.py2
-rw-r--r--odoo/service/db.py3
-rw-r--r--odoo/tools/__init__.py2
-rw-r--r--odoo/tools/appdirs.py9
-rw-r--r--odoo/tools/cache.py4
-rw-r--r--odoo/tools/config.py20
-rw-r--r--odoo/tools/convert.py5
-rw-r--r--odoo/tools/misc.py8
-rw-r--r--odoo/tools/pycompat.py43
-rw-r--r--odoo/tools/safe_eval.py4
-rw-r--r--odoo/tools/test_reports.py29
-rw-r--r--odoo/tools/translate.py31
-rw-r--r--odoo/tools/yaml_import.py19
67 files changed, 359 insertions, 337 deletions
diff --git a/addons/anonymization/models/anonymization.py b/addons/anonymization/models/anonymization.py
index 2d4fe1b17d2..a0c90382938 100644
--- a/addons/anonymization/models/anonymization.py
+++ b/addons/anonymization/models/anonymization.py
@@ -6,6 +6,7 @@ from operator import itemgetter
from odoo import api, fields, models, _
from odoo.exceptions import UserError
+from odoo.tools import pycompat
FIELD_STATES = [('clear', 'Clear'), ('anonymized', 'Anonymized'), ('not_existing', 'Not Existing'), ('new', 'New')]
ANONYMIZATION_HISTORY_STATE = [('started', 'Started'), ('done', 'Done'), ('in_exception', 'Exception occured')]
@@ -13,7 +14,7 @@ ANONYMIZATION_DIRECTION = [('clear -> anonymized', 'clear -> anonymized'), ('ano
def group(lst, cols):
- if isinstance(cols, basestring):
+ if isinstance(cols, pycompat.string_types):
cols = [cols]
return dict((k, [v for v in itr]) for k, itr in groupby(sorted(lst, key=itemgetter(*cols)), itemgetter(*cols)))
diff --git a/addons/base_import/models/base_import.py b/addons/base_import/models/base_import.py
index e707beecc17..f2bab8b52b3 100644
--- a/addons/base_import/models/base_import.py
+++ b/addons/base_import/models/base_import.py
@@ -208,9 +208,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
@@ -653,7 +653,7 @@ class Import(models.TransientModel):
except ValueError as error:
return [{
'type': 'error',
- 'message': unicode(error),
+ 'message': pycompat.text_type(error),
'record': False,
}]
diff --git a/addons/board/models/board.py b/addons/board/models/board.py
index 3acd277826a..81e849af263 100644
--- a/addons/board/models/board.py
+++ b/addons/board/models/board.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
+from odoo.tools import pycompat
class Board(models.AbstractModel):
@@ -45,7 +46,7 @@ class Board(models.AbstractModel):
return node
def encode(s):
- if isinstance(s, unicode):
+ if isinstance(s, pycompat.text_type):
return s.encode('utf8')
return s
diff --git a/addons/bus/controllers/main.py b/addons/bus/controllers/main.py
index 40118764751..4e82116e7c7 100644
--- a/addons/bus/controllers/main.py
+++ b/addons/bus/controllers/main.py
@@ -4,6 +4,8 @@ from odoo import exceptions
from odoo.http import Controller, request, route
from odoo.addons.bus.models.bus import dispatch
+from odoo.tools import pycompat
+
class BusController(Controller):
""" Examples:
@@ -14,7 +16,7 @@ class BusController(Controller):
@route('/longpolling/send', type="json", auth="public")
def send(self, channel, message):
- if not isinstance(channel, basestring):
+ if not isinstance(channel, pycompat.string_types):
raise Exception("bus.Bus only string channels are allowed.")
return request.env['bus.bus'].sendone(channel, message)
@@ -33,7 +35,7 @@ class BusController(Controller):
options = {}
if not dispatch:
raise Exception("bus.Bus unavailable")
- if [c for c in channels if not isinstance(c, basestring)]:
+ if [c for c in channels if not isinstance(c, pycompat.string_types)]:
raise Exception("bus.Bus only string channels are allowed.")
if request.registry.in_test_mode():
raise exceptions.UserError("bus.Bus not available in test mode")
diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py
index b0c16c15b5a..a6656af1a5b 100644
--- a/addons/calendar/models/calendar.py
+++ b/addons/calendar/models/calendar.py
@@ -34,7 +34,7 @@ def calendar_id2real_id(calendar_id=None, with_date=False):
: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]
@@ -48,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)):
@@ -1060,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()
@@ -1209,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
@@ -1221,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"))
@@ -1316,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)
@@ -1352,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)
@@ -1390,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})
@@ -1474,7 +1475,7 @@ class Meeting(models.Model):
for calendar_id, real_id in select:
res = real_data[real_id].copy()
ls = calendar_id2real_id(calendar_id, with_date=res and res.get('duration', 0) > 0 and res.get('duration') or 1)
- if not isinstance(ls, (basestring, pycompat.integer_types)) and len(ls) >= 2:
+ if not isinstance(ls, (pycompat.string_types, pycompat.integer_types)) and len(ls) >= 2:
res['start'] = ls[1]
res['stop'] = ls[2]
diff --git a/addons/calendar/models/ir_attachment.py b/addons/calendar/models/ir_attachment.py
index c98242db7e7..ae734ec59f4 100644
--- a/addons/calendar/models/ir_attachment.py
+++ b/addons/calendar/models/ir_attachment.py
@@ -5,6 +5,8 @@ from odoo import api, models
from odoo.addons.calendar.models.calendar import get_real_ids
+from odoo.tools import pycompat
+
class Attachment(models.Model):
@@ -16,13 +18,13 @@ class Attachment(models.Model):
args = list(args)
if any([leaf for leaf in args if leaf[0] == "res_model" and leaf[2] == 'calendar.event']):
for index in range(len(args)):
- if args[index][0] == "res_id" and isinstance(args[index][2], basestring):
+ if args[index][0] == "res_id" and isinstance(args[index][2], pycompat.string_types):
args[index] = (args[index][0], args[index][1], get_real_ids(args[index][2]))
return super(Attachment, self).search(args, offset=offset, limit=limit, order=order, count=count)
@api.multi
def write(self, vals):
""" When posting an attachment (new or not), convert the virtual ids in real ids. """
- if isinstance(vals.get('res_id'), basestring):
+ if isinstance(vals.get('res_id'), pycompat.string_types):
vals['res_id'] = get_real_ids(vals.get('res_id'))
return super(Attachment, self).write(vals)
diff --git a/addons/calendar/models/mail_message.py b/addons/calendar/models/mail_message.py
index 0dc4d24e364..26be5080ab5 100644
--- a/addons/calendar/models/mail_message.py
+++ b/addons/calendar/models/mail_message.py
@@ -5,6 +5,8 @@ from odoo import api, models
from odoo.addons.calendar.models.calendar import get_real_ids
+from odoo.tools import pycompat
+
class Message(models.Model):
@@ -16,7 +18,7 @@ class Message(models.Model):
args = list(args)
for index in range(len(args)):
if args[index][0] == "res_id":
- if isinstance(args[index][2], basestring):
+ if isinstance(args[index][2], pycompat.string_types):
args[index] = (args[index][0], args[index][1], get_real_ids(args[index][2]))
elif isinstance(args[index][2], list):
args[index] = (args[index][0], args[index][1], [get_real_ids(x) for x in args[index][2]])
diff --git a/addons/crm/wizard/base_partner_merge.py b/addons/crm/wizard/base_partner_merge.py
index 7751e0106ed..24a573ad871 100644
--- a/addons/crm/wizard/base_partner_merge.py
+++ b/addons/crm/wizard/base_partner_merge.py
@@ -39,7 +39,7 @@ def html_entity_decode(string):
def sanitize_email(email):
- assert isinstance(email, basestring) and email
+ assert isinstance(email, pycompat.string_types) and email
result = re.subn(r';|/|:', ',', html_entity_decode(email or ''))[0].split(',')
diff --git a/addons/document/models/ir_attachment.py b/addons/document/models/ir_attachment.py
index eff40db5df4..7e1afe36e4e 100644
--- a/addons/document/models/ir_attachment.py
+++ b/addons/document/models/ir_attachment.py
@@ -11,19 +11,6 @@ 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:
diff --git a/addons/hw_escpos/escpos/escpos.py b/addons/hw_escpos/escpos/escpos.py
index e50537e0eeb..f076778ca9c 100644
--- a/addons/hw_escpos/escpos/escpos.py
+++ b/addons/hw_escpos/escpos/escpos.py
@@ -12,6 +12,8 @@ from hashlib import md5
from PIL import Image
from xml.etree import ElementTree as ET
+from odoo.tools import pycompat
+
try:
import jcconv
except ImportError:
@@ -27,7 +29,7 @@ from .exceptions import *
def utfstr(stuff):
""" converts stuff to string and does without failing if stuff is a utf8 string """
- if isinstance(stuff,basestring):
+ if isinstance(stuff,pycompat.string_types):
return stuff
else:
return str(stuff)
diff --git a/addons/l10n_be_intrastat/wizard/xml_decl.py b/addons/l10n_be_intrastat/wizard/xml_decl.py
index f7d01db30d1..b5486d963c9 100644
--- a/addons/l10n_be_intrastat/wizard/xml_decl.py
+++ b/addons/l10n_be_intrastat/wizard/xml_decl.py
@@ -6,6 +6,7 @@ from xml.etree import ElementTree as ET
from collections import namedtuple
from odoo import api, exceptions, fields, models, _
+from odoo.tools.pycompat import text_type
INTRASTAT_XMLNS = 'http://www.onegate.eu/2010-01-01'
@@ -264,18 +265,18 @@ class XmlDeclaration(models.TransientModel):
continue
numlgn += 1
item = ET.SubElement(datas, 'Item')
- self._set_Dim(item, 'EXSEQCODE', unicode(numlgn))
- self._set_Dim(item, 'EXTRF', unicode(linekey.EXTRF))
- self._set_Dim(item, 'EXCNT', unicode(linekey.EXCNT))
- self._set_Dim(item, 'EXTTA', unicode(linekey.EXTTA))
- self._set_Dim(item, 'EXREG', unicode(linekey.EXREG))
- self._set_Dim(item, 'EXTGO', unicode(linekey.EXGO))
+ self._set_Dim(item, 'EXSEQCODE', text_type(numlgn))
+ self._set_Dim(item, 'EXTRF', text_type(linekey.EXTRF))
+ self._set_Dim(item, 'EXCNT', text_type(linekey.EXCNT))
+ self._set_Dim(item, 'EXTTA', text_type(linekey.EXTTA))
+ self._set_Dim(item, 'EXREG', text_type(linekey.EXREG))
+ self._set_Dim(item, 'EXTGO', text_type(linekey.EXGO))
if extendedmode:
- self._set_Dim(item, 'EXTPC', unicode(linekey.EXTPC))
- self._set_Dim(item, 'EXDELTRM', unicode(linekey.EXDELTRM))
- self._set_Dim(item, 'EXTXVAL', unicode(round(amounts[0], 0)).replace(".", ","))
- self._set_Dim(item, 'EXWEIGHT', unicode(round(amounts[1], 0)).replace(".", ","))
- self._set_Dim(item, 'EXUNITS', unicode(round(amounts[2], 0)).replace(".", ","))
+ self._set_Dim(item, 'EXTPC', text_type(linekey.EXTPC))
+ self._set_Dim(item, 'EXDELTRM', text_type(linekey.EXDELTRM))
+ self._set_Dim(item, 'EXTXVAL', text_type(round(amounts[0], 0)).replace(".", ","))
+ self._set_Dim(item, 'EXWEIGHT', text_type(round(amounts[1], 0)).replace(".", ","))
+ self._set_Dim(item, 'EXUNITS', text_type(round(amounts[2], 0)).replace(".", ","))
if numlgn == 0:
#no datas
diff --git a/addons/l10n_lu/scripts/tax2csv.py b/addons/l10n_lu/scripts/tax2csv.py
index 9993272573f..013df32e3d3 100644
--- a/addons/l10n_lu/scripts/tax2csv.py
+++ b/addons/l10n_lu/scripts/tax2csv.py
@@ -5,12 +5,11 @@ import xlrd
from odoo.tools import pycompat
def _e(s):
- if type(s) is unicode:
- return s.encode('utf8')
- elif s is None:
+ if s is None:
return ''
- else:
- return str(s)
+ if pycompat.PY2 and type(s) == pycompat.text_type:
+ return s.encode('utf8')
+ return str(s)
def _is_true(s):
diff --git a/addons/mail/controllers/main.py b/addons/mail/controllers/main.py
index 74f326d19fb..52ea655c078 100644
--- a/addons/mail/controllers/main.py
+++ b/addons/mail/controllers/main.py
@@ -13,7 +13,7 @@ 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__)
@@ -182,7 +182,7 @@ class MailController(http.Controller):
else:
# either a wrong message_id, either someone trying ids -> just go to messaging
return self._redirect_to_messaging()
- elif res_id and isinstance(res_id, basestring):
+ elif res_id and isinstance(res_id, pycompat.string_types):
res_id = int(res_id)
return self._redirect_to_record(model, res_id, access_token)
diff --git a/addons/mail/models/ir_model.py b/addons/mail/models/ir_model.py
index e2231e38f5d..01862352e96 100644
--- a/addons/mail/models/ir_model.py
+++ b/addons/mail/models/ir_model.py
@@ -3,6 +3,7 @@
from odoo import _, api, fields, models
from odoo.exceptions import UserError
+from odoo.tools import pycompat
class IrModel(models.Model):
@@ -41,7 +42,7 @@ class IrModel(models.Model):
model_class = super(IrModel, self)._instanciate(model_data)
if model_data.get('is_mail_thread') and model_class._name != 'mail.thread':
parents = model_class._inherit or []
- parents = [parents] if isinstance(parents, basestring) else parents
+ parents = [parents] if isinstance(parents, pycompat.string_types) else parents
model_class._inherit = parents + ['mail.thread']
return model_class
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 35ae6d4ff9e..bc7b1d1072d 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -423,7 +423,7 @@ class MailThread(models.AbstractModel):
for field_name, (template, post_kwargs) in pycompat.items(templates):
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)
@@ -1268,12 +1268,15 @@ class MailThread(models.AbstractModel):
# convert it to utf-8 for transport between the mailgate script and here.
if isinstance(message, xmlrpclib.Binary):
message = bytes(message.data)
- # FIXME: message_from_string parses from a *native string*
- # Warning: message_from_string doesn't always work correctly on unicode,
- # we must use utf-8 strings here :-(
- if isinstance(message, unicode):
+ # 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)
@@ -1494,9 +1497,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)
@@ -1725,11 +1728,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,
@@ -1922,7 +1925,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
diff --git a/addons/payment_authorize/models/authorize_request.py b/addons/payment_authorize/models/authorize_request.py
index 0a00b1a4c70..7388a1a4329 100644
--- a/addons/payment_authorize/models/authorize_request.py
+++ b/addons/payment_authorize/models/authorize_request.py
@@ -58,7 +58,7 @@ class AuthorizeAPI():
:param etree._Element data: etree data to process
"""
- data = etree.tostring(data, xml_declaration=True, encoding='utf-8')
+ data = etree.tostring(data, encoding='utf-8')
r = requests.post(self.url, data=data, headers={'Content-Type': 'text/xml'})
r.raise_for_status()
response = strip_ns(r.content, XMLNS)
diff --git a/addons/payment_authorize/tests/test_authorize.py b/addons/payment_authorize/tests/test_authorize.py
index b02d3d81209..668e17d0562 100644
--- a/addons/payment_authorize/tests/test_authorize.py
+++ b/addons/payment_authorize/tests/test_authorize.py
@@ -95,7 +95,7 @@ class AuthorizeForm(AuthorizeCommon):
if values[1] in ['submit', 'x_fp_hash', 'return_url', 'x_state', 'x_ship_to_state']:
continue
self.assertEqual(
- unicode(values[2], "utf-8"),
+ values[2],
form_values[values[1]],
'Authorize: wrong value for input %s: received %s instead of %s' % (values[1], values[2], form_values[values[1]])
)
diff --git a/addons/product/models/product.py b/addons/product/models/product.py
index 638e898aed8..c550e063709 100644
--- a/addons/product/models/product.py
+++ b/addons/product/models/product.py
@@ -158,7 +158,7 @@ class ProductProduct(models.Model):
quantity = self._context.get('quantity', 1.0)
# Support context pricelists specified as display_name or ID for compatibility
- if isinstance(pricelist_id_or_name, basestring):
+ if isinstance(pricelist_id_or_name, pycompat.string_types):
pricelist_name_search = self.env['product.pricelist'].name_search(pricelist_id_or_name, operator='=', limit=1)
if pricelist_name_search:
pricelist = self.env['product.pricelist'].browse([pricelist_name_search[0][0]])
diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py
index ccfbcf28b96..c9a2411f173 100644
--- a/addons/product/models/product_template.py
+++ b/addons/product/models/product_template.py
@@ -171,7 +171,7 @@ class ProductTemplate(models.Model):
quantity = self._context.get('quantity', 1.0)
# Support context pricelists specified as display_name or ID for compatibility
- if isinstance(pricelist_id_or_name, basestring):
+ if isinstance(pricelist_id_or_name, pycompat.string_types):
pricelist_data = self.env['product.pricelist'].name_search(pricelist_id_or_name, operator='=', limit=1)
if pricelist_data:
pricelist = self.env['product.pricelist'].browse(pricelist_data[0][0])
diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py
index 43cbe1e2e4d..693f5c0119e 100644
--- a/addons/stock/models/product.py
+++ b/addons/stock/models/product.py
@@ -161,7 +161,7 @@ class Product(models.Model):
if self.env.context.get('location', False):
if isinstance(self.env.context['location'], pycompat.integer_types):
location_ids = [self.env.context['location']]
- elif isinstance(self.env.context['location'], basestring):
+ elif isinstance(self.env.context['location'], pycompat.string_types):
domain = [('complete_name', 'ilike', self.env.context['location'])]
if self.env.context.get('force_company', False):
domain += [('company_id', '=', self.env.context['force_company'])]
@@ -172,7 +172,7 @@ class Product(models.Model):
if self.env.context.get('warehouse', False):
if isinstance(self.env.context['warehouse'], pycompat.integer_types):
wids = [self.env.context['warehouse']]
- elif isinstance(self.env.context['warehouse'], basestring):
+ elif isinstance(self.env.context['warehouse'], pycompat.string_types):
domain = [('name', 'ilike', self.env.context['warehouse'])]
if self.env.context.get('force_company', False):
domain += [('company_id', '=', self.env.context['force_company'])]
diff --git a/addons/survey/controllers/main.py b/addons/survey/controllers/main.py
index 553fc5ad4b2..150503f07da 100644
--- a/addons/survey/controllers/main.py
+++ b/addons/survey/controllers/main.py
@@ -174,7 +174,7 @@ class WebsiteSurvey(http.Controller):
answer_tag = "%s_%s" % (answer_tag, 'comment')
answer_value = answer.value_text
elif answer.answer_type == 'number':
- answer_value = answer.value_number.__str__()
+ answer_value = str(answer.value_number)
elif answer.answer_type == 'date':
answer_value = answer.value_date
elif answer.answer_type == 'suggestion' and not answer.value_suggested_row:
diff --git a/addons/survey/wizard/survey_email_compose_message.py b/addons/survey/wizard/survey_email_compose_message.py
index fb36d839c57..f23d836bc30 100644
--- a/addons/survey/wizard/survey_email_compose_message.py
+++ b/addons/survey/wizard/survey_email_compose_message.py
@@ -8,6 +8,7 @@ from werkzeug import urls
from odoo import api, fields, models, _
from odoo.exceptions import UserError
+from odoo.tools import pycompat
emails_split = re.compile(r"[;,\n\r]+")
email_validator = re.compile(r"[^@]+@[^@]+\.[^@]+")
@@ -119,7 +120,7 @@ class SurveyMailComposeMessage(models.TransientModel):
if wizard.public != 'email_private':
return None
else:
- token = uuid.uuid4().__str__()
+ token = pycompat.text_type(uuid.uuid4())
# create response with token
survey_user_input = SurveyUserInput.create({
'survey_id': wizard.survey_id.id,
diff --git a/addons/utm/models/utm.py b/addons/utm/models/utm.py
index 5cb04d51425..b73329c1011 100644
--- a/addons/utm/models/utm.py
+++ b/addons/utm/models/utm.py
@@ -3,6 +3,7 @@
from odoo import api, fields, models, SUPERUSER_ID
from odoo.http import request
+from odoo.tools import pycompat
class UtmMedium(models.Model):
@@ -72,7 +73,7 @@ class UtmMixin(models.AbstractModel):
# ir_http dispatch saves the url params in a cookie
value = request.httprequest.cookies.get(cookie_name)
# if we receive a string for a many2one, we search/create the id
- if field.type == 'many2one' and isinstance(value, basestring) and value:
+ if field.type == 'many2one' and isinstance(value, pycompat.string_types) and value:
Model = self.env[field.comodel_name]
records = Model.search([('name', '=', value)], limit=1)
if not records:
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index 4efc3df9eec..9ac7b7a2fd8 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -1396,7 +1396,7 @@ class CSVExport(ExportFormat, http.Controller):
for data in rows:
row = []
for d in data:
- if isinstance(d, unicode):
+ if type(d) != str and isinstance(d, pycompat.string_types):
try:
d = d.encode('utf-8')
except UnicodeError:
@@ -1446,7 +1446,7 @@ class ExcelExport(ExportFormat, http.Controller):
for row_index, row in enumerate(rows):
for cell_index, cell_value in enumerate(row):
cell_style = base_style
- if isinstance(cell_value, basestring):
+ if isinstance(cell_value, pycompat.string_types):
cell_value = re.sub("\r", " ", cell_value)
elif isinstance(cell_value, datetime.datetime):
cell_style = datetime_style
diff --git a/addons/web_editor/controllers/main.py b/addons/web_editor/controllers/main.py
index c501ce769d2..50a9540323a 100644
--- a/addons/web_editor/controllers/main.py
+++ b/addons/web_editor/controllers/main.py
@@ -39,7 +39,7 @@ class Web_Editor(http.Controller):
debug=request.debug)
for k in kwargs:
- if isinstance(kwargs[k], basestring) and kwargs[k].isdigit():
+ if isinstance(kwargs[k], pycompat.string_types) and kwargs[k].isdigit():
kwargs[k] = int(kwargs[k])
trans = dict(
@@ -186,7 +186,7 @@ class Web_Editor(http.Controller):
uploads += attachments.read(['name', 'mimetype', 'checksum', 'url'])
except Exception as e:
logger.exception("Failed to upload image to attachment")
- message = unicode(e)
+ message = pycompat.text_type(e)
return """<script type='text/javascript'>
window.parent['%s'](%s, %s);
diff --git a/addons/web_editor/models/ir_qweb.py b/addons/web_editor/models/ir_qweb.py
index cd665f38833..4b1cd512e06 100644
--- a/addons/web_editor/models/ir_qweb.py
+++ b/addons/web_editor/models/ir_qweb.py
@@ -48,7 +48,10 @@ class QWeb(models.AbstractModel):
el.set('t-call', el.attrib.pop('t-snippet'))
name = self.env['ir.ui.view'].search([('key', '=', el.attrib.get('t-call'))]).display_name
thumbnail = el.attrib.pop('t-thumbnail', "oe-thumbnail")
- div = u'<div name="%s" data-oe-type="snippet" data-oe-thumbnail="%s">' % (escape(ir_qweb.unicodifier(name)), escape(ir_qweb.unicodifier(thumbnail)))
+ div = u'<div name="%s" data-oe-type="snippet" data-oe-thumbnail="%s">' % (
+ escape(pycompat.to_text(name)),
+ escape(pycompat.to_text(thumbnail))
+ )
return [self._append(ast.Str(div))] + self._compile_node(el, options) + [self._append(ast.Str(u'</div>'))]
def _compile_directive_install(self, el, options):
@@ -58,7 +61,11 @@ class QWeb(models.AbstractModel):
return []
name = el.attrib.get('string') or 'Snippet'
thumbnail = el.attrib.pop('t-thumbnail', 'oe-thumbnail')
- div = u'<div name="%s" data-oe-type="snippet" data-module-id="%s" data-oe-thumbnail="%s"><section/></div>' % (escape(ir_qweb.unicodifier(name)), module.id, escape(ir_qweb.unicodifier(thumbnail)))
+ div = u'<div name="%s" data-oe-type="snippet" data-module-id="%s" data-oe-thumbnail="%s"><section/></div>' % (
+ escape(pycompat.to_text(name)),
+ module.id,
+ escape(pycompat.to_text(thumbnail))
+ )
return [self._append(ast.Str(div))]
else:
return []
@@ -202,7 +209,7 @@ class DateTime(models.AbstractModel):
def attributes(self, record, field_name, options, values):
attrs = super(DateTime, self).attributes(record, field_name, options, values)
value = record[field_name]
- if isinstance(value, basestring):
+ if isinstance(value, pycompat.string_types):
value = fields.Datetime.from_string(value)
if value:
# convert from UTC (server timezone) to user timezone
@@ -326,7 +333,7 @@ class Image(models.AbstractModel):
img = '<img class="%s" src="%s" style="%s"%s%s/>' % \
(classes, src, options.get('style', ''), ' alt="%s"' % alt if alt else '', ' data-zoom="1" data-zoom-image="%s"' % src_zoom if src_zoom else '')
- return ir_qweb.unicodifier(img)
+ return pycompat.to_text(img)
local_url_re = re.compile(r'^/(?P<module>[^]]+)/static/(?P<rest>.+)$')
diff --git a/addons/web_editor/models/ir_ui_view.py b/addons/web_editor/models/ir_ui_view.py
index 7cf9e2f3a27..96c27bc923d 100644
--- a/addons/web_editor/models/ir_ui_view.py
+++ b/addons/web_editor/models/ir_ui_view.py
@@ -127,7 +127,7 @@ class IrUiView(models.Model):
@api.model
def _view_obj(self, view_id):
- if isinstance(view_id, basestring):
+ if isinstance(view_id, pycompat.string_types):
return self.env.ref(view_id)
elif isinstance(view_id, pycompat.integer_types):
return self.browse(view_id)
diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py
index 2d758a3cc1f..229afc5c883 100644
--- a/addons/website/controllers/main.py
+++ b/addons/website/controllers/main.py
@@ -381,7 +381,7 @@ class Website(Home):
action = action_id = None
# find the action_id: either an xml_id, the path, or an ID
- if isinstance(path_or_xml_id_or_id, basestring) and '.' in path_or_xml_id_or_id:
+ if isinstance(path_or_xml_id_or_id, pycompat.string_types) and '.' in path_or_xml_id_or_id:
action = request.env.ref(path_or_xml_id_or_id, raise_if_not_found=False)
if not action:
action = ServerActions.search([('website_path', '=', path_or_xml_id_or_id), ('website_published', '=', True)], limit=1)
diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py
index 7e956e85658..b5a3284d159 100644
--- a/addons/website/models/ir_ui_view.py
+++ b/addons/website/models/ir_ui_view.py
@@ -52,7 +52,7 @@ class View(models.Model):
@api.model
def _view_obj(self, view_id):
- if isinstance(view_id, basestring):
+ if isinstance(view_id, pycompat.string_types):
if 'website_id' in self._context:
domain = [('key', '=', view_id), '|', ('website_id', '=', False), ('website_id', '=', self._context.get('website_id'))]
order = 'website_id'
diff --git a/addons/website/models/website.py b/addons/website/models/website.py
index 0b407e4d753..c46e3c2a327 100644
--- a/addons/website/models/website.py
+++ b/addons/website/models/website.py
@@ -37,11 +37,8 @@ DEFAULT_CDN_FILTERS = [
def url_for(path_or_uri, lang=None):
- if isinstance(path_or_uri, unicode):
- path_or_uri = path_or_uri.encode('utf-8')
- current_path = request.httprequest.path
- if isinstance(current_path, unicode):
- current_path = current_path.encode('utf-8')
+ path_or_uri = pycompat.to_native(path_or_uri)
+ current_path = pycompat.to_native(request.httprequest.path)
location = path_or_uri.strip()
force_lang = lang is not None
url = urls.url_parse(location)
@@ -667,7 +664,7 @@ class Menu(models.Model):
self.browse(to_delete).unlink()
for menu in data['data']:
mid = menu['id']
- if isinstance(mid, basestring):
+ if isinstance(mid, pycompat.string_types):
new_menu = self.create({'name': menu['name']})
replace_id(mid, new_menu.id)
for menu in data['data']:
diff --git a/doc/_extensions/github_link.py b/doc/_extensions/github_link.py
index d3e5d61c28e..155e4083025 100644
--- a/doc/_extensions/github_link.py
+++ b/doc/_extensions/github_link.py
@@ -4,6 +4,8 @@ import os.path
from werkzeug import urls
+from odoo.tools import pycompat
+
"""
* adds github_link(mode) context variable: provides URL (in relevant mode) of
current document on github
@@ -99,7 +101,7 @@ def add_doc_link(app, pagename, templatename, context, doctree):
# in Sphinx 1.3 it's possible to have mutliple source suffixes and that
# may be useful in the future
source_suffix = app.config.source_suffix
- source_suffix = source_suffix if isinstance(source_suffix, basestring) else source_suffix[0]
+ source_suffix = source_suffix if isinstance(source_suffix, pycompat.string_types) else source_suffix[0]
# can't use functools.partial because 3rd positional is line not mode
context['github_link'] = lambda mode='edit': make_github_link(
app, 'doc/%s%s' % (pagename, source_suffix), mode=mode)
diff --git a/doc/_extensions/odoo_ext/translator.py b/doc/_extensions/odoo_ext/translator.py
index 3a1928364eb..443458f33c7 100644
--- a/doc/_extensions/odoo_ext/translator.py
+++ b/doc/_extensions/odoo_ext/translator.py
@@ -11,6 +11,8 @@ from docutils import nodes
from sphinx import addnodes, util
from sphinx.locale import admonitionlabels
+from odoo.tools import pycompat
+
def _parents(node):
while node.parent:
@@ -62,7 +64,7 @@ class BootstrapTranslator(nodes.NodeVisitor, object):
self.param_separator = ','
def encode(self, text):
- return unicode(text).translate({
+ return pycompat.text_type(text).translate({
ord('&'): u'&amp;',
ord('<'): u'&lt;',
ord('"'): u'&quot;',
@@ -71,7 +73,7 @@ class BootstrapTranslator(nodes.NodeVisitor, object):
})
def starttag(self, node, tagname, **attributes):
- tagname = unicode(tagname).lower()
+ tagname = pycompat.text_type(tagname).lower()
# extract generic attributes
attrs = {name.lower(): value for name, value in attributes.items()}
@@ -105,8 +107,8 @@ class BootstrapTranslator(nodes.NodeVisitor, object):
)
# only "space characters" SPACE, CHARACTER TABULATION, LINE FEED,
# FORM FEED and CARRIAGE RETURN should be collapsed, not al White_Space
- def attval(self, value, whitespace=re.compile(u'[ \t\n\f\r]')):
- return self.encode(whitespace.sub(u' ', unicode(value)))
+ def attval(self, value, whitespace=re.compile(u'[ \t\n\f\r]+')):
+ return self.encode(whitespace.sub(u' ', pycompat.text_type(value)))
def astext(self):
return u''.join(self.body)
diff --git a/odoo/addons/base/ir/ir_fields.py b/odoo/addons/base/ir/ir_fields.py
index 204d4599425..b8994d9221d 100644
--- a/odoo/addons/base/ir/ir_fields.py
+++ b/odoo/addons/base/ir/ir_fields.py
@@ -38,9 +38,9 @@ class IrFieldsConverter(models.AbstractModel):
@api.model
def _format_import_error(self, error_type, error_msg, error_params=(), error_args=None):
# sanitize error params for later formatting by the import system
- sanitize = lambda p: p.replace('%', '%%') if isinstance(p, basestring) else p
+ sanitize = lambda p: p.replace('%', '%%') if isinstance(p, pycompat.string_types) else p
if error_params:
- if isinstance(error_params, basestring):
+ if isinstance(error_params, pycompat.string_types):
error_params = sanitize(error_params)
elif isinstance(error_params, dict):
error_params = {k: sanitize(v) for k, v in pycompat.items(error_params)}
@@ -78,7 +78,7 @@ class IrFieldsConverter(models.AbstractModel):
try:
converted[field], ws = converters[field](value)
for w in ws:
- if isinstance(w, basestring):
+ if isinstance(w, pycompat.string_types):
# wrap warning string in an ImportWarning for
# uniform handling
w = ImportWarning(w)
@@ -269,14 +269,14 @@ class IrFieldsConverter(models.AbstractModel):
for item, label in selection:
label = ustr(label)
labels = [label] + self._get_translations(('selection', 'model', 'code'), label)
- if value == unicode(item) or value in labels:
+ if value == pycompat.text_type(item) or value in labels:
return item, []
raise self._format_import_error(
ValueError,
_(u"Value '%s' not found in selection field '%%(field)s'"),
value,
- {'moreinfo': [_label or unicode(item) for item, _label in selection if _label or item]}
+ {'moreinfo': [_label or pycompat.text_type(item) for item, _label in selection if _label or item]}
)
@api.model
diff --git a/odoo/addons/base/ir/ir_model.py b/odoo/addons/base/ir/ir_model.py
index 4fa66406108..2092f4cd0b2 100644
--- a/odoo/addons/base/ir/ir_model.py
+++ b/odoo/addons/base/ir/ir_model.py
@@ -18,11 +18,6 @@ _logger = logging.getLogger(__name__)
MODULE_UNINSTALL_FLAG = '_force_unlink'
-def encode(s):
- """ Return an UTF8-encoded version of ``s``. """
- return s.encode('utf8') if isinstance(s, unicode) else s
-
-
# base environment for doing a safe_eval
SAFE_EVAL_BASE = {
'datetime': datetime,
@@ -272,7 +267,7 @@ class IrModel(models.Model):
def _instanciate(self, model_data):
""" Return a class for the custom model given by parameters ``model_data``. """
class CustomModel(models.Model):
- _name = encode(model_data['model'])
+ _name = pycompat.to_native(model_data['model'])
_description = model_data['name']
_module = False
_custom = True
@@ -1134,7 +1129,7 @@ class IrModelAccess(models.Model):
# User root have all accesses
return True
- assert isinstance(model, basestring), 'Not a model name: %s' % (model,)
+ assert isinstance(model, pycompat.string_types), 'Not a model name: %s' % (model,)
assert mode in ('read', 'write', 'create', 'unlink'), 'Invalid access mode'
# TransientModel records have no access rights, only an implicit access rule
diff --git a/odoo/addons/base/ir/ir_qweb/__init__.py b/odoo/addons/base/ir/ir_qweb/__init__.py
index b7fb78857b1..b0add281fdb 100644
--- a/odoo/addons/base/ir/ir_qweb/__init__.py
+++ b/odoo/addons/base/ir/ir_qweb/__init__.py
@@ -5,5 +5,5 @@ from . import fields
from . import assetsbundle
from .assetsbundle import AssetsBundle
-from .qweb import unicodifier, QWebException
+from .qweb import QWebException
from .ir_qweb import IrQWeb, QWeb
diff --git a/odoo/addons/base/ir/ir_qweb/fields.py b/odoo/addons/base/ir/ir_qweb/fields.py
index 7e83fced25f..6cc5337f8a6 100644
--- a/odoo/addons/base/ir/ir_qweb/fields.py
+++ b/odoo/addons/base/ir/ir_qweb/fields.py
@@ -6,8 +6,7 @@ from io import BytesIO
from odoo import api, fields, models, _
from PIL import Image
import babel
-from odoo.tools import html_escape as escape, posix_to_ldml, safe_eval, float_utils, format_date
-from .qweb import unicodifier
+from odoo.tools import html_escape as escape, posix_to_ldml, safe_eval, float_utils, format_date, pycompat
import logging
_logger = logging.getLogger(__name__)
@@ -20,7 +19,7 @@ def nl2br(string):
:param str string:
:rtype: unicode
"""
- return unicodifier(string).replace(u'\n', u'<br>\n')
+ return pycompat.to_text(string).replace(u'\n', u'<br>\n')
def html_escape(string, options):
""" Automatically escapes content unless options['html-escape']
@@ -89,7 +88,7 @@ class FieldConverter(models.AbstractModel):
Converts a single value to its HTML version/output
:rtype: unicode
"""
- return html_escape(unicodifier(value) or u'', options)
+ return html_escape(pycompat.to_text(value), options)
@api.model
def record_to_html(self, record, field_name, options):
@@ -124,7 +123,7 @@ class IntegerConverter(models.AbstractModel):
@api.model
def value_to_html(self, value, options):
- return unicodifier(self.user_lang().format('%d', value, grouping=True).replace(r'-', u'\u2011'))
+ return pycompat.to_text(self.user_lang().format('%d', value, grouping=True).replace(r'-', u'\u2011'))
class FloatConverter(models.AbstractModel):
@@ -153,7 +152,7 @@ class FloatConverter(models.AbstractModel):
if precision is None:
formatted = re.sub(r'(?:(0|\d+?)0+)$', r'\1', formatted)
- return unicodifier(formatted)
+ return pycompat.to_text(formatted)
@api.model
def record_to_html(self, record, field_name, options):
@@ -183,7 +182,7 @@ class DateTimeConverter(models.AbstractModel):
lang = self.user_lang()
locale = babel.Locale.parse(lang.code)
- if isinstance(value, basestring):
+ if isinstance(value, pycompat.string_types):
value = fields.Datetime.from_string(value)
value = fields.Datetime.context_timestamp(self, value)
@@ -201,7 +200,7 @@ class DateTimeConverter(models.AbstractModel):
if options and options.get('hide_seconds'):
pattern = pattern.replace(":ss", "").replace(":s", "")
- return unicodifier(babel.dates.format_datetime(value, format=pattern, locale=locale))
+ return pycompat.to_text(babel.dates.format_datetime(value, format=pattern, locale=locale))
class TextConverter(models.AbstractModel):
@@ -224,7 +223,7 @@ class SelectionConverter(models.AbstractModel):
def value_to_html(self, value, options):
if not value:
return ''
- return html_escape(unicodifier(options['selection'][value]) or u'', options)
+ return html_escape(pycompat.to_text(options['selection'][value]) or u'', options)
@api.model
def record_to_html(self, record, field_name, options):
@@ -253,7 +252,7 @@ class HTMLConverter(models.AbstractModel):
@api.model
def value_to_html(self, value, options):
- return unicodifier(value) or u''
+ return pycompat.to_text(value)
class ImageConverter(models.AbstractModel):
@@ -278,7 +277,7 @@ class ImageConverter(models.AbstractModel):
except: # image.verify() throws "suitable exceptions", I have no idea what they are
raise ValueError("Invalid image content")
- return unicodifier(b'<img src="data:%s;base64,%s">' % (Image.MIME[image.format].encode('ascii'), value))
+ return u'<img src="data:%s;base64,%s">' % (Image.MIME[image.format], value.decode('ascii'))
class MonetaryConverter(models.AbstractModel):
@@ -399,13 +398,13 @@ class RelativeDatetimeConverter(models.AbstractModel):
def value_to_html(self, value, options):
locale = babel.Locale.parse(self.user_lang().code)
- if isinstance(value, basestring):
+ if isinstance(value, pycompat.string_types):
value = fields.Datetime.from_string(value)
# value should be a naive datetime in UTC. So is fields.Datetime.now()
reference = fields.Datetime.from_string(options['now'])
- return unicodifier(babel.dates.format_timedelta(value - reference, add_direction=True, locale=locale))
+ return pycompat.to_text(babel.dates.format_timedelta(value - reference, add_direction=True, locale=locale))
@api.model
def record_to_html(self, record, field_name, options):
@@ -482,4 +481,4 @@ class QwebView(models.AbstractModel):
view = view.with_context(object=record)
- return unicodifier(view.render(view._context, engine='ir.qweb'))
+ return pycompat.to_text(view.render(view._context, engine='ir.qweb'))
diff --git a/odoo/addons/base/ir/ir_qweb/ir_qweb.py b/odoo/addons/base/ir/ir_qweb/ir_qweb.py
index 102b36e0898..b23ab34f103 100644
--- a/odoo/addons/base/ir/ir_qweb/ir_qweb.py
+++ b/odoo/addons/base/ir/ir_qweb/ir_qweb.py
@@ -218,7 +218,7 @@ class IrQWeb(models.AbstractModel, QWeb):
files = []
remains = []
for el in html.fragments_fromstring(template):
- if isinstance(el, basestring):
+ if isinstance(el, pycompat.string_types):
remains.append(el)
elif isinstance(el, html.HtmlElement):
href = el.get('href', '')
diff --git a/odoo/addons/base/ir/ir_qweb/qweb.py b/odoo/addons/base/ir/ir_qweb/qweb.py
index 69a01635f2d..61bd39eab8e 100644
--- a/odoo/addons/base/ir/ir_qweb/qweb.py
+++ b/odoo/addons/base/ir/ir_qweb/qweb.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import ast
import logging
+import os.path
import re
import traceback
@@ -176,13 +177,6 @@ class QWebException(Exception):
# Avoid DeprecationWarning while still remaining compatible with werkzeug pre-0.9
escape = (lambda text: _escape(text, quote=True)) if getattr(werkzeug, '__version__', '0.0') < '0.9.0' else _escape
-def unicodifier(val):
- if val is None or val is False:
- return u''
- if isinstance(val, str):
- return val.decode('utf-8')
- return unicode(val)
-
def foreach_iterator(base_ctx, enum, name):
ctx = base_ctx.copy()
if not enum:
@@ -380,10 +374,11 @@ class QWeb(object):
if isinstance(document, etree._Element):
element = document
document = etree.tostring(document)
- elif document.startswith("<?xml"):
- element = etree.fromstring(document)
- else:
+ elif os.path.exists(document):
element = etree.parse(document).getroot()
+ else:
+ element = etree.fromstring(document)
+
for node in element:
if node.get('t-name') == str(template):
return (node, document)
@@ -527,12 +522,14 @@ class QWeb(object):
* itertools
Define:
* escape
- * unicodifier (empty string for a None or False, otherwise unicode string)
+ * to_text (empty string for a None or False, otherwise unicode string)
+ * string_types (replacement for basestring)
"""
return ast.parse(dedent("""
from collections import OrderedDict
from odoo.tools import pycompat
- from odoo.addons.base.ir.ir_qweb.qweb import escape, unicodifier, foreach_iterator
+ from odoo.tools.pycompat import to_text, string_types
+ from odoo.addons.base.ir.ir_qweb.qweb import escape, foreach_iterator
"""))
def _create_def(self, options, body, prefix='fn', lineno=None):
@@ -777,7 +774,7 @@ class QWeb(object):
if unqualified_el_tag == 't':
return content
- tag = u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in pycompat.items(attrib)]))
+ tag = u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(pycompat.to_text(value))) for name, value in pycompat.items(attrib)]))
if unqualified_el_tag in self._void_elements:
return [self._append(ast.Str(tag + '/>'))] + content
else:
@@ -879,7 +876,7 @@ class QWeb(object):
# append(u' ')
# append(name)
# append(u'="')
- # append(escape(unicodifier((value)))
+ # append(escape(to_text((value)))
# append(u'"')
body.append(ast.For(
target=ast.Tuple(elts=[ast.Name(id='name', ctx=ast.Store()), ast.Name(id='value', ctx=ast.Store())], ctx=ast.Store()),
@@ -901,7 +898,7 @@ class QWeb(object):
func=ast.Name(id='isinstance', ctx=ast.Load()),
args=[
ast.Name(id='value', ctx=ast.Load()),
- ast.Name(id='basestring', ctx=ast.Load())
+ ast.Name(id='string_types', ctx=ast.Load())
],
keywords=[],
starargs=None, kwargs=None
@@ -915,7 +912,7 @@ class QWeb(object):
self._append(ast.Call(
func=ast.Name(id='escape', ctx=ast.Load()),
args=[ast.Call(
- func=ast.Name(id='unicodifier', ctx=ast.Load()),
+ func=ast.Name(id='to_text', ctx=ast.Load()),
args=[ast.Name(id='value', ctx=ast.Load())], keywords=[],
starargs=None, kwargs=None
)], keywords=[],
@@ -955,7 +952,7 @@ class QWeb(object):
if unqualified_el_tag == 't':
return content
- body = [self._append(ast.Str(u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in pycompat.items(extra_attrib)]))))]
+ body = [self._append(ast.Str(u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(pycompat.to_text(value))) for name, value in pycompat.items(extra_attrib)]))))]
body.extend(self._compile_all_attributes(el, options, attr_already_created))
if unqualified_el_tag in self._void_elements:
body.append(self._append(ast.Str(u'/>')))
@@ -1044,7 +1041,7 @@ class QWeb(object):
def _compile_directive_content(self, el, options):
body = []
if el.text is not None:
- body.append(self._append(ast.Str(unicodifier(el.text))))
+ body.append(self._append(ast.Str(pycompat.to_text(el.text))))
if el.getchildren():
for item in el:
# ignore comments & processing instructions
@@ -1135,7 +1132,7 @@ class QWeb(object):
)]
def _compile_tail(self, el):
- return el.tail is not None and [self._append(ast.Str(unicodifier(el.tail)))] or []
+ return el.tail is not None and [self._append(ast.Str(pycompat.to_text(el.tail)))] or []
def _compile_directive_esc(self, el, options):
field_options = self._compile_widget_options(el, 'esc')
@@ -1148,14 +1145,14 @@ class QWeb(object):
return content + self._compile_widget_value(el, options)
def _compile_widget(self, el, expression, field_options, escape=None):
- # if isinstance(value, basestring):
- # value = escape(unicodifier(value))
+ # if isinstance(value, string_types):
+ # value = escape(to_text(value))
escaped = ast.If(
test=ast.Call(
func=ast.Name(id='isinstance', ctx=ast.Load()),
args=[
ast.Name(id='content', ctx=ast.Load()),
- ast.Name(id='basestring', ctx=ast.Load())
+ ast.Name(id='string_types', ctx=ast.Load())
], keywords=[],
starargs=None, kwargs=None
),
@@ -1164,7 +1161,7 @@ class QWeb(object):
value=ast.Call(
func=ast.Name(id=escape, ctx=ast.Load()),
args=[ast.Call(
- func=ast.Name(id='unicodifier', ctx=ast.Load()),
+ func=ast.Name(id='to_text', ctx=ast.Load()),
args=[ast.Name(id='content', ctx=ast.Load())], keywords=[],
starargs=None, kwargs=None
)], keywords=[],
@@ -1332,7 +1329,7 @@ class QWeb(object):
]
# if content is not None:
- # display the tag (unicodifier(content))
+ # display the tag (to_text(content))
# else
# if default_content:
# display the tag with default content
@@ -1341,7 +1338,7 @@ class QWeb(object):
return [self._if_content_is_not_Falsy(
body=self._compile_tag(el, [self._append(
ast.Call(
- func=ast.Name(id='unicodifier', ctx=ast.Load()),
+ func=ast.Name(id='to_text', ctx=ast.Load()),
args=[ast.Name(id='content', ctx=ast.Load())], keywords=[],
starargs=None, kwargs=None
)
@@ -1461,7 +1458,7 @@ class QWeb(object):
keys = []
values = []
for key, value in pycompat.items(options['nsmap']):
- if isinstance(key, basestring):
+ if isinstance(key, pycompat.string_types):
keys.append(ast.Str(s=key))
elif key is None:
keys.append(ast.Name(id='None', ctx=ast.Load()))
@@ -1547,7 +1544,7 @@ class QWeb(object):
def _compile_strexpr(self, expr):
# ensure result is unicode
return ast.Call(
- func=ast.Name(id='unicodifier', ctx=ast.Load()),
+ func=ast.Name(id='to_text', ctx=ast.Load()),
args=[self._compile_expr(expr)], keywords=[],
starargs=None, kwargs=None
)
@@ -1591,7 +1588,7 @@ class QWeb(object):
for m in _FORMAT_REGEX.finditer(f):
literal = f[base_idx:m.start()]
if literal:
- elts.append(ast.Str(literal if isinstance(literal, unicode) else literal.decode('utf-8')))
+ elts.append(ast.Str(literal if isinstance(literal, pycompat.text_type) else literal.decode('utf-8')))
expr = m.group(1) or m.group(2)
elts.append(self._compile_strexpr(expr))
@@ -1599,7 +1596,7 @@ class QWeb(object):
# string past last regex match
literal = f[base_idx:]
if literal:
- elts.append(ast.Str(literal if isinstance(literal, unicode) else literal.decode('utf-8')))
+ elts.append(ast.Str(literal if isinstance(literal, pycompat.text_type) else literal.decode('utf-8')))
return reduce(lambda acc, it: ast.BinOp(
left=acc,
diff --git a/odoo/addons/base/ir/ir_translation.py b/odoo/addons/base/ir/ir_translation.py
index cd954778a92..02c085818d2 100644
--- a/odoo/addons/base/ir/ir_translation.py
+++ b/odoo/addons/base/ir/ir_translation.py
@@ -394,7 +394,7 @@ class IrTranslation(models.Model):
# always pass unicode so we can remove the string encoding/decoding.
if not lang:
return tools.ustr(source or '')
- if isinstance(types, basestring):
+ if isinstance(types, pycompat.string_types):
types = (types,)
if res_id:
if isinstance(res_id, pycompat.integer_types):
diff --git a/odoo/addons/base/ir/ir_ui_view.py b/odoo/addons/base/ir/ir_ui_view.py
index d70ee4cf951..1078d81b02f 100644
--- a/odoo/addons/base/ir/ir_ui_view.py
+++ b/odoo/addons/base/ir/ir_ui_view.py
@@ -29,7 +29,7 @@ from odoo.tools.convert import _fix_multiple_roots
from odoo.tools.parse_version import parse_version
from odoo.tools.safe_eval import safe_eval
from odoo.tools.view_validation import valid_view
-from odoo.tools.translate import encode, xml_translate, TRANSLATED_ATTRS
+from odoo.tools.translate import xml_translate, TRANSLATED_ATTRS
_logger = logging.getLogger(__name__)
@@ -233,7 +233,7 @@ actual arch.
else:
_logger.warning("View %s: Full path [%s] cannot be found.", view.xml_id, view.arch_fs)
arch_fs = False
- view.arch = arch_fs or view.arch_db
+ view.arch = pycompat.to_text(arch_fs or view.arch_db)
def _inverse_arch(self):
for view in self:
@@ -269,7 +269,7 @@ actual arch.
view.model_data_id = data['id']
def _search_model_data_id(self, operator, value):
- name = 'name' if isinstance(value, basestring) else 'id'
+ name = 'name' if isinstance(value, pycompat.string_types) else 'id'
domain = [('model', '=', 'ir.ui.view'), (name, operator, value)]
data = self.env['ir.model.data'].sudo().search(domain)
return [('id', 'in', data.mapped('res_id'))]
@@ -313,7 +313,7 @@ actual arch.
# Sanity checks: the view should not break anything upon rendering!
# Any exception raised below will cause a transaction rollback.
for view in self:
- view_arch = etree.fromstring(encode(view.arch))
+ view_arch = etree.fromstring(view.arch.encode('utf-8'))
view._valid_inheritance(view_arch)
view_def = view.read_combined(['arch'])
view_arch_utf8 = view_def['arch']
@@ -942,7 +942,7 @@ actual arch.
arch_tree = etree.fromstring(arch)
self.distribute_branding(arch_tree)
root = E.templates(arch_tree)
- arch = etree.tostring(root, encoding='utf-8', xml_declaration=True)
+ arch = etree.tostring(root, encoding='utf-8')
return arch
@api.model
diff --git a/odoo/addons/base/ir/ir_values.py b/odoo/addons/base/ir/ir_values.py
index ace2998b2b3..ebe1917da10 100644
--- a/odoo/addons/base/ir/ir_values.py
+++ b/odoo/addons/base/ir/ir_values.py
@@ -211,8 +211,8 @@ class IrValues(models.Model):
match)
:return: the newly created ir.values entry
"""
- if isinstance(value, unicode):
- value = value.encode('utf8')
+ if isinstance(value, pycompat.string_types):
+ value = pycompat.to_native(value)
if company_id is True:
# should be company-specific, need to get company id
company_id = self.env.user.company_id.id
@@ -347,7 +347,7 @@ class IrValues(models.Model):
specific record of the model, not all records.
:return: the newly created ir.values entry
"""
- assert isinstance(action, basestring) and ',' in action, \
+ assert isinstance(action, pycompat.string_types) and ',' in action, \
'Action definition must be an action reference, e.g. "ir.actions.act_window,42"'
assert action_slot in ACTION_SLOTS, \
'Action slot (%s) must be one of: %r' % (action_slot, ACTION_SLOTS)
diff --git a/odoo/addons/base/module/module.py b/odoo/addons/base/module/module.py
index e2f48e444a0..ccd4be8aa8b 100644
--- a/odoo/addons/base/module/module.py
+++ b/odoo/addons/base/module/module.py
@@ -645,7 +645,7 @@ class Module(models.Model):
updated_values = {}
for key in values:
old = getattr(mod, key)
- updated = tools.ustr(values[key]) if isinstance(values[key], basestring) else values[key]
+ updated = tools.ustr(values[key]) if isinstance(values[key], pycompat.string_types) else values[key]
if (old or updated) and updated != old:
updated_values[key] = values[key]
if terp.get('installable', True) and mod.state == 'uninstallable':
diff --git a/odoo/addons/base/res/res_users.py b/odoo/addons/base/res/res_users.py
index 2a66c05a4aa..548e18e6879 100644
--- a/odoo/addons/base/res/res_users.py
+++ b/odoo/addons/base/res/res_users.py
@@ -107,7 +107,7 @@ class Groups(models.Model):
return expression.AND(domains)
else:
return expression.OR(domains)
- if isinstance(operand, basestring):
+ if isinstance(operand, pycompat.string_types):
lst = False
operand = [operand]
where = []
@@ -306,7 +306,7 @@ class Users(models.Model):
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
- groupby_fields = set([groupby] if isinstance(groupby, basestring) else groupby)
+ groupby_fields = set([groupby] if isinstance(groupby, pycompat.string_types) else groupby)
if groupby_fields.intersection(USER_PRIVATE_FIELDS):
raise AccessError(_("Invalid 'group by' parameter"))
return super(Users, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
@@ -739,7 +739,7 @@ class GroupsView(models.Model):
xml2.append({'class': "o_label_nowrap"})
xml = E.field(E.group(*(xml1), col="2"), E.group(*(xml2), col="4"), name="groups_id", position="replace")
xml.addprevious(etree.Comment("GENERATED AUTOMATICALLY BY GROUPS"))
- xml_content = etree.tostring(xml, pretty_print=True, xml_declaration=True, encoding="utf-8")
+ xml_content = etree.tostring(xml, pretty_print=True, encoding="utf-8")
view.with_context(lang=None).write({'arch': xml_content, 'arch_fs': False})
def get_application_groups(self, domain):
diff --git a/odoo/addons/base/tests/test_func.py b/odoo/addons/base/tests/test_func.py
index ac394cb9b79..66ad02f4e9d 100644
--- a/odoo/addons/base/tests/test_func.py
+++ b/odoo/addons/base/tests/test_func.py
@@ -4,7 +4,7 @@
import functools
import unittest
-from odoo.tools import frozendict
+from odoo.tools import frozendict, pycompat
from odoo.tools.func import compose
@@ -16,7 +16,7 @@ class TestCompose(unittest.TestCase):
def test_decorator(self):
""" ensure compose() can be partially applied as a decorator
"""
- @functools.partial(compose, unicode)
+ @functools.partial(compose, pycompat.text_type)
def mul(a, b):
return a * b
diff --git a/odoo/addons/base/tests/test_qweb.py b/odoo/addons/base/tests/test_qweb.py
index 7a8f921af55..088949b3bd7 100644
--- a/odoo/addons/base/tests/test_qweb.py
+++ b/odoo/addons/base/tests/test_qweb.py
@@ -484,7 +484,7 @@ class FileSystemLoader(object):
if node.get('t-name') == name:
root = etree.Element('templates')
root.append(deepcopy(node))
- arch = etree.tostring(root, encoding='utf-8', xml_declaration=True)
+ arch = etree.tostring(root, encoding='utf-8')
return arch
diff --git a/odoo/addons/base/tests/test_res_config.py b/odoo/addons/base/tests/test_res_config.py
index 0be7dcc7c8d..1e30e1f1a24 100644
--- a/odoo/addons/base/tests/test_res_config.py
+++ b/odoo/addons/base/tests/test_res_config.py
@@ -46,7 +46,7 @@ class TestResConfig(TransactionCase):
# Check types
self.assertIsInstance(res, tuple)
self.assertEqual(len(res), 2, "The result should contain 2 elements")
- self.assertIsInstance(res[0], basestring)
+ self.assertIsInstance(res[0], pycompat.string_types)
self.assertIsInstance(res[1], pycompat.integer_types)
# Check returned values
@@ -58,7 +58,7 @@ class TestResConfig(TransactionCase):
res = self.ResConfig.get_option_name(self.full_field_name)
# Check type
- self.assertIsInstance(res, basestring)
+ self.assertIsInstance(res, pycompat.string_types)
# Check returned value
self.assertEqual(res, self.expected_name)
diff --git a/odoo/addons/test_impex/models.py b/odoo/addons/test_impex/models.py
index f1e10b10abf..027739b2538 100644
--- a/odoo/addons/test_impex/models.py
+++ b/odoo/addons/test_impex/models.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
+from odoo.tools import pycompat
def selection_fn(model):
@@ -48,7 +49,7 @@ for name, field in MODELS:
@api.model
def name_search(self, name='', args=None, operator='ilike', limit=100):
- if isinstance(name, basestring) and name.split(':')[0] == self._name:
+ if isinstance(name, pycompat.string_types) and name.split(':')[0] == self._name:
records = self.search([('value', operator, int(name.split(':')[1]))])
return records.name_get()
else:
@@ -70,7 +71,7 @@ class One2ManyChild(models.Model):
@api.model
def name_search(self, name='', args=None, operator='ilike', limit=100):
- if isinstance(name, basestring) and name.split(':')[0] == self._name:
+ if isinstance(name, pycompat.string_types) and name.split(':')[0] == self._name:
records = self.search([('value', operator, int(name.split(':')[1]))])
return records.name_get()
else:
@@ -124,7 +125,7 @@ class Many2ManyChild(models.Model):
@api.model
def name_search(self, name='', args=None, operator='ilike', limit=100):
- if isinstance(name, basestring) and name.split(':')[0] == self._name:
+ if isinstance(name, pycompat.string_types) and name.split(':')[0] == self._name:
records = self.search([('value', operator, int(name.split(':')[1]))])
return records.name_get()
else:
diff --git a/odoo/addons/test_impex/tests/test_export.py b/odoo/addons/test_impex/tests/test_export.py
index 72a3b2a6f75..9c7311319e8 100644
--- a/odoo/addons/test_impex/tests/test_export.py
+++ b/odoo/addons/test_impex/tests/test_export.py
@@ -69,7 +69,7 @@ class test_integer_field(CreatorCase):
def test_huge(self):
self.assertEqual(
self.export(2**31-1),
- [[unicode(2**31-1)]])
+ [[pycompat.text_type(2**31-1)]])
class test_float_field(CreatorCase):
@@ -319,7 +319,7 @@ class test_m2o(CreatorCase):
record = self.env['export.integer'].create({'value': 42})
self.assertEqual(
self.export(record.id, fields=['value/.id', 'value/value']),
- [[unicode(record.id), u'42']])
+ [[pycompat.text_type(record.id), u'42']])
def test_external_id(self):
record = self.env['export.integer'].create({'value': 42})
diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py
index 948470d60d6..b3fa31c77b8 100644
--- a/odoo/addons/test_new_api/tests/test_new_fields.py
+++ b/odoo/addons/test_new_api/tests/test_new_fields.py
@@ -5,7 +5,7 @@ from datetime import date, datetime
from odoo.exceptions import AccessError, except_orm
from odoo.tests import common
-from odoo.tools import mute_logger, float_repr
+from odoo.tools import mute_logger, float_repr, pycompat
class TestFields(common.TransactionCase):
@@ -16,8 +16,8 @@ class TestFields(common.TransactionCase):
discussion = self.env.ref('test_new_api.discussion_0')
# read field as a record attribute or as a record item
- self.assertIsInstance(discussion.name, basestring)
- self.assertIsInstance(discussion['name'], basestring)
+ self.assertIsInstance(discussion.name, pycompat.string_types)
+ self.assertIsInstance(discussion['name'], pycompat.string_types)
self.assertEqual(discussion['name'], discussion.name)
# read it with method read()
diff --git a/odoo/fields.py b/odoo/fields.py
index 12702612d48..8efdd20b8fd 100644
--- a/odoo/fields.py
+++ b/odoo/fields.py
@@ -20,12 +20,11 @@ except ImportError:
import psycopg2
-from odoo.sql_db import LazyCursor
-from odoo.tools import 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, _
-import odoo.tools.sql as sql
+from .sql_db import LazyCursor
+from .tools import float_repr, float_round, frozendict, html_sanitize, human_size, pg_varchar, ustr, OrderedSet, pycompat, sql
+from .tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT
+from .tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
+from .tools.translate import html_translate, _
DATE_LENGTH = len(date.today().strftime(DATE_FORMAT))
DATETIME_LENGTH = len(datetime.now().strftime(DATETIME_FORMAT))
@@ -499,7 +498,7 @@ class Field(MetaField('DummyField', (object,), {})):
def make_depends(deps):
return tuple(deps(model) if callable(deps) else deps)
- if isinstance(self.compute, basestring):
+ if isinstance(self.compute, pycompat.string_types):
# if the compute method has been overridden, concatenate all their _depends
self.depends = ()
for method in resolve_mro(model, self.compute, callable):
@@ -518,7 +517,7 @@ class Field(MetaField('DummyField', (object,), {})):
def _setup_related_full(self, model):
""" Setup the attributes of a related field. """
# fix the type of self.related if necessary
- if isinstance(self.related, basestring):
+ if isinstance(self.related, pycompat.string_types):
self.related = tuple(self.related.split('.'))
# determine the chain of fields, and make sure they are all set up
@@ -746,9 +745,7 @@ class Field(MetaField('DummyField', (object,), {})):
""" Convert ``value`` from the ``write`` format to the SQL format. """
if value is None or value is False:
return None
- if isinstance(value, unicode):
- return value.encode('utf8')
- return str(value)
+ return pycompat.to_native(value)
def convert_to_cache(self, value, record, validate=True):
""" Convert ``value`` to the cache format; ``value`` may come from an
@@ -974,7 +971,7 @@ class Field(MetaField('DummyField', (object,), {})):
for field in fields:
for record in records:
record._cache[field] = field.convert_to_cache(False, record, validate=False)
- if isinstance(self.compute, basestring):
+ if isinstance(self.compute, pycompat.string_types):
getattr(records, self.compute)()
else:
self.compute(records)
@@ -1048,14 +1045,14 @@ class Field(MetaField('DummyField', (object,), {})):
def determine_inverse(self, records):
""" Given the value of ``self`` on ``records``, inverse the computation. """
- if isinstance(self.inverse, basestring):
+ if isinstance(self.inverse, pycompat.string_types):
getattr(records, self.inverse)()
else:
self.inverse(records)
def determine_domain(self, records, operator, value):
""" Return a domain representing a condition on ``self``. """
- if isinstance(self.search, basestring):
+ if isinstance(self.search, pycompat.string_types):
return getattr(records, self.search)(operator, value)
else:
return self.search(records, operator, value)
@@ -1510,7 +1507,7 @@ class Date(Field):
def convert_to_cache(self, value, record, validate=True):
if not value:
return False
- if isinstance(value, basestring):
+ if isinstance(value, pycompat.string_types):
if validate:
# force parsing for validation
self.from_string(value)
@@ -1580,7 +1577,7 @@ class Datetime(Field):
def convert_to_cache(self, value, record, validate=True):
if not value:
return False
- if isinstance(value, basestring):
+ if isinstance(value, pycompat.string_types):
if validate:
# force parsing for validation
self.from_string(value)
@@ -1736,7 +1733,7 @@ class Selection(Field):
translated according to context language
"""
selection = self.selection
- if isinstance(selection, basestring):
+ if isinstance(selection, pycompat.string_types):
return getattr(env[self.model_name], selection)()
if callable(selection):
return selection(env[self.model_name])
@@ -1753,7 +1750,7 @@ class Selection(Field):
def get_values(self, env):
""" return a list of the possible values """
selection = self.selection
- if isinstance(selection, basestring):
+ if isinstance(selection, pycompat.string_types):
selection = getattr(env[self.model_name], selection)()
elif callable(selection):
selection = selection(env[self.model_name])
@@ -1777,14 +1774,6 @@ class Selection(Field):
return item[1]
return False
- def convert_to_column(self, value, record, values=None):
- """ Convert ``value`` from the ``write`` format to the SQL format. """
- if value is None or value is False:
- return None
- if isinstance(value, unicode):
- return value.encode('utf8')
- return str(value)
-
class Reference(Selection):
type = 'reference'
@@ -1802,7 +1791,7 @@ class Reference(Selection):
if isinstance(value, BaseModel):
if not validate or (value._name in self.get_values(record.env) and len(value) <= 1):
return process(value._name, value.id) if value else False
- elif isinstance(value, basestring):
+ elif isinstance(value, pycompat.string_types):
res_model, res_id = value.split(',')
if record.env[res_model].browse(int(res_id)).exists():
return process(res_model, int(res_id))
@@ -2122,7 +2111,7 @@ class _RelationalMulti(_Relational):
self.depends += tuple(
self.name + '.' + arg[0]
for arg in self.domain
- if isinstance(arg, (tuple, list)) and isinstance(arg[0], basestring)
+ if isinstance(arg, (tuple, list)) and isinstance(arg[0], pycompat.string_types)
)
diff --git a/odoo/http.py b/odoo/http.py
index 83cde441fb9..941fae06fe2 100644
--- a/odoo/http.py
+++ b/odoo/http.py
@@ -44,12 +44,12 @@ except ImportError:
psutil = None
import odoo
-from odoo.service.server import memory_info
-from odoo.service import security, model as service_model
-from odoo.tools.func import lazy_property
-from odoo.tools import ustr, consteq, frozendict, pycompat, unique
+from .service.server import memory_info
+from .service import security, model as service_model
+from .tools.func import lazy_property
+from .tools import ustr, consteq, frozendict, pycompat, unique
-from odoo.modules.module import module_manifest
+from .modules.module import module_manifest
_logger = logging.getLogger(__name__)
rpc_request = logging.getLogger(__name__ + '.rpc.request')
@@ -510,7 +510,7 @@ def route(route=None, **kw):
if isinstance(response, Response) or f.routing_type == 'json':
return response
- if isinstance(response, basestring):
+ if isinstance(response, pycompat.string_types):
return Response(response)
if isinstance(response, werkzeug.exceptions.HTTPException):
@@ -1386,7 +1386,7 @@ class Root(object):
else:
raise
- if isinstance(result, basestring):
+ if isinstance(result, pycompat.string_types):
response = Response(result, mimetype='text/html')
else:
response = result
@@ -1554,7 +1554,7 @@ def send_file(filepath_or_fp, mimetype=None, as_attachment=False, filename=None,
:param cache_timeout: the timeout in seconds for the headers.
"""
- if isinstance(filepath_or_fp, (str, unicode)):
+ if isinstance(filepath_or_fp, pycompat.string_types):
if not filename:
filename = os.path.basename(filepath_or_fp)
file = open(filepath_or_fp, 'rb')
@@ -1604,7 +1604,7 @@ def send_file(filepath_or_fp, mimetype=None, as_attachment=False, filename=None,
mtime,
size,
adler32(
- filename.encode('utf-8') if isinstance(filename, unicode)
+ filename.encode('utf-8') if isinstance(filename, pycompat.text_type)
else filename
) & 0xffffffff
))
diff --git a/odoo/loglevels.py b/odoo/loglevels.py
index 8c0242867d2..4380ff29427 100644
--- a/odoo/loglevels.py
+++ b/odoo/loglevels.py
@@ -37,13 +37,16 @@ def get_encodings(hint_encoding='utf-8'):
if prefenc:
yield prefenc
+# not using pycompat to avoid circular import: pycompat is in tools much of
+# which comes back to import loglevels
+text_type = type(u'')
def ustr(value, hint_encoding='utf-8', errors='strict'):
"""This method is similar to the builtin `unicode`, except
that it may try multiple encodings to find one that works
for decoding `value`, and defaults to 'utf-8' first.
- :param: value: the value to convert
- :param: hint_encoding: an optional encoding that was detecte
+ :param value: the value to convert
+ :param hint_encoding: an optional encoding that was detecte
upstream and should be tried first to decode ``value``.
:param str errors: optional `errors` flag to pass to the unicode
built-in to indicate how illegal character values should be
@@ -60,24 +63,24 @@ def ustr(value, hint_encoding='utf-8', errors='strict'):
# cases faster (isinstance/issubclass are significantly slower)
ttype = type(value)
- if ttype is unicode:
+ if ttype is text_type:
return value
# special short-circuit for str, as we still needs to support
# str subclasses such as `odoo.tools.unquote`
- if ttype is str or issubclass(ttype, str):
+ if ttype is bytes or issubclass(ttype, bytes):
# try hint_encoding first, avoids call to get_encoding()
# for the most common case
try:
- return unicode(value, hint_encoding, errors=errors)
+ return value.decode(hint_encoding, errors=errors)
except Exception:
pass
# rare: no luck with hint_encoding, attempt other ones
for ln in get_encodings(hint_encoding):
try:
- return unicode(value, ln, errors=errors)
+ return value.decode(ln, errors=errors)
except Exception:
pass
@@ -86,7 +89,7 @@ def ustr(value, hint_encoding='utf-8', errors='strict'):
# fallback for non-string values
try:
- return unicode(value)
+ return text_type(value)
except Exception:
raise UnicodeError('unable to convert %r' % (value,))
@@ -95,6 +98,6 @@ def exception_to_unicode(e):
if hasattr(e, 'args'):
return "\n".join((ustr(a) for a in e.args))
try:
- return unicode(e)
+ return text_type(e)
except Exception:
return u"Unknown message"
diff --git a/odoo/models.py b/odoo/models.py
index dd55b799f0a..d8b693322c7 100644
--- a/odoo/models.py
+++ b/odoo/models.py
@@ -173,7 +173,7 @@ class NewId(object):
return False
__nonzero__ = __bool__
-IdType = pycompat.integer_types + (str, unicode, NewId)
+IdType = pycompat.integer_types + pycompat.string_types + (NewId,)
# maximum number of prefetched records
@@ -184,6 +184,7 @@ LOG_ACCESS_COLUMNS = ['create_uid', 'create_date', 'write_uid', 'write_date']
MAGIC_COLUMNS = ['id'] + LOG_ACCESS_COLUMNS
+@pycompat.implements_to_string
class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
""" Base class for Odoo models.
@@ -405,7 +406,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
# determine inherited models
parents = cls._inherit
- parents = [parents] if isinstance(parents, basestring) else (parents or [])
+ parents = [parents] if isinstance(parents, pycompat.string_types) else (parents or [])
# determine the model's name
name = cls._name or (len(parents) == 1 and parents[0]) or cls.__name__
@@ -769,7 +770,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
# avoid broken transaction) and keep going
cr.execute('ROLLBACK TO SAVEPOINT model_load_save')
except Exception as e:
- message = (_('Unknown error during import:') + ' %s: %s' % (type(e), unicode(e)))
+ message = (_(u'Unknown error during import:') + u' %s: %s' % (type(e), e))
moreinfo = _('Resolve other errors first')
messages.append(dict(info, type='error', message=message, moreinfo=moreinfo))
# Failed for some reason, perhaps due to invalid data supplied,
@@ -886,7 +887,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
# processing of response, but injects human readable in message
exc_vals = dict(base, record=record, field=field_names[field])
record = dict(base, type=type, record=record, field=field,
- message=unicode(exception.args[0]) % exc_vals)
+ message=pycompat.text_type(exception.args[0]) % exc_vals)
if len(exception.args) > 1 and exception.args[1]:
record.update(exception.args[1])
log(record)
@@ -1714,7 +1715,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
value = False if value is None else value
gb = groupby_dict.get(key)
if gb and gb['type'] in ('date', 'datetime') and value:
- if isinstance(value, basestring):
+ if isinstance(value, pycompat.string_types):
dt_format = DEFAULT_SERVER_DATETIME_FORMAT if gb['type'] == 'datetime' else DEFAULT_SERVER_DATE_FORMAT
value = datetime.datetime.strptime(value, dt_format)
if gb['tz_convert']:
@@ -1821,7 +1822,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
"""
result = self._read_group_raw(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
- groupby = [groupby] if isinstance(groupby, basestring) else list(OrderedSet(groupby))
+ groupby = [groupby] if isinstance(groupby, pycompat.string_types) else list(OrderedSet(groupby))
dt = [
f for f in groupby
if self._fields[f.split(':')[0]].type in ('date', 'datetime')
@@ -1845,7 +1846,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
query = self._where_calc(domain)
fields = fields or [f.name for f in pycompat.values(self._fields) if f.store]
- groupby = [groupby] if isinstance(groupby, basestring) else list(OrderedSet(groupby))
+ groupby = [groupby] if isinstance(groupby, pycompat.string_types) else list(OrderedSet(groupby))
groupby_list = groupby[:1] if lazy else groupby
annotated_groupbys = [self._read_group_process_groupby(gb, query) for gb in groupby_list]
groupby_fields = [g['field'] for g in annotated_groupbys]
@@ -4396,7 +4397,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
"""
if not func:
return self # support for an empty path of fields
- if isinstance(func, basestring):
+ if isinstance(func, pycompat.string_types):
recs = self
for name in func.split('.'):
recs = recs._mapped_func(operator.itemgetter(name))
@@ -4421,7 +4422,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
:param func: a function or a dot-separated sequence of field names
"""
- if isinstance(func, basestring):
+ if isinstance(func, pycompat.string_types):
name = func
func = lambda rec: any(rec.mapped(name))
return self.browse([rec.id for rec in self if func(rec)])
@@ -4438,7 +4439,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
if key is None:
recs = self.search([('id', 'in', self.ids)])
return self.browse(reversed(recs._ids)) if reverse else recs
- if isinstance(key, basestring):
+ if isinstance(key, pycompat.string_types):
key = itemgetter(key)
return self.browse(item.id for item in sorted(self, key=key, reverse=reverse))
@@ -4523,7 +4524,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
"""
if isinstance(item, BaseModel) and self._name == item._name:
return len(item) == 1 and item.id in self._ids
- elif isinstance(item, basestring):
+ elif isinstance(item, pycompat.string_types):
return item in self._fields
else:
raise TypeError("Mixing apples and oranges: %s in %s" % (item, self))
@@ -4616,11 +4617,8 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
def __str__(self):
return "%s%s" % (self._name, getattr(self, '_ids', ""))
-
- def __unicode__(self):
- return unicode(str(self))
-
- __repr__ = __str__
+ def __repr__(self):
+ return str(self)
def __hash__(self):
if hasattr(self, '_ids'):
@@ -4640,7 +4638,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
rs = inst[10:20] # subset of inst
nm = rs['name'] # name of first record in inst
"""
- if isinstance(key, basestring):
+ if isinstance(key, pycompat.string_types):
# important: one must call the field's getter
return self._fields[key].__get__(self, type(self))
elif isinstance(key, slice):
@@ -4983,7 +4981,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
# At the moment, the client does not support updates on a *2many field
# while this one is modified by the user.
- if isinstance(field_name, basestring) and \
+ if isinstance(field_name, pycompat.string_types) and \
self._fields[field_name].type in ('one2many', 'many2many'):
dirty.discard(field_name)
@@ -5008,13 +5006,13 @@ class RecordCache(MutableMapping):
def contains(self, field):
""" Return whether `records[0]` has a value for ``field`` in cache. """
- if isinstance(field, basestring):
+ if isinstance(field, pycompat.string_types):
field = self._recs._fields[field]
return self._recs.id in self._recs.env.cache[field]
def __contains__(self, field):
""" Return whether `records[0]` has a regular value for ``field`` in cache. """
- if isinstance(field, basestring):
+ if isinstance(field, pycompat.string_types):
field = self._recs._fields[field]
dummy = SpecialValue(None)
value = self._recs.env.cache[field].get(self._recs.id, dummy)
@@ -5022,7 +5020,7 @@ class RecordCache(MutableMapping):
def get(self, field, default=None):
""" Return the cached, regular value of ``field`` for `records[0]`, or ``default``. """
- if isinstance(field, basestring):
+ if isinstance(field, pycompat.string_types):
field = self._recs._fields[field]
dummy = SpecialValue(None)
value = self._recs.env.cache[field].get(self._recs.id, dummy)
@@ -5030,14 +5028,14 @@ class RecordCache(MutableMapping):
def __getitem__(self, field):
""" Return the cached value of ``field`` for `records[0]`. """
- if isinstance(field, basestring):
+ if isinstance(field, pycompat.string_types):
field = self._recs._fields[field]
value = self._recs.env.cache[field][self._recs.id]
return value.get() if isinstance(value, SpecialValue) else value
def __setitem__(self, field, value):
""" Assign the cached value of ``field`` for all records in ``records``. """
- if isinstance(field, basestring):
+ if isinstance(field, pycompat.string_types):
field = self._recs._fields[field]
values = dict.fromkeys(self._recs._ids, value)
self._recs.env.cache[field].update(values)
@@ -5056,7 +5054,7 @@ class RecordCache(MutableMapping):
def __delitem__(self, field):
""" Remove the cached value of ``field`` for all ``records``. """
- if isinstance(field, basestring):
+ if isinstance(field, pycompat.string_types):
field = self._recs._fields[field]
field_cache = self._recs.env.cache[field]
for id in self._recs._ids:
diff --git a/odoo/osv/expression.py b/odoo/osv/expression.py
index 8df300a2bd4..c1363c4f06d 100644
--- a/odoo/osv/expression.py
+++ b/odoo/osv/expression.py
@@ -377,7 +377,7 @@ def normalize_leaf(element):
def is_operator(element):
""" Test whether an object is a valid domain operator. """
- return isinstance(element, basestring) and element in DOMAIN_OPERATORS
+ return isinstance(element, pycompat.string_types) and element in DOMAIN_OPERATORS
def is_leaf(element, internal=False):
@@ -398,7 +398,7 @@ def is_leaf(element, internal=False):
return (isinstance(element, tuple) or isinstance(element, list)) \
and len(element) == 3 \
and element[1] in INTERNAL_OPS \
- and ((isinstance(element[0], basestring) and element[0])
+ and ((isinstance(element[0], pycompat.string_types) and element[0])
or tuple(element) in (TRUE_LEAF, FALSE_LEAF))
@@ -697,9 +697,9 @@ class expression(object):
return the list of related ids
"""
names = []
- if isinstance(value, basestring):
+ if isinstance(value, pycompat.string_types):
names = [value]
- elif value and isinstance(value, (tuple, list)) and all(isinstance(item, basestring) for item in value):
+ elif value and isinstance(value, (tuple, list)) and all(isinstance(item, pycompat.string_types) for item in value):
names = value
elif isinstance(value, pycompat.integer_types):
return [value]
@@ -925,7 +925,7 @@ class expression(object):
domain = domain(model)
is_integer_m2o = comodel._fields[field.inverse_name].type == 'integer'
if right is not False:
- if isinstance(right, basestring):
+ if isinstance(right, pycompat.string_types):
op = {'!=': '=', 'not like': 'like', 'not ilike': 'ilike'}.get(operator, operator)
ids2 = [x[0] for x in comodel.name_search(right, domain or [], op, limit=None)]
if ids2:
@@ -993,7 +993,7 @@ class expression(object):
else:
call_null_m2m = True
if right is not False:
- if isinstance(right, basestring):
+ if isinstance(right, pycompat.string_types):
op = {'!=': '=', 'not like': 'like', 'not ilike': 'ilike'}.get(operator, operator)
domain = field.domain
if callable(domain):
@@ -1051,8 +1051,8 @@ class expression(object):
res_ids.append(False) # TODO this should not be appended if False was in 'right'
return left, 'in', res_ids
# resolve string-based m2o criterion into IDs
- if isinstance(right, basestring) or \
- right and isinstance(right, (tuple, list)) and all(isinstance(item, basestring) for item in right):
+ if isinstance(right, pycompat.string_types) or \
+ right and isinstance(right, (tuple, list)) and all(isinstance(item, pycompat.string_types) for item in right):
push(create_substitution_leaf(leaf, _get_expression(comodel, left, right, operator), model))
else:
# right == [] or right == False and all other cases are handled by __leaf_to_sql()
@@ -1263,21 +1263,16 @@ class expression(object):
add_null = False
if need_wildcard:
- if isinstance(right, str):
- str_utf8 = right
- elif isinstance(right, unicode):
- str_utf8 = right.encode('utf-8')
- else:
- str_utf8 = str(right)
- params = '%%%s%%' % str_utf8
- add_null = not str_utf8
+ native_str = pycompat.to_native(right)
+ params = '%%%s%%' % native_str
+ add_null = not native_str
elif left in model:
params = model._fields[left].convert_to_column(right, model)
if add_null:
query = '(%s OR %s."%s" IS NULL)' % (query, table_alias, left)
- if isinstance(params, basestring):
+ if isinstance(params, pycompat.string_types):
params = [params]
return query, params
diff --git a/odoo/osv/orm.py b/odoo/osv/orm.py
index e4be2706c7f..f189638582b 100644
--- a/odoo/osv/orm.py
+++ b/odoo/osv/orm.py
@@ -125,7 +125,7 @@ def setup_modifiers(node, field=None, context=None, in_tree_view=False):
def test_modifiers(what, expected):
modifiers = {}
- if isinstance(what, basestring):
+ if isinstance(what, pycompat.string_types):
node = etree.fromstring(what)
transfer_node_to_modifiers(node, modifiers)
simplify_modifiers(modifiers)
diff --git a/odoo/service/db.py b/odoo/service/db.py
index 861369b5b07..ce9483fedd7 100644
--- a/odoo/service/db.py
+++ b/odoo/service/db.py
@@ -23,6 +23,7 @@ import odoo.sql_db
import odoo.tools
from odoo.sql_db import db_connect
from odoo.release import version_info
+from odoo.tools import pycompat
_logger = logging.getLogger(__name__)
@@ -223,7 +224,7 @@ def exp_restore(db_name, data, copy=False):
return True
def restore_db(db, dump_file, copy=False):
- assert isinstance(db, basestring)
+ assert isinstance(db, pycompat.string_types)
if exp_db_exist(db):
_logger.info('RESTORE DB: %s already exists', db)
raise Exception("Database already exists")
diff --git a/odoo/tools/__init__.py b/odoo/tools/__init__.py
index 13c2fd410b9..2b4eeb50c55 100644
--- a/odoo/tools/__init__.py
+++ b/odoo/tools/__init__.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-import copy
+from . import pycompat
from . import win32
from . import appdirs
from .config import config
diff --git a/odoo/tools/appdirs.py b/odoo/tools/appdirs.py
index 8617dabfa4f..6176e6bce75 100644
--- a/odoo/tools/appdirs.py
+++ b/odoo/tools/appdirs.py
@@ -13,6 +13,7 @@ from __future__ import print_function
# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120
# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html
# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
+from . import pycompat
__version_info__ = (1, 3, 0)
__version__ = '.'.join(str(v) for v in __version_info__)
@@ -21,12 +22,6 @@ __version__ = '.'.join(str(v) for v in __version_info__)
import sys
import os
-PY3 = sys.version_info[0] == 3
-
-if PY3:
- unicode = str
-
-
def user_data_dir(appname=None, appauthor=None, version=None, roaming=False):
r"""Return full path to the user-specific data dir for this application.
@@ -397,7 +392,7 @@ def _get_win_folder_with_pywin32(csidl_name):
# not return unicode strings when there is unicode data in the
# path.
try:
- dir = unicode(dir)
+ dir = pycompat.text_type(dir)
# Downgrade to short path name if have highbit chars. See
# <http://bugs.activestate.com/show_bug.cgi?id=85099>.
diff --git a/odoo/tools/cache.py b/odoo/tools/cache.py
index 2554331ce9b..1226038a21c 100644
--- a/odoo/tools/cache.py
+++ b/odoo/tools/cache.py
@@ -8,7 +8,7 @@ from decorator import decorator
from inspect import formatargspec, getargspec
import logging
-from odoo.tools import pycompat
+from . import pycompat
unsafe_eval = eval
@@ -135,7 +135,7 @@ class ormcache_multi(ormcache):
def determine_key(self):
""" Determine the function that computes a cache key from arguments. """
assert self.skiparg is None, "ormcache_multi() no longer supports skiparg"
- assert isinstance(self.multi, basestring), "ormcache_multi() parameter multi must be an argument name"
+ assert isinstance(self.multi, pycompat.string_types), "ormcache_multi() parameter multi must be an argument name"
super(ormcache_multi, self).determine_key()
diff --git a/odoo/tools/config.py b/odoo/tools/config.py
index 97c0d2f3d09..ee6c03a7e2e 100644
--- a/odoo/tools/config.py
+++ b/odoo/tools/config.py
@@ -5,15 +5,13 @@ try:
import configparser as ConfigParser
except ImportError:
import ConfigParser
-
+
+import logging
import optparse
import os
import sys
import odoo
-import odoo.conf
-import odoo.loglevels as loglevels
-import logging
-import odoo.release as release
+from .. import release, conf, loglevels
from . import appdirs, pycompat
@@ -398,10 +396,10 @@ class configmanager(object):
if getattr(opt, arg):
self.options[arg] = getattr(opt, arg)
# ... or keep, but cast, the config file value.
- elif isinstance(self.options[arg], basestring) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
+ elif isinstance(self.options[arg], pycompat.string_types) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
- if isinstance(self.options['log_handler'], basestring):
+ if isinstance(self.options['log_handler'], pycompat.string_types):
self.options['log_handler'] = self.options['log_handler'].split(',')
self.options['log_handler'].extend(opt.log_handler)
@@ -433,7 +431,7 @@ class configmanager(object):
if getattr(opt, arg) is not None:
self.options[arg] = getattr(opt, arg)
# ... or keep, but cast, the config file value.
- elif isinstance(self.options[arg], basestring) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
+ elif isinstance(self.options[arg], pycompat.string_types) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
self.options['root_path'] = os.path.abspath(os.path.expanduser(os.path.expandvars(os.path.join(os.path.dirname(__file__), '..'))))
@@ -471,9 +469,9 @@ class configmanager(object):
if opt.save:
self.save()
- odoo.conf.addons_paths = self.options['addons_path'].split(',')
+ conf.addons_paths = self.options['addons_path'].split(',')
- odoo.conf.server_wide_modules = [
+ conf.server_wide_modules = [
m.strip() for m in self.options['server_wide_modules'].split(',') if m.strip()
]
@@ -575,7 +573,7 @@ class configmanager(object):
def __setitem__(self, key, value):
self.options[key] = value
- if key in self.options and isinstance(self.options[key], basestring) and \
+ if key in self.options and isinstance(self.options[key], pycompat.string_types) and \
key in self.casts and self.casts[key].type in optparse.Option.TYPE_CHECKER:
self.options[key] = optparse.Option.TYPE_CHECKER[self.casts[key].type](self.casts[key], key, self.options[key])
diff --git a/odoo/tools/convert.py b/odoo/tools/convert.py
index 66e72c5d3ff..7da9f4e4412 100644
--- a/odoo/tools/convert.py
+++ b/odoo/tools/convert.py
@@ -16,7 +16,6 @@ import pytz
from lxml import etree, builder
import odoo
-import odoo.release
from . import assertion_report, pycompat
from .config import config
from .misc import file_open, unquote, ustr, SKIPPED_ELEMENT_TYPES
@@ -683,7 +682,7 @@ form: module.record_id""" % (xml_id,)
f_val = int(f_val)
elif model._fields[f_name].type in ['float', 'monetary']:
f_val = float(f_val)
- elif model._fields[f_name].type == 'boolean' and isinstance(f_val, basestring):
+ elif model._fields[f_name].type == 'boolean' and isinstance(f_val, pycompat.string_types):
f_val = str2bool(f_val)
res[f_name] = f_val
@@ -893,7 +892,7 @@ def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=Fa
if idref is None:
idref={}
- if isinstance(xmlfile, basestring):
+ if isinstance(xmlfile, pycompat.string_types):
xml_filename = xmlfile
else:
xml_filename = xmlfile.name
diff --git a/odoo/tools/misc.py b/odoo/tools/misc.py
index 8d3854aeb6d..b348e983a2e 100644
--- a/odoo/tools/misc.py
+++ b/odoo/tools/misc.py
@@ -465,7 +465,7 @@ def human_size(sz):
if not sz:
return False
units = ('bytes', 'Kb', 'Mb', 'Gb')
- if isinstance(sz,basestring):
+ if isinstance(sz,pycompat.string_types):
sz=len(sz)
s, i = float(sz), 0
while s >= 1024 and i < len(units)-1:
@@ -798,7 +798,7 @@ class mute_logger(object):
def __enter__(self):
for logger in self.loggers:
- assert isinstance(logger, basestring),\
+ assert isinstance(logger, pycompat.string_types),\
"A logger name must be a string, got %s" % type(logger)
logging.getLogger(logger).addFilter(self)
@@ -1073,7 +1073,7 @@ def formatLang(env, value, digits=None, grouping=True, monetary=False, dp=False,
if not digits and digits is not 0:
digits = DEFAULT_DIGITS
- if isinstance(value, (str, unicode)) and not value:
+ if isinstance(value, pycompat.string_types) and not value:
return ''
lang = env.user.company_id.partner_id.lang or 'en_US'
@@ -1108,7 +1108,7 @@ def format_date(env, value, lang_code=False, date_format=False):
return ''
if isinstance(value, datetime.datetime):
value = value.date()
- elif isinstance(value, basestring):
+ elif isinstance(value, pycompat.string_types):
if len(value) < DATE_LENGTH:
return ''
value = value[:DATE_LENGTH]
diff --git a/odoo/tools/pycompat.py b/odoo/tools/pycompat.py
index ff229f0562c..e4111668d24 100644
--- a/odoo/tools/pycompat.py
+++ b/odoo/tools/pycompat.py
@@ -10,6 +10,15 @@ if PY2:
unichr = unichr
text_type = unicode
string_types = (str, unicode)
+ def to_native(source, encoding='utf-8', falsy_empty=False):
+ if not source and falsy_empty:
+ return ''
+
+ if isinstance(source, text_type):
+ return source.encode(encoding)
+
+ return str(source)
+
integer_types = (int, long)
keys = lambda d: iter(d.iterkeys())
@@ -19,6 +28,11 @@ if PY2:
# noinspection PyUnresolvedReferences
from itertools import imap, izip, ifilter
+ def implements_to_string(cls):
+ cls.__unicode__ = cls.__str__
+ cls.__str__ = lambda x: x.__unicode__().encode('utf-8')
+ return cls
+
def implements_iterator(cls):
cls.next = cls.__next__
del cls.__next__
@@ -30,6 +44,15 @@ else:
unichr = chr
text_type = str
string_types = (str,)
+ def to_native(source, encoding='utf-8', falsy_empty=False):
+ if not source and falsy_empty:
+ return ''
+
+ if isinstance(source, bytes):
+ return source.decode(encoding)
+
+ return str(source)
+
integer_types = (int,)
keys = lambda d: iter(d.keys())
@@ -40,6 +63,9 @@ else:
izip = zip
ifilter = filter
+ def implements_to_string(cls):
+ return cls
+
def implements_iterator(cls):
return cls
@@ -47,3 +73,20 @@ else:
if value.__traceback__ != tb:
raise value.with_traceback(tb)
raise value
+
+def to_text(source):
+ """ Generates a text value (an instance of text_type) from an arbitrary
+ source.
+
+ * False and None are converted to empty strings
+ * text is passed through
+ * bytes are decoded as UTF-8
+ * rest is textified via the current version's relevant data model method
+ """
+ if source is None or source is False:
+ return u''
+
+ if isinstance(source, bytes):
+ return source.decode('utf-8')
+
+ return text_type(source)
diff --git a/odoo/tools/safe_eval.py b/odoo/tools/safe_eval.py
index de767fb7453..fec89fac087 100644
--- a/odoo/tools/safe_eval.py
+++ b/odoo/tools/safe_eval.py
@@ -26,6 +26,7 @@ import werkzeug
from . import pycompat
from .misc import ustr
+from . import pycompat
import odoo
@@ -267,8 +268,9 @@ _BUILTINS = {
'True': True,
'False': False,
'None': None,
+ 'bytes': bytes,
'str': str,
- 'unicode': unicode,
+ 'unicode': pycompat.text_type,
'bool': bool,
'int': int,
'float': float,
diff --git a/odoo/tools/test_reports.py b/odoo/tools/test_reports.py
index cad44431c1d..6a6401eb896 100644
--- a/odoo/tools/test_reports.py
+++ b/odoo/tools/test_reports.py
@@ -7,15 +7,14 @@
through the code of yaml tests.
"""
-import odoo
-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
import tempfile
+from subprocess import Popen, PIPE
+
+from .. import api
+from . import pycompat, ustr, config
+from .safe_eval import safe_eval
_logger = logging.getLogger(__name__)
_test_logger = logging.getLogger('odoo.tests')
@@ -30,7 +29,7 @@ def try_report(cr, uid, rname, ids, data=None, context=None, our_module=None, re
context = {}
_test_logger.info(" - Trying %s.create(%r)", rname, ids)
- env = odoo.api.Environment(cr, uid, context)
+ env = api.Environment(cr, uid, context)
report_id = env['ir.actions.report'].search([('report_name', '=', rname)], limit=1)
if not report_id:
@@ -41,12 +40,12 @@ def try_report(cr, uid, rname, ids, data=None, context=None, our_module=None, re
if not res_data:
raise ValueError("Report %s produced an empty result!" % rname)
- if tools.config['test_report_directory']:
- open(os.path.join(tools.config['test_report_directory'], rname+ '.'+res_format), 'wb+').write(res_data)
+ if config['test_report_directory']:
+ open(os.path.join(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':
- if res_data[:5] != '%PDF-':
+ if res_data[:5] != b'%PDF-':
raise ValueError("Report %s produced a non-pdf header, %r" % (rname, res_data[:10]))
res_text = False
try:
@@ -56,7 +55,7 @@ def try_report(cr, uid, rname, ids, data=None, context=None, our_module=None, re
proc = Popen(['pdftotext', '-enc', 'UTF-8', '-nopgbrk', rfname, '-'], shell=False, stdout=PIPE)
stdout, stderr = proc.communicate()
- res_text = tools.ustr(stdout)
+ res_text = ustr(stdout)
os.unlink(rfname)
except Exception:
_logger.debug("Unable to parse PDF report: install pdftotext to perform automated tests.")
@@ -92,14 +91,14 @@ def try_report_action(cr, uid, action_id, active_model=None, active_ids=None,
Eg. 'OK' or 'fa-print'
:param our_module: the name of the calling module (string), like 'account'
"""
- if not our_module and isinstance(action_id, basestring):
+ if not our_module and isinstance(action_id, pycompat.string_types):
if '.' in action_id:
our_module = action_id.split('.', 1)[0]
context = dict(context or {})
# TODO context fill-up
- env = odoo.api.Environment(cr, uid, context)
+ env = api.Environment(cr, uid, context)
def log_test(msg, *args):
_test_logger.info(" - " + msg, *args)
@@ -113,7 +112,7 @@ def try_report_action(cr, uid, action_id, active_model=None, active_ids=None,
if not wiz_buttons:
wiz_buttons = []
- if isinstance(action_id, basestring):
+ if isinstance(action_id, pycompat.string_types):
if '.' in action_id:
_, act_xmlid = action_id.split('.', 1)
else:
@@ -138,7 +137,7 @@ def try_report_action(cr, uid, action_id, active_model=None, active_ids=None,
if datas.get('id',False):
context.update( {'active_id': datas.get('id',False), 'active_ids': datas.get('ids',[]), 'active_model': datas.get('model',False)})
context1 = action.get('context', {})
- if isinstance(context1, basestring):
+ if isinstance(context1, pycompat.string_types):
context1 = safe_eval(context1, dict(context))
context.update(context1)
env = env(context=context)
diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py
index 49aeb2864a8..dbf995a5161 100644
--- a/odoo/tools/translate.py
+++ b/odoo/tools/translate.py
@@ -127,9 +127,7 @@ class UNIX_LINE_TERMINATOR(csv.excel):
csv.register_dialect("UNIX", UNIX_LINE_TERMINATOR)
-#
-# Helper functions for translating fields
-#
+# FIXME: holy shit this whole thing needs to be cleaned up hard it's a mess
def encode(s):
if isinstance(s, unicode):
return s.encode('utf8')
@@ -280,18 +278,18 @@ def translate_xml_node(node, callback, parse, serialize):
def parse_xml(text):
- return etree.fromstring(encode(text))
+ return etree.fromstring(text)
def serialize_xml(node):
- return etree.tostring(node, method='xml', encoding='utf8').decode('utf8')
+ return etree.tostring(node, method='xml', encoding='unicode')
_HTML_PARSER = etree.HTMLParser(encoding='utf8')
def parse_html(text):
- return html.fragment_fromstring(encode(text), parser=_HTML_PARSER)
+ return html.fragment_fromstring(text, parser=_HTML_PARSER)
def serialize_html(node):
- return etree.tostring(node, method='html', encoding='utf8').decode('utf8')
+ return etree.tostring(node, method='html', encoding='unicode')
def xml_translate(callback, value):
@@ -307,7 +305,7 @@ def xml_translate(callback, value):
return serialize_xml(result)
except etree.ParseError:
# fallback for translated terms: use an HTML parser and wrap the term
- root = parse_html("<div>%s</div>" % value)
+ root = parse_html(u"<div>%s</div>" % value)
result = translate_xml_node(root, callback, parse_xml, serialize_xml)
# remove tags <div> and </div> from result
return serialize_xml(result)[5:-6]
@@ -643,14 +641,13 @@ class PoFile(object):
# only strings in python code are python formated
self.buffer.write(u"#, python-format\n")
- if not isinstance(trad, unicode):
- trad = unicode(trad, 'utf8')
- if not isinstance(source, unicode):
- source = unicode(source, 'utf8')
-
- msg = u"msgid %s\n" \
- u"msgstr %s\n\n" \
- % (quote(source), quote(trad))
+ msg = (
+ u"msgid %s\n"
+ u"msgstr %s\n\n"
+ ) % (
+ quote(pycompat.text_type(source)),
+ quote(pycompat.text_type(trad))
+ )
self.buffer.write(msg)
@@ -1109,7 +1106,7 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
return
if isinstance(res_id, pycompat.integer_types) or \
- (isinstance(res_id, basestring) and res_id.isdigit()):
+ (isinstance(res_id, pycompat.string_types) and res_id.isdigit()):
dic['res_id'] = int(res_id)
if module_name:
dic['module'] = module_name
diff --git a/odoo/tools/yaml_import.py b/odoo/tools/yaml_import.py
index 4cb2e7ea184..6cf4cc357eb 100644
--- a/odoo/tools/yaml_import.py
+++ b/odoo/tools/yaml_import.py
@@ -12,8 +12,7 @@ from lxml import etree
import yaml
import odoo
-from . import assertion_report
-from . import yaml_tag, pycompat
+from . import assertion_report, pycompat, yaml_tag
from .config import config
from .misc import file_open, DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
from odoo import SUPERUSER_ID
@@ -25,9 +24,6 @@ from .safe_eval import safe_eval
_logger = logging.getLogger(__name__)
-def encode(s):
- return s.encode('utf8') if isinstance(s, unicode) else s
-
class YamlImportException(Exception):
pass
@@ -41,7 +37,7 @@ def _is_yaml_mapping(node, tag_constructor):
return value
def is_comment(node):
- return isinstance(node, basestring)
+ return isinstance(node, pycompat.string_types)
def is_assert(node):
return isinstance(node, yaml_tag.Assert) \
@@ -87,7 +83,7 @@ def is_ir_set(node):
return _is_yaml_mapping(node, yaml_tag.IrSet)
def is_string(node):
- return isinstance(node, basestring)
+ return isinstance(node, pycompat.string_types)
class RecordDictWrapper(dict):
"""
@@ -278,7 +274,7 @@ class YamlInterpreter(object):
record, fields = next(pycompat.items(node))
model = self.env[record.model]
view_id = record.view
- if view_id and (view_id is not True) and isinstance(view_id, basestring):
+ if view_id and (view_id is not True) and isinstance(view_id, pycompat.string_types):
if '.' not in view_id:
view_id = self.module + '.' + view_id
view_id = self.env.ref(view_id).id
@@ -358,7 +354,10 @@ class YamlInterpreter(object):
traverse(child, elems)
elems = OrderedDict()
- traverse(etree.fromstring(encode(view['arch'])), elems)
+ arch = view['arch']
+ if isinstance(arch, pycompat.text_type):
+ arch = arch.encode('utf-8')
+ traverse(etree.fromstring(arch), elems)
return elems
def is_readonly(field_elem):
@@ -584,7 +583,7 @@ class YamlInterpreter(object):
assert python.model or python.id, "!python node must have attribute `model` or `id`"
if python.id is None:
record = self.env[python.model]
- elif isinstance(python.id, basestring):
+ elif isinstance(python.id, pycompat.string_types):
record = self.get_record(python.id)
else:
record = self.env[python.model].browse(python.id)