[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
This commit is contained in:
Xavier Morel
2017-08-20 23:25:54 +02:00
committed by Olivier Dony
parent 3824b5dcc1
commit 7dd062f835
67 changed files with 359 additions and 337 deletions
+2 -1
View File
@@ -6,6 +6,7 @@ from operator import itemgetter
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import pycompat
FIELD_STATES = [('clear', 'Clear'), ('anonymized', 'Anonymized'), ('not_existing', 'Not Existing'), ('new', 'New')]
ANONYMIZATION_HISTORY_STATE = [('started', 'Started'), ('done', 'Done'), ('in_exception', 'Exception occured')]
@@ -13,7 +14,7 @@ ANONYMIZATION_DIRECTION = [('clear -> anonymized', 'clear -> anonymized'), ('ano
def group(lst, cols):
if isinstance(cols, basestring):
if isinstance(cols, pycompat.string_types):
cols = [cols]
return dict((k, [v for v in itr]) for k, itr in groupby(sorted(lst, key=itemgetter(*cols)), itemgetter(*cols)))
+3 -3
View File
@@ -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,
}]
+2 -1
View File
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
from odoo.tools import pycompat
class Board(models.AbstractModel):
@@ -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
+4 -2
View File
@@ -4,6 +4,8 @@ from odoo import exceptions
from odoo.http import Controller, request, route
from odoo.addons.bus.models.bus import dispatch
from odoo.tools import pycompat
class BusController(Controller):
""" Examples:
@@ -14,7 +16,7 @@ class BusController(Controller):
@route('/longpolling/send', type="json", auth="public")
def send(self, channel, message):
if not isinstance(channel, basestring):
if not isinstance(channel, pycompat.string_types):
raise Exception("bus.Bus only string channels are allowed.")
return request.env['bus.bus'].sendone(channel, message)
@@ -33,7 +35,7 @@ class BusController(Controller):
options = {}
if not dispatch:
raise Exception("bus.Bus unavailable")
if [c for c in channels if not isinstance(c, basestring)]:
if [c for c in channels if not isinstance(c, pycompat.string_types)]:
raise Exception("bus.Bus only string channels are allowed.")
if request.registry.in_test_mode():
raise exceptions.UserError("bus.Bus not available in test mode")
+9 -8
View File
@@ -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]
+4 -2
View File
@@ -5,6 +5,8 @@ from odoo import api, models
from odoo.addons.calendar.models.calendar import get_real_ids
from odoo.tools import pycompat
class Attachment(models.Model):
@@ -16,13 +18,13 @@ class Attachment(models.Model):
args = list(args)
if any([leaf for leaf in args if leaf[0] == "res_model" and leaf[2] == 'calendar.event']):
for index in range(len(args)):
if args[index][0] == "res_id" and isinstance(args[index][2], basestring):
if args[index][0] == "res_id" and isinstance(args[index][2], pycompat.string_types):
args[index] = (args[index][0], args[index][1], get_real_ids(args[index][2]))
return super(Attachment, self).search(args, offset=offset, limit=limit, order=order, count=count)
@api.multi
def write(self, vals):
""" When posting an attachment (new or not), convert the virtual ids in real ids. """
if isinstance(vals.get('res_id'), basestring):
if isinstance(vals.get('res_id'), pycompat.string_types):
vals['res_id'] = get_real_ids(vals.get('res_id'))
return super(Attachment, self).write(vals)
+3 -1
View File
@@ -5,6 +5,8 @@ from odoo import api, models
from odoo.addons.calendar.models.calendar import get_real_ids
from odoo.tools import pycompat
class Message(models.Model):
@@ -16,7 +18,7 @@ class Message(models.Model):
args = list(args)
for index in range(len(args)):
if args[index][0] == "res_id":
if isinstance(args[index][2], basestring):
if isinstance(args[index][2], pycompat.string_types):
args[index] = (args[index][0], args[index][1], get_real_ids(args[index][2]))
elif isinstance(args[index][2], list):
args[index] = (args[index][0], args[index][1], [get_real_ids(x) for x in args[index][2]])
+1 -1
View File
@@ -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(',')
-13
View File
@@ -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:
+3 -1
View File
@@ -12,6 +12,8 @@ from hashlib import md5
from PIL import Image
from xml.etree import ElementTree as ET
from odoo.tools import pycompat
try:
import jcconv
except ImportError:
@@ -27,7 +29,7 @@ from .exceptions import *
def utfstr(stuff):
""" converts stuff to string and does without failing if stuff is a utf8 string """
if isinstance(stuff,basestring):
if isinstance(stuff,pycompat.string_types):
return stuff
else:
return str(stuff)
+12 -11
View File
@@ -6,6 +6,7 @@ from xml.etree import ElementTree as ET
from collections import namedtuple
from odoo import api, exceptions, fields, models, _
from odoo.tools.pycompat import text_type
INTRASTAT_XMLNS = 'http://www.onegate.eu/2010-01-01'
@@ -264,18 +265,18 @@ class XmlDeclaration(models.TransientModel):
continue
numlgn += 1
item = ET.SubElement(datas, 'Item')
self._set_Dim(item, 'EXSEQCODE', unicode(numlgn))
self._set_Dim(item, 'EXTRF', unicode(linekey.EXTRF))
self._set_Dim(item, 'EXCNT', unicode(linekey.EXCNT))
self._set_Dim(item, 'EXTTA', unicode(linekey.EXTTA))
self._set_Dim(item, 'EXREG', unicode(linekey.EXREG))
self._set_Dim(item, 'EXTGO', unicode(linekey.EXGO))
self._set_Dim(item, 'EXSEQCODE', text_type(numlgn))
self._set_Dim(item, 'EXTRF', text_type(linekey.EXTRF))
self._set_Dim(item, 'EXCNT', text_type(linekey.EXCNT))
self._set_Dim(item, 'EXTTA', text_type(linekey.EXTTA))
self._set_Dim(item, 'EXREG', text_type(linekey.EXREG))
self._set_Dim(item, 'EXTGO', text_type(linekey.EXGO))
if extendedmode:
self._set_Dim(item, 'EXTPC', unicode(linekey.EXTPC))
self._set_Dim(item, 'EXDELTRM', unicode(linekey.EXDELTRM))
self._set_Dim(item, 'EXTXVAL', unicode(round(amounts[0], 0)).replace(".", ","))
self._set_Dim(item, 'EXWEIGHT', unicode(round(amounts[1], 0)).replace(".", ","))
self._set_Dim(item, 'EXUNITS', unicode(round(amounts[2], 0)).replace(".", ","))
self._set_Dim(item, 'EXTPC', text_type(linekey.EXTPC))
self._set_Dim(item, 'EXDELTRM', text_type(linekey.EXDELTRM))
self._set_Dim(item, 'EXTXVAL', text_type(round(amounts[0], 0)).replace(".", ","))
self._set_Dim(item, 'EXWEIGHT', text_type(round(amounts[1], 0)).replace(".", ","))
self._set_Dim(item, 'EXUNITS', text_type(round(amounts[2], 0)).replace(".", ","))
if numlgn == 0:
#no datas
+4 -5
View File
@@ -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):
+2 -2
View File
@@ -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)
+2 -1
View File
@@ -3,6 +3,7 @@
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.tools import pycompat
class IrModel(models.Model):
@@ -41,7 +42,7 @@ class IrModel(models.Model):
model_class = super(IrModel, self)._instanciate(model_data)
if model_data.get('is_mail_thread') and model_class._name != 'mail.thread':
parents = model_class._inherit or []
parents = [parents] if isinstance(parents, basestring) else parents
parents = [parents] if isinstance(parents, pycompat.string_types) else parents
model_class._inherit = parents + ['mail.thread']
return model_class
+15 -12
View File
@@ -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
@@ -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)
@@ -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]])
)
+1 -1
View File
@@ -158,7 +158,7 @@ class ProductProduct(models.Model):
quantity = self._context.get('quantity', 1.0)
# Support context pricelists specified as display_name or ID for compatibility
if isinstance(pricelist_id_or_name, basestring):
if isinstance(pricelist_id_or_name, pycompat.string_types):
pricelist_name_search = self.env['product.pricelist'].name_search(pricelist_id_or_name, operator='=', limit=1)
if pricelist_name_search:
pricelist = self.env['product.pricelist'].browse([pricelist_name_search[0][0]])
+1 -1
View File
@@ -171,7 +171,7 @@ class ProductTemplate(models.Model):
quantity = self._context.get('quantity', 1.0)
# Support context pricelists specified as display_name or ID for compatibility
if isinstance(pricelist_id_or_name, basestring):
if isinstance(pricelist_id_or_name, pycompat.string_types):
pricelist_data = self.env['product.pricelist'].name_search(pricelist_id_or_name, operator='=', limit=1)
if pricelist_data:
pricelist = self.env['product.pricelist'].browse(pricelist_data[0][0])
+2 -2
View File
@@ -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'])]
+1 -1
View File
@@ -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:
@@ -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,
+2 -1
View File
@@ -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:
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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);
+11 -4
View File
@@ -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>.+)$')
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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'
+3 -6
View File
@@ -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']:
+3 -1
View File
@@ -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)
+6 -4
View File
@@ -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)
+5 -5
View File
@@ -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
+2 -7
View File
@@ -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
+1 -1
View File
@@ -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
+13 -14
View File
@@ -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'))
+1 -1
View File
@@ -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', '')
+26 -29
View File
@@ -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,
+1 -1
View File
@@ -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):
+5 -5
View File
@@ -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
+3 -3
View File
@@ -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)
+1 -1
View File
@@ -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':
+3 -3
View File
@@ -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):
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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)
+4 -3
View File
@@ -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:
+2 -2
View File
@@ -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})
@@ -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()
+17 -28
View File
@@ -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)
)
+9 -9
View File
@@ -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
))
+11 -8
View File
@@ -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"
+22 -24
View File
@@ -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:
+12 -17
View File
@@ -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
+1 -1
View File
@@ -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)
+2 -1
View File
@@ -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")
+1 -1
View File
@@ -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
+2 -7
View File
@@ -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>.
+2 -2
View File
@@ -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()
+9 -11
View File
@@ -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])
+2 -3
View File
@@ -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
+4 -4
View File
@@ -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]
+43
View File
@@ -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)
+3 -1
View File
@@ -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,
+14 -15
View File
@@ -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)
+14 -17
View File
@@ -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
+9 -10
View File
@@ -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)