diff options
| author | Xavier Morel <xmo@odoo.com> | 2017-05-02 12:07:30 +0200 |
|---|---|---|
| committer | Olivier Dony <odo@openerp.com> | 2017-08-20 23:25:54 +0200 |
| commit | 7dd062f83552fa8ff96224c29d60bafc001d73d3 (patch) | |
| tree | d507e27e9d5daa91981e04b5aa81a802a9854a8d | |
| parent | 3824b5dcc13678d3985ad45ac21dcf9ac9202c20 (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
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'&', ord('<'): u'<', ord('"'): u'"', @@ -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) |
