diff options
| author | xmo-odoo <xmo@odoo.com> | 2017-05-10 09:39:55 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-05-10 09:39:55 +0200 |
| commit | fffaf735f58f1ac57781c095eb552de6d1fcdc47 (patch) | |
| tree | 88ab0ab65430d871862a836524e0ecc890d1674d /odoo/addons | |
| parent | 10eee18a047d35305dc46282e610e88936d81ffc (diff) | |
[FIX] P3: list -> iterable builtins (#16811)
In Python 3:
* various builtins and dict methods were changed to return
view/iterable objects rather than lists
* and the separate Python 2 view/iterable builtins and methods were
removed altogether
This is problematic when using these items as list (which the happens
repeatedly in Odoo), but more viciously when iterating *multiple times*
over them (which also happens, which I've messed up multiple times while
writing this, and which is a pain to debug even when you've just created
the issue).
Convert all code using these to semantics-matching cross-version
helper functions to get the LCD behaviour between P2 and P3, and
forbid the builtins via lint.
issue #8530
Diffstat (limited to 'odoo/addons')
35 files changed, 243 insertions, 195 deletions
diff --git a/odoo/addons/base/ir/ir_actions.py b/odoo/addons/base/ir/ir_actions.py index 918e5d90880..4979c898e08 100644 --- a/odoo/addons/base/ir/ir_actions.py +++ b/odoo/addons/base/ir/ir_actions.py @@ -5,6 +5,7 @@ import odoo from odoo import api, fields, models, tools, SUPERUSER_ID, _ from odoo.exceptions import MissingError, UserError, ValidationError, AccessError from odoo.tools.safe_eval import safe_eval, test_python_expr +from odoo.tools import pycompat from odoo.http import request import datetime @@ -649,17 +650,19 @@ class IrActionsTodo(models.Model): """ user_groups = self.env.user.groups_id - def groups_match(todo): - """ Checks if the todo's groups match those of the current user """ - return not todo.groups_id or bool(todo.groups_id & user_groups) - - done = filter(groups_match, self.browse(self.search([('state', '!=', 'open')]))) - total = filter(groups_match, self.browse(self.search([]))) - + done_count = self.search_count([ + ('state', '!=', open), + '|', ('groups_id', '=', False), + ('groups_id', 'in', user_groups.ids), + ]) + total_count = self.search_count([ + '|', ('groups_id', '=', False), + ('groups_id', 'in', user_groups.ids), + ]) return { - 'done': len(done), - 'total': len(total), - 'todo': len(total) - len(done), + 'done': done_count, + 'total': total_count, + 'todo': total_count - done_count, } @@ -688,7 +691,7 @@ class IrActionsActClient(models.Model): @api.depends('params_store') def _compute_params(self): self_bin = self.with_context(bin_size=False, bin_size_params_store=False) - for record, record_bin in zip(self, self_bin): + for record, record_bin in pycompat.izip(self, self_bin): record.params = record_bin.params_store and safe_eval(record_bin.params_store, {'uid': self._uid}) def _inverse_params(self): diff --git a/odoo/addons/base/ir/ir_attachment.py b/odoo/addons/base/ir/ir_attachment.py index b6bd9a08ee7..9d19d076438 100644 --- a/odoo/addons/base/ir/ir_attachment.py +++ b/odoo/addons/base/ir/ir_attachment.py @@ -11,7 +11,7 @@ from collections import defaultdict from odoo import api, fields, models, tools, SUPERUSER_ID, _ from odoo.exceptions import AccessError -from odoo.tools import config, human_size, ustr, html_escape +from odoo.tools import config, human_size, ustr, html_escape, pycompat from odoo.tools.mimetypes import guess_mimetype _logger = logging.getLogger(__name__) @@ -167,7 +167,7 @@ class IrAttachment(models.Model): # remove garbage files, and clean up checklist removed = 0 - for fname, filepath in checklist.iteritems(): + for fname, filepath in pycompat.items(checklist): if fname not in whitelist: try: os.unlink(self._full_path(fname)) @@ -318,7 +318,7 @@ class IrAttachment(models.Model): model_ids[values['res_model']].add(values['res_id']) # check access rights on the records - for res_model, res_ids in model_ids.iteritems(): + for res_model, res_ids in pycompat.items(model_ids): # ignore attachments that are not attached to a resource anymore # when checking access rights (resource was deleted but attachment # was not) @@ -374,12 +374,12 @@ class IrAttachment(models.Model): # To avoid multiple queries for each attachment found, checks are # performed in batch as much as possible. - for res_model, targets in model_attachments.iteritems(): + for res_model, targets in pycompat.items(model_attachments): if res_model not in self.env: continue if not self.env[res_model].check_access_rights('read', False): # remove all corresponding attachment ids - ids.difference_update(itertools.chain(*targets.itervalues())) + ids.difference_update(itertools.chain(*pycompat.values(targets))) continue # filter ids according to what access rules permit target_ids = list(targets) diff --git a/odoo/addons/base/ir/ir_config_parameter.py b/odoo/addons/base/ir/ir_config_parameter.py index cbb44ca6457..8e4ea20b5a5 100644 --- a/odoo/addons/base/ir/ir_config_parameter.py +++ b/odoo/addons/base/ir/ir_config_parameter.py @@ -8,7 +8,7 @@ import uuid import logging from odoo import api, fields, models -from odoo.tools import config, ormcache, mute_logger +from odoo.tools import config, ormcache, mute_logger, pycompat _logger = logging.getLogger(__name__) @@ -42,7 +42,7 @@ class IrConfigParameter(models.Model): Initializes the parameters listed in _default_parameters. It overrides existing parameters if force is ``True``. """ - for key, func in _default_parameters.iteritems(): + for key, func in pycompat.items(_default_parameters): # force=True skips search and always performs the 'if' body (because ids=False) params = self.sudo().search([('key', '=', key)]) if force or not params: diff --git a/odoo/addons/base/ir/ir_fields.py b/odoo/addons/base/ir/ir_fields.py index 26c351d6fa8..204d4599425 100644 --- a/odoo/addons/base/ir/ir_fields.py +++ b/odoo/addons/base/ir/ir_fields.py @@ -8,13 +8,13 @@ import psycopg2 import pytz from odoo import api, fields, models, _ -from odoo.tools import ustr +from odoo.tools import ustr, pycompat REFERENCING_FIELDS = {None, 'id', '.id'} def only_ref_fields(record): - return {k: v for k, v in record.iteritems() if k in REFERENCING_FIELDS} + return {k: v for k, v in pycompat.items(record) if k in REFERENCING_FIELDS} def exclude_ref_fields(record): - return {k: v for k, v in record.iteritems() if k not in REFERENCING_FIELDS} + return {k: v for k, v in pycompat.items(record) if k not in REFERENCING_FIELDS} CREATE = lambda values: (0, False, values) UPDATE = lambda id, values: (1, id, values) @@ -43,9 +43,9 @@ class IrFieldsConverter(models.AbstractModel): if isinstance(error_params, basestring): error_params = sanitize(error_params) elif isinstance(error_params, dict): - error_params = {k: sanitize(v) for k, v in error_params.iteritems()} + error_params = {k: sanitize(v) for k, v in pycompat.items(error_params)} elif isinstance(error_params, tuple): - error_params = tuple(map(sanitize, error_params)) + error_params = tuple(sanitize(v) for v in error_params) return error_type(error_msg % error_params, error_args) @api.model @@ -64,12 +64,12 @@ class IrFieldsConverter(models.AbstractModel): converters = { name: self.to_field(model, field, fromtype) - for name, field in model._fields.iteritems() + for name, field in pycompat.items(model._fields) } def fn(record, log): converted = {} - for field, value in record.iteritems(): + for field, value in pycompat.items(record): if field in REFERENCING_FIELDS: continue if not value: @@ -369,7 +369,7 @@ class IrFieldsConverter(models.AbstractModel): :rtype: str, list """ # Can import by name_get, external id or database id - fieldset = set(record.iterkeys()) + fieldset = set(record) if fieldset - REFERENCING_FIELDS: raise ValueError( _(u"Can not create Many-To-One records indirectly, import the field separately")) diff --git a/odoo/addons/base/ir/ir_http.py b/odoo/addons/base/ir/ir_http.py index 3d9c72aec60..aa23531055e 100644 --- a/odoo/addons/base/ir/ir_http.py +++ b/odoo/addons/base/ir/ir_http.py @@ -21,6 +21,7 @@ import odoo from odoo import api, http, models, tools, SUPERUSER_ID from odoo.exceptions import AccessDenied, AccessError from odoo.http import request, STATIC_CACHE, content_disposition +from odoo.tools import pycompat from odoo.tools.mimetypes import guess_mimetype from odoo.modules.module import get_resource_path, get_module_path @@ -54,7 +55,7 @@ class ModelsConverter(werkzeug.routing.BaseConverter): def to_python(self, value): env = api.Environment(request.cr, UID_PLACEHOLDER, request.context) - return env[self.model].browse(map(int, value.split(','))) + return env[self.model].browse(int(v) for v in value.split(',')) def to_url(self, value): return ",".join(value.ids) @@ -202,7 +203,7 @@ class IrHttp(models.AbstractModel): @classmethod def _postprocess_args(cls, arguments, rule): """ post process arg to set uid on browse records """ - for name, arg in arguments.items(): + for name, arg in list(pycompat.items(arguments)): if isinstance(arg, models.BaseModel) and arg._uid is UID_PLACEHOLDER: arguments[name] = arg.sudo(request.uid) if not arg.exists(): diff --git a/odoo/addons/base/ir/ir_mail_server.py b/odoo/addons/base/ir/ir_mail_server.py index 22f78da6c18..737e9806d68 100644 --- a/odoo/addons/base/ir/ir_mail_server.py +++ b/odoo/addons/base/ir/ir_mail_server.py @@ -13,9 +13,11 @@ import re import smtplib import threading +import itertools + from odoo import api, fields, models, tools, _ from odoo.exceptions import except_orm, UserError -from odoo.tools import html2text, ustr +from odoo.tools import html2text, ustr, pycompat _logger = logging.getLogger(__name__) _test_logger = logging.getLogger('odoo.tests') @@ -110,7 +112,7 @@ def extract_rfc2822_addresses(text): if not text: return [] candidates = address_pattern.findall(ustr(text).encode('utf-8')) - return filter(try_coerce_ascii, candidates) + return [c for c in candidates if try_coerce_ascii(c)] def encode_rfc2822_address_header(header_text): @@ -127,7 +129,7 @@ def encode_rfc2822_address_header(header_text): return formataddr((name, email)) addresses = getaddresses([ustr(header_text).encode('utf-8')]) - return COMMASPACE.join(map(encode_addr, addresses)) + return COMMASPACE.join(encode_addr(a) for a in addresses) class IrMailServer(models.Model): @@ -333,7 +335,7 @@ class IrMailServer(models.Model): msg['Bcc'] = encode_rfc2822_address_header(COMMASPACE.join(email_bcc)) msg['Date'] = formatdate() # Custom headers may override normal headers or provide additional ones - for key, value in headers.iteritems(): + for key, value in pycompat.items(headers): msg[ustr(key).encode('utf-8')] = encode_header(value) if subtype == 'html' and not body_alternative and html2text: @@ -442,7 +444,12 @@ class IrMailServer(models.Model): email_cc = message['Cc'] email_bcc = message['Bcc'] - smtp_to_list = filter(None, tools.flatten(map(extract_rfc2822_addresses, [email_to, email_cc, email_bcc]))) + smtp_to_list = [ + address + for base in [email_to, email_cc, email_bcc] + for address in extract_rfc2822_addresses(base) + if address + ] assert smtp_to_list, self.NO_VALID_RECIPIENT x_forge_to = message['X-Forge-To'] diff --git a/odoo/addons/base/ir/ir_model.py b/odoo/addons/base/ir/ir_model.py index ad72694d410..9272a790fbc 100644 --- a/odoo/addons/base/ir/ir_model.py +++ b/odoo/addons/base/ir/ir_model.py @@ -45,7 +45,7 @@ def query_insert(cr, table, values): query = INSERT_QUERY.format( table=table, cols=",".join(values), - vals=",".join(map("%({0})s".format, values)), + vals=",".join("%({0})s".format(v) for v in values), ) cr.execute(query, values) @@ -53,8 +53,8 @@ def query_update(cr, table, values, selectors): setters = set(values) - set(selectors) query = UPDATE_QUERY.format( table=table, - assignment=",".join(map("{0}=%({0})s".format, setters)), - condition=" AND ".join(map("{0}=%({0})s".format, selectors)), + assignment=",".join("{0}=%({0})s".format(s) for s in setters), + condition=" AND ".join("{0}=%({0})s".format(s) for s in selectors), ) cr.execute(query, values) @@ -188,7 +188,7 @@ class IrModel(models.Model): @api.multi def write(self, vals): if '__last_update' in self._context: - self = self.with_context({k: v for k, v in self._context.iteritems() if k != '__last_update'}) + self = self.with_context({k: v for k, v in pycompat.items(self._context) if k != '__last_update'}) if 'model' in vals and any(rec.model != vals['model'] for rec in self): raise UserError(_('Field "Model" cannot be modified on models.')) if 'state' in vals and any(rec.state != vals['state'] for rec in self): @@ -559,8 +559,8 @@ class IrModelFields(models.Model): except Exception: raise UserError("\n".join([ _("Cannot rename/delete fields that are still present in views:"), - _("Fields:") + " " + ", ".join(map(str, fields)), - _("View:") + " " + view.name, + _("Fields: %s") % ", ".join(str(f) for f in fields), + _("View: %s") % view.name, ])) finally: # the registry has been modified, restore it @@ -760,7 +760,7 @@ class IrModelFields(models.Model): fields_data[field.name] = dict(params, id=record.id) return record - diff = {key for key, val in params.items() if field_data[key] != val} + diff = {key for key, val in pycompat.items(params) if field_data[key] != val} if diff: cr = self.env.cr # update the entry in this table @@ -778,7 +778,7 @@ class IrModelFields(models.Model): def _reflect_model(self, model): """ Reflect the given model's fields. """ self.clear_caches() - for field in model._fields.itervalues(): + for field in pycompat.values(model._fields): self._reflect_field(field) if not self.pool._init: @@ -1228,7 +1228,7 @@ class IrModelData(models.Model): @api.depends('module', 'name') def _compute_complete_name(self): for res in self: - res.complete_name = ".".join(filter(None, [res.module, res.name])) + res.complete_name = ".".join(n for n in [res.module, res.name] if n) @api.depends('model', 'res_id') def _compute_reference(self): @@ -1259,7 +1259,7 @@ class IrModelData(models.Model): model_id_name[xid.model][xid.res_id] = None # fill in model_id_name with name_get() of corresponding records - for model, id_name in model_id_name.iteritems(): + for model, id_name in pycompat.items(model_id_name): try: ng = self.env[model].browse(id_name).name_get() id_name.update(ng) @@ -1356,7 +1356,7 @@ class IrModelData(models.Model): record = self.get_object(module, xml_id) if record: self.loads[(module, xml_id)] = (model, record.id) - for parent_model, parent_field in self.env[model]._inherits.iteritems(): + for parent_model, parent_field in pycompat.items(self.env[model]._inherits): parent = record[parent_field] parent_xid = '%s_%s' % (xml_id, parent_model.replace('.', '_')) self.loads[(module, parent_xid)] = (parent_model, parent.id) @@ -1411,7 +1411,7 @@ class IrModelData(models.Model): elif record: record.write(values) if xml_id: - for parent_model, parent_field in record._inherits.iteritems(): + for parent_model, parent_field in pycompat.items(record._inherits): self.sudo().create({ 'name': xml_id + '_' + parent_model.replace('.', '_'), 'model': parent_model, @@ -1430,7 +1430,7 @@ class IrModelData(models.Model): elif mode == 'init' or (mode == 'update' and xml_id): existing_parents = set() # {parent_model, ...} if xml_id: - for parent_model, parent_field in record._inherits.iteritems(): + for parent_model, parent_field in pycompat.items(record._inherits): xid = self.sudo().search([ ('module', '=', module), ('name', '=', xml_id + '_' + parent_model.replace('.', '_')), @@ -1450,7 +1450,7 @@ class IrModelData(models.Model): inherit_models = [record] while inherit_models: current_model = inherit_models.pop() - for parent_model_name, parent_field in current_model._inherits.iteritems(): + for parent_model_name, parent_field in pycompat.items(current_model._inherits): inherit_models.append(self.env[parent_model_name]) if parent_model_name in existing_parents: continue @@ -1476,7 +1476,7 @@ class IrModelData(models.Model): if xml_id and record: self.loads[(module, xml_id)] = (model, record.id) - for parent_model, parent_field in record._inherits.iteritems(): + for parent_model, parent_field in pycompat.items(record._inherits): parent_xml_id = xml_id + '_' + parent_model.replace('.', '_') self.loads[(module, parent_xml_id)] = (parent_model, record[parent_field].id) diff --git a/odoo/addons/base/ir/ir_qweb/assetsbundle.py b/odoo/addons/base/ir/ir_qweb/assetsbundle.py index 5bc2dec8a9b..19902e78d1b 100644 --- a/odoo/addons/base/ir/ir_qweb/assetsbundle.py +++ b/odoo/addons/base/ir/ir_qweb/assetsbundle.py @@ -13,7 +13,7 @@ from odoo.http import request from odoo.modules.module import get_resource_path import psycopg2 import werkzeug -from odoo.tools import func, misc +from odoo.tools import func, misc, pycompat import logging _logger = logging.getLogger(__name__) @@ -308,7 +308,7 @@ class AssetsBundle(object): outdated = False assets = dict((asset.html_url, asset) for asset in self.stylesheets if isinstance(asset, atype)) if assets: - assets_domain = [('url', 'in', assets.keys())] + assets_domain = [('url', 'in', list(assets))] attachments = self.env['ir.attachment'].sudo().search(assets_domain) for attachment in attachments: asset = assets[attachment.url] @@ -320,7 +320,7 @@ class AssetsBundle(object): if not asset._content and attachment.file_size > 0: asset._content = None # file missing, force recompile - if any(asset._content is None for asset in assets.itervalues()): + if any(asset._content is None for asset in pycompat.values(assets)): outdated = True if outdated: @@ -452,7 +452,7 @@ class WebAsset(object): def stat(self): if not (self.inline or self._filename or self._ir_attach): - path = filter(None, self.url.split('/')) + path = (segment for segment in self.url.split('/') if segment) self._filename = get_resource_path(*path) if self._filename: return diff --git a/odoo/addons/base/ir/ir_qweb/ir_qweb.py b/odoo/addons/base/ir/ir_qweb/ir_qweb.py index 68c4bf048d7..5548fcd47d8 100644 --- a/odoo/addons/base/ir/ir_qweb/ir_qweb.py +++ b/odoo/addons/base/ir/ir_qweb/ir_qweb.py @@ -75,7 +75,7 @@ class IrQWeb(models.AbstractModel, QWeb): # apply ormcache_context decorator unless in dev mode... @tools.conditional( 'xml' not in tools.config['dev_mode'], - tools.ormcache('id_or_xml_id', 'tuple(map(options.get, self._get_template_cache_keys()))'), + tools.ormcache('id_or_xml_id', 'tuple(options.get(k) for k in self._get_template_cache_keys())'), ) def compile(self, id_or_xml_id, options): return super(IrQWeb, self).compile(id_or_xml_id, options=options) @@ -173,7 +173,7 @@ class IrQWeb(models.AbstractModel, QWeb): if field_options and 'monetary' in field_options: try: options = "{'widget': 'monetary'" - for k, v in json.loads(field_options).iteritems(): + for k, v in pycompat.items(json.loads(field_options)): if k in ('display_currency', 'from_currency'): options = "%s, '%s': %s" % (options, k, v) else: @@ -238,12 +238,12 @@ class IrQWeb(models.AbstractModel, QWeb): atype = 'text/less' if atype not in ('text/less', 'text/sass'): atype = 'text/css' - path = filter(None, href.split('/')) + path = [segment for segment in href.split('/') if segment] filename = get_resource_path(*path) if path else None files.append({'atype': atype, 'url': href, 'filename': filename, 'content': el.text, 'media': media}) elif el.tag == 'script': atype = 'text/javascript' - path = filter(None, src.split('/')) + path = [segment for segment in src.split('/') if segment] filename = get_resource_path(*path) if path else None files.append({'atype': atype, 'url': src, 'filename': filename, 'content': el.text, 'media': media}) else: diff --git a/odoo/addons/base/ir/ir_qweb/qweb.py b/odoo/addons/base/ir/ir_qweb/qweb.py index 14a0a54fbd9..06aba8ccdb4 100644 --- a/odoo/addons/base/ir/ir_qweb/qweb.py +++ b/odoo/addons/base/ir/ir_qweb/qweb.py @@ -6,14 +6,15 @@ import traceback from collections import OrderedDict, Sized, Mapping, defaultdict from functools import reduce -from itertools import chain, izip, tee, count +from itertools import tee, count from textwrap import dedent +import itertools from lxml import etree, html import werkzeug from werkzeug.utils import escape as _escape -from odoo.tools import pycompat +from odoo.tools import pycompat, freehash try: import builtins @@ -82,7 +83,7 @@ class Contextifier(ast.NodeTransformer): return ast.copy_location(ast.Lambda( args=ast.arguments( args=args.args, - defaults=map(self.visit, args.defaults), + defaults=[self.visit(default) for default in args.defaults], vararg=args.vararg, kwarg=args.kwarg, ), @@ -109,7 +110,7 @@ class Contextifier(ast.NodeTransformer): for field, value in ast.iter_fields(node): # map transformation of comprehensions if isinstance(value, list): - setattr(newnode, field, map(transformer.visit, value)) + setattr(newnode, field, [transformer.visit(v) for v in value]) else: # set transformation of key/value/expr fields setattr(newnode, field, transformer.visit(value)) return newnode @@ -168,14 +169,14 @@ def foreach_iterator(base_ctx, enum, name): if not enum: return if isinstance(enum, int): - enum = pycompat.range(enum) + enum = range(enum) size = None if isinstance(enum, Sized): ctx["%s_size" % name] = size = len(enum) if isinstance(enum, Mapping): - enum = enum.iteritems() + enum = pycompat.items(enum) else: - enum = izip(*tee(enum)) + enum = pycompat.izip(*tee(enum)) value_key = '%s_value' % name index_key = '%s_index' % name first_key = '%s_first' % name @@ -201,7 +202,7 @@ def foreach_iterator(base_ctx, enum, name): yield ctx # copy changed items back into source context (?) # FIXME: maybe values could provide a ChainMap-style clone? - for k in base_ctx.keys(): + for k in list(base_ctx): base_ctx[k] = ctx[k] _FORMAT_REGEX = re.compile( @@ -226,7 +227,7 @@ class frozendict(dict): def update(self, *args, **kwargs): raise NotImplementedError("'update' not supported on frozendict") def __hash__(self): - return hash(frozenset((key, freehash(val)) for key, val in self.iteritems())) + return hash(frozenset((key, freehash(val)) for key, val in pycompat.items(self))) #################################### @@ -510,6 +511,7 @@ class QWeb(object): """ 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 """)) @@ -665,7 +667,7 @@ class QWeb(object): # all directives have been compiled, there should be none left if any(att.startswith('t-') for att in el.attrib): - raise "Unknown directive on %s" % ("', '".join(directives), etree.tostring(el)) + raise NameError("Unknown directive on %s" % etree.tostring(el)) return [] def _values_var(self, varname, ctx): @@ -726,7 +728,7 @@ class QWeb(object): attrib = {} # If `el` introduced new namespaces, write them as attribute by using the # `attrib` dict. - for ns_prefix, ns_definition in set(el.nsmap.items()) - set(options['nsmap'].items()): + for ns_prefix, ns_definition in set(pycompat.items(el.nsmap)) - set(pycompat.items(options['nsmap'])): if ns_prefix is None: attrib['xmlns'] = ns_definition else: @@ -735,8 +737,9 @@ class QWeb(object): # Etree will also remove the ns prefixes indirection in the attributes. As we only have # the namespace definition, we'll use an nsmap where the keys are the definitions and # the values the prefixes in order to get back the right prefix and restore it. - nsprefixmap = {v: k for k, v in options['nsmap'].items() + el.nsmap.items()} - for key, value in el.attrib.items(): + ns = itertools.chain(pycompat.items(options['nsmap']), pycompat.items(el.nsmap)) + nsprefixmap = {v: k for k, v in ns} + for key, value in pycompat.items(el.attrib): attrib_qname = etree.QName(key) if attrib_qname.namespace: attrib['%s:%s' % (nsprefixmap[attrib_qname.namespace], attrib_qname.localname)] = value @@ -754,7 +757,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 attrib.iteritems()])) + tag = u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in pycompat.items(attrib)])) if unqualified_el_tag in self._void_elements: return [self._append(ast.Str(tag + '/>'))] + content else: @@ -766,10 +769,10 @@ class QWeb(object): # Etree will also remove the ns prefixes indirection in the attributes. As we only have # the namespace definition, we'll use an nsmap where the keys are the definitions and # the values the prefixes in order to get back the right prefix and restore it. - nsprefixmap = {v: k for k, v in options['nsmap'].items() + el.nsmap.items()} + nsprefixmap = {v: k for k, v in itertools.chain(pycompat.items(options['nsmap']), pycompat.items(el.nsmap))} nodes = [] - for key, value in el.attrib.iteritems(): + for key, value in pycompat.items(el.attrib): if not key.startswith('t-'): attrib_qname = etree.QName(key) if attrib_qname.namespace: @@ -784,7 +787,7 @@ class QWeb(object): We do not support namespaced dynamic attributes. """ nodes = [] - for name, value in el.attrib.iteritems(): + for name, value in pycompat.items(el.attrib): if name.startswith('t-attf-'): nodes.append((name[7:], self._compile_format(value))) elif name.startswith('t-att-'): @@ -810,7 +813,7 @@ class QWeb(object): def _compile_all_attributes(self, el, options, attr_already_created=False): """ Compile the attributes of the given elements into a list of AST nodes. """ body = [] - if any(name.startswith('t-att') or not name.startswith('t-') for name, value in el.attrib.iteritems()): + if any(name.startswith('t-att') or not name.startswith('t-') for name, value in pycompat.items(el.attrib)): if not attr_already_created: attr_already_created = True body.append( @@ -851,7 +854,7 @@ class QWeb(object): ))) if attr_already_created: - # for name, value in t_attrs.iteritems(): + # for name, value in pycompat.items(t_attrs): # if value or isinstance(value, basestring)): # append(u' ') # append(name) @@ -862,11 +865,11 @@ class QWeb(object): target=ast.Tuple(elts=[ast.Name(id='name', ctx=ast.Store()), ast.Name(id='value', ctx=ast.Store())], ctx=ast.Store()), iter=ast.Call( func=ast.Attribute( - value=ast.Name(id='t_attrs', ctx=ast.Load()), - attr='iteritems', + value=ast.Name(id='pycompat', ctx=ast.Load()), + attr='items', ctx=ast.Load() ), - args=[], keywords=[], + args=[ast.Name(id='t_attrs', ctx=ast.Load())], keywords=[], starargs=None, kwargs=None ), body=[ast.If( @@ -923,7 +926,7 @@ class QWeb(object): # If `el` introduced new namespaces, write them as attribute by using the # `extra_attrib` dict. - for ns_prefix, ns_definition in set(el.nsmap.items()) - set(options['nsmap'].items()): + for ns_prefix, ns_definition in set(pycompat.items(el.nsmap)) - set(pycompat.items(options['nsmap'])): if ns_prefix is None: extra_attrib['xmlns'] = ns_definition else: @@ -932,7 +935,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 extra_attrib.iteritems()]))))] + 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.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'/>'))) @@ -1054,7 +1057,7 @@ class QWeb(object): def _compile_directive_if(self, el, options): orelse = [] next_el = el.getnext() - if next_el is not None and {'t-else', 't-elif'} & set(next_el.attrib.keys()): + if next_el is not None and {'t-else', 't-elif'} & set(next_el.attrib): if el.tail and not el.tail.isspace(): raise ValueError("Unexpected non-whitespace characters between t-if and t-else directives") el.tail = None @@ -1437,7 +1440,7 @@ class QWeb(object): # make the nsmap an ast dict keys = [] values = [] - for key, value in options['nsmap'].items(): + for key, value in pycompat.items(options['nsmap']): if isinstance(key, basestring): keys.append(ast.Str(s=key)) elif key is None: diff --git a/odoo/addons/base/ir/ir_sequence.py b/odoo/addons/base/ir/ir_sequence.py index a46f5e95eca..58961e921ba 100644 --- a/odoo/addons/base/ir/ir_sequence.py +++ b/odoo/addons/base/ir/ir_sequence.py @@ -6,6 +6,7 @@ import pytz from odoo import api, fields, models, _ from odoo.exceptions import UserError +from odoo.tools import pycompat _logger = logging.getLogger(__name__) @@ -178,7 +179,7 @@ class IrSequence(models.Model): 'weekday': '%w', 'h24': '%H', 'h12': '%I', 'min': '%M', 'sec': '%S' } res = {} - for key, format in sequences.iteritems(): + for key, format in pycompat.items(sequences): res[key] = effective_date.strftime(format) res['range_' + key] = range_date.strftime(format) res['current_' + key] = now.strftime(format) diff --git a/odoo/addons/base/ir/ir_translation.py b/odoo/addons/base/ir/ir_translation.py index 7d73f40d65b..219ebcc471d 100644 --- a/odoo/addons/base/ir/ir_translation.py +++ b/odoo/addons/base/ir/ir_translation.py @@ -128,7 +128,7 @@ class IrTranslationImport(object): env = api.Environment(cr, SUPERUSER_ID, {}) src_relevant_fields = [] for model in env: - for field_name, field in env[model]._fields.items(): + for field_name, field in pycompat.items(env[model]._fields): if hasattr(field, 'translate') and callable(field.translate): src_relevant_fields.append("%s,%s" % (model, field_name)) @@ -530,7 +530,7 @@ class IrTranslation(models.Model): # check for read/write access on translated field records fmode = 'read' if mode == 'read' else 'write' - for mname, ids in model_ids.iteritems(): + for mname, ids in pycompat.items(model_ids): records = self.env[mname].browse(ids) records.check_access_rights(fmode) records.check_field_access_rights(fmode, model_fields[mname]) @@ -640,7 +640,7 @@ class IrTranslation(models.Model): return ['&', ('res_id', '=', rec.id), ('name', '=', name)] # insert missing translations, and extend domain for related fields - for name, fld in record._fields.items(): + for name, fld in pycompat.items(record._fields): if not fld.translate: continue diff --git a/odoo/addons/base/ir/ir_ui_view.py b/odoo/addons/base/ir/ir_ui_view.py index c557d15ee6b..0a709985ff6 100644 --- a/odoo/addons/base/ir/ir_ui_view.py +++ b/odoo/addons/base/ir/ir_ui_view.py @@ -8,6 +8,8 @@ import logging import os import re import time + +import itertools from dateutil.relativedelta import relativedelta from operator import itemgetter @@ -49,7 +51,7 @@ def keep_query(*keep_params, **additional_params): if not keep_params and not additional_params: keep_params = ('*',) params = additional_params.copy() - qs_keys = request.httprequest.args.keys() + qs_keys = list(request.httprequest.args) for keep_param in keep_params: for param in fnmatch.filter(qs_keys, keep_param): if param not in additional_params and param in qs_keys: @@ -251,11 +253,11 @@ actual arch. @api.depends('arch') def _compute_arch_base(self): # 'arch_base' is the same as 'arch' without translation - for view, view_wo_lang in zip(self, self.with_context(lang=None)): + for view, view_wo_lang in pycompat.izip(self, self.with_context(lang=None)): view.arch_base = view_wo_lang.arch def _inverse_arch_base(self): - for view, view_wo_lang in zip(self, self.with_context(lang=None)): + for view, view_wo_lang in pycompat.izip(self, self.with_context(lang=None)): view_wo_lang.arch = view.arch_base @api.depends('write_date') @@ -458,7 +460,7 @@ actual arch. # not required. The root cause is the INNER JOIN # used to implement it. views = self.search(conditions + [('model_ids.module', 'in', tuple(self.pool._init_modules))]) - views = self.search(conditions + [('id', 'in', list(self._context.get('check_view_ids') or (0,)) + map(int, views))]) + views = self.search(conditions + [('id', 'in', list(self._context.get('check_view_ids') or (0,)) + views.ids)]) else: views = self.search(conditions) @@ -581,10 +583,16 @@ actual arch. separator = child.get('separator', ',') if separator == ' ': separator = None # squash spaces - to_add = filter(bool, map(str.strip, child.get('add', '').split(separator))) - to_remove = map(str.strip, child.get('remove', '').split(separator)) - values = map(str.strip, node.get(attribute, '').split(separator)) - value = (separator or ' ').join(filter(lambda s: s not in to_remove, values) + to_add) + to_add = ( + s for s in (s.strip() for s in child.get('add', '').split(separator)) + if s + ) + to_remove = {s.strip() for s in child.get('remove', '').split(separator)} + values = (s.strip() for s in node.get(attribute, '').split(separator)) + value = (separator or ' ').join(itertools.chain( + (v for v in values if v not in to_remove), + to_add + )) if value: node.set(attribute, value) elif attribute in node.attrib: @@ -834,7 +842,7 @@ actual arch. collect(arch, self.env[model_name]) - for field, nodes in field_nodes.iteritems(): + for field, nodes in pycompat.items(field_nodes): # if field should trigger an onchange, add on_change="1" on the # nodes referring to field model = self.env[field.model_name] @@ -900,7 +908,7 @@ actual arch. node.set(action, 'false') arch = etree.tostring(node, encoding="utf-8").replace('\t', '') - for k in fields.keys(): + for k in list(fields): if k not in fields_def: del fields[k] for field in fields_def: @@ -924,7 +932,7 @@ actual arch. @tools.conditional( 'xml' not in config['dev_mode'], tools.ormcache('frozenset(self.env.user.groups_id.ids)', 'view_id', - 'tuple(map(self._context.get, self._read_template_keys()))'), + 'tuple(self._context.get(k) for k in self._read_template_keys())'), ) def _read_template(self, view_id): arch = self.browse(view_id).read_combined(['arch'])['arch'] @@ -1091,12 +1099,12 @@ actual arch. Model = self.env[model] Node = self.env[node_obj] - for model_key, model_value in Model._fields.iteritems(): + for model_key, model_value in pycompat.items(Model._fields): if model_value.type == 'one2many': if model_value.comodel_name == node_obj: _Node_Field = model_key _Model_Field = model_value.inverse_name - for node_key, node_value in Node._fields.iteritems(): + for node_key, node_value in pycompat.items(Node._fields): if node_value.type == 'one2many': if node_value.comodel_name == conn_obj: # _Source_Field = "Incoming Arrows" (connected via des_node) @@ -1159,7 +1167,7 @@ actual arch. GROUP BY coalesce(v.inherit_id, v.id)""" self._cr.execute(query, [model]) - rec = self.browse(map(itemgetter(0), self._cr.fetchall())) + rec = self.browse(it[0] for it in self._cr.fetchall()) return rec.with_context({'load_all_views': True})._check_xml() @api.model @@ -1173,7 +1181,7 @@ actual arch. xmlid_filter = "AND md.name IN %s" names = tuple( name - for (xmod, name), (model, res_id) in self.pool.model_data_reference_ids.items() + for (xmod, name), (model, res_id) in pycompat.items(self.pool.model_data_reference_ids) if xmod == module and model == self._name ) if not names: diff --git a/odoo/addons/base/ir/ir_values.py b/odoo/addons/base/ir/ir_values.py index 3672d7811c2..ace2998b2b3 100644 --- a/odoo/addons/base/ir/ir_values.py +++ b/odoo/addons/base/ir/ir_values.py @@ -5,7 +5,7 @@ from ast import literal_eval from odoo import api, fields, models, tools, _ from odoo.exceptions import AccessError, MissingError, ValidationError -from odoo.tools import pickle +from odoo.tools import pickle, pycompat import logging _logger = logging.getLogger(__name__) @@ -315,7 +315,7 @@ class IrValues(models.Model): for row in self._cr.dictfetchall(): value = pickle.loads(row['value'].encode('utf-8')) defaults.setdefault(row['name'], (row['id'], row['name'], value)) - return defaults.values() + return list(pycompat.values(defaults)) # use ormcache: this is called a lot by BaseModel.default_get()! @api.model @@ -426,4 +426,4 @@ class IrValues(models.Model): results[name] = (id, name, action_def) except (AccessError, MissingError): continue - return sorted(results.values()) + return sorted(pycompat.values(results)) diff --git a/odoo/addons/base/module/module.py b/odoo/addons/base/module/module.py index ae36ccd1727..c8a83a01bf1 100644 --- a/odoo/addons/base/module/module.py +++ b/odoo/addons/base/module/module.py @@ -10,6 +10,8 @@ import shutil import tempfile import zipfile +from odoo.tools import pycompat + try: from urllib import parse as urlparse from urllib.request import urlopen @@ -209,9 +211,9 @@ class Module(models.Model): def format_view(v): return '%s%s (%s)' % (v.inherit_id and '* INHERIT ' or '', v.name, v.type) - module.views_by_module = "\n".join(sorted(map(format_view, browse('ir.ui.view')))) - module.reports_by_module = "\n".join(sorted(map(attrgetter('name'), browse('ir.actions.report')))) - module.menus_by_module = "\n".join(sorted(map(attrgetter('complete_name'), browse('ir.ui.menu')))) + module.views_by_module = "\n".join(sorted(format_view(v) for v in browse('ir.ui.view'))) + module.reports_by_module = "\n".join(sorted(r.name for r in browse('ir.actions.report'))) + module.menus_by_module = "\n".join(sorted(m.complete_name for m in browse('ir.ui.menu'))) @api.depends('icon') def _get_icon_image(self): @@ -668,7 +670,7 @@ class Module(models.Model): _logger.debug('Install from url: %r', urls) try: # 1. Download & unzip missing modules - for module_name, url in urls.iteritems(): + for module_name, url in pycompat.items(urls): if not url: continue # nothing to download, local version is already the last one @@ -687,7 +689,7 @@ class Module(models.Model): assert os.path.isdir(os.path.join(tmp, module_name)) # 2a. Copy/Replace module source in addons path - for module_name, url in urls.iteritems(): + for module_name, url in pycompat.items(urls): if module_name == OPENERP or not url: continue # OPENERP is special case, handled below, and no URL means local module module_path = modules.get_module_path(module_name, downloaded=True, display_warning=False) @@ -719,11 +721,11 @@ class Module(models.Model): self.update_list() - with_urls = [module_name for module_name, url in urls.iteritems() if url] + with_urls = [module_name for module_name, url in pycompat.items(urls) if url] downloaded = self.search([('name', 'in', with_urls)]) installed = self.search([('id', 'in', downloaded.ids), ('state', '=', 'installed')]) - to_install = self.search([('name', 'in', urls.keys()), ('state', '=', 'uninstalled')]) + to_install = self.search([('name', 'in', list(urls)), ('state', '=', 'uninstalled')]) post_install_action = to_install.button_immediate_install() if installed or to_install: diff --git a/odoo/addons/base/module/report/ir_module_reference_print.py b/odoo/addons/base/module/report/ir_module_reference_print.py index 1f2dff53243..a933cdd9430 100644 --- a/odoo/addons/base/module/report/ir_module_reference_print.py +++ b/odoo/addons/base/module/report/ir_module_reference_print.py @@ -2,6 +2,8 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, models +from odoo.tools import pycompat + class IrModelReferenceReport(models.AbstractModel): _name = 'report.base.report_irmodulereference' @@ -21,7 +23,7 @@ class IrModelReferenceReport(models.AbstractModel): if data: res_ids = data.mapped('res_id') fnames = self.env['ir.model.fields'].browse(res_ids).mapped('name') - return sorted(self.env[model].fields_get(fnames).iteritems()) + return sorted(pycompat.items(self.env[model].fields_get(fnames))) return [] @api.model diff --git a/odoo/addons/base/res/ir_property.py b/odoo/addons/base/res/ir_property.py index 8e1b7eda860..ec086fb6c61 100644 --- a/odoo/addons/base/res/ir_property.py +++ b/odoo/addons/base/res/ir_property.py @@ -217,7 +217,7 @@ class Property(models.Model): prop.write({'value': value}) # create new properties for records that do not have one yet - for ref, id in refs.iteritems(): + for ref, id in pycompat.items(refs): value = clean(values[id]) if value != default_value: self.create({ @@ -248,13 +248,13 @@ class Property(models.Model): elif operator in ('!=', '<=', '<', '>', '>='): value = makeref(value) elif operator in ('in', 'not in'): - value = map(makeref, value) + value = [makeref(v) for v in value] elif operator in ('=like', '=ilike', 'like', 'not like', 'ilike', 'not ilike'): # most probably inefficient... but correct target = self.env[comodel] target_names = target.name_search(value, operator=operator, limit=None) - target_ids = map(itemgetter(0), target_names) - operator, value = 'in', map(makeref, target_ids) + target_ids = [n[0] for n in target_names] + operator, value = 'in', [makeref(v) for v in target_ids] elif field.type in ('integer', 'float'): # No record is created in ir.property if the field's type is float or integer with a value # equal to 0. Then to match with the records that are linked to a property field equal to 0, diff --git a/odoo/addons/base/res/res_company.py b/odoo/addons/base/res/res_company.py index 96e922f5e77..9dfdba96cba 100644 --- a/odoo/addons/base/res/res_company.py +++ b/odoo/addons/base/res/res_company.py @@ -6,6 +6,7 @@ import re from odoo import api, fields, models, tools, _ from odoo.exceptions import ValidationError +from odoo.tools import pycompat class Company(models.Model): @@ -146,7 +147,7 @@ class Company(models.Model): if self.country_id: res['domain']['state_id'] = [('country_id', '=', self.country_id.id)] values = self.on_change_country(self.country_id.id)['value'] - for fname, value in values.iteritems(): + for fname, value in pycompat.items(values): setattr(self, fname, value) return res diff --git a/odoo/addons/base/res/res_config.py b/odoo/addons/base/res/res_config.py index 59764fca1e8..a19c6406dcb 100644 --- a/odoo/addons/base/res/res_config.py +++ b/odoo/addons/base/res/res_config.py @@ -9,7 +9,7 @@ from lxml import etree from odoo import api, models, registry, SUPERUSER_ID, _ from odoo.exceptions import AccessError, RedirectWarning, UserError -from odoo.tools import ustr +from odoo.tools import ustr, pycompat _logger = logging.getLogger(__name__) @@ -257,7 +257,7 @@ class ResConfigInstaller(models.TransientModel, ResConfigModuleInstallationMixin installer :rtype: [str] """ - return map(attrgetter('name'), self._already_installed()) + return [m.name for m in self._already_installed()] def _already_installed(self): """ For each module (boolean fields in a res.config.installer), @@ -267,7 +267,7 @@ class ResConfigInstaller(models.TransientModel, ResConfigModuleInstallationMixin :returns: a list of all installed modules in this installer :rtype: recordset (collection of Record) """ - selectable = [name for name, field in self._fields.iteritems() + selectable = [name for name, field in pycompat.items(self._fields) if field.type == 'boolean'] return self.env['ir.module.module'].search([('name', 'in', selectable), ('state', 'in', ['to install', 'installed', 'to upgrade'])]) @@ -292,7 +292,7 @@ class ResConfigInstaller(models.TransientModel, ResConfigModuleInstallationMixin """ base = set(module_name for installer in self.read() - for module_name, to_install in installer.iteritems() + for module_name, to_install in pycompat.items(installer) if self._fields[module_name].type == 'boolean' and to_install) hooks_results = set() @@ -302,7 +302,7 @@ class ResConfigInstaller(models.TransientModel, ResConfigModuleInstallationMixin hooks_results.update(hook() or set()) additionals = set(module - for requirements, consequences in self._install_if.iteritems() + for requirements, consequences in pycompat.items(self._install_if) if base.issuperset(requirements) for module in consequences) @@ -462,13 +462,13 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) ref = self.env.ref defaults, groups, modules, others = [], [], [], [] - for name, field in self._fields.iteritems(): + for name, field in pycompat.items(self._fields): if name.startswith('default_') and hasattr(field, 'default_model'): defaults.append((name, field.default_model, name[8:])) elif name.startswith('group_') and field.type in ('boolean', 'selection') and \ hasattr(field, 'implied_group'): field_group_xmlids = getattr(field, 'group', 'base.group_user').split(',') - field_groups = Groups.concat(*map(ref, field_group_xmlids)) + field_groups = Groups.concat(*(ref(it) for it in field_group_xmlids)) groups.append((name, field_groups, ref(field.implied_group))) elif name.startswith('module_') and field.type in ('boolean', 'selection'): module = IrModule.sudo().search([('name', '=', name[7:])], limit=1) diff --git a/odoo/addons/base/res/res_lang.py b/odoo/addons/base/res/res_lang.py index 19a5b9be339..f4e2d7a1578 100644 --- a/odoo/addons/base/res/res_lang.py +++ b/odoo/addons/base/res/res_lang.py @@ -8,6 +8,7 @@ import re from operator import itemgetter from odoo import api, fields, models, tools, _ +from odoo.tools import pycompat from odoo.tools.safe_eval import safe_eval from odoo.exceptions import UserError, ValidationError @@ -22,7 +23,7 @@ class Lang(models.Model): _description = "Languages" _order = "active desc,name" - _disallowed_datetime_patterns = tools.DATETIME_FORMATS_MAP.keys() + _disallowed_datetime_patterns = list(tools.DATETIME_FORMATS_MAP) _disallowed_datetime_patterns.remove('%y') # this one is in fact allowed, just not good practice name = fields.Char(required=True) @@ -125,7 +126,7 @@ class Lang(models.Model): # For some locales, nl_langinfo returns a D_FMT/T_FMT that contains # unsupported '%-' patterns, e.g. for cs_CZ format = format.replace('%-', '%') - for pattern, replacement in tools.DATETIME_FORMATS_MAP.iteritems(): + for pattern, replacement in pycompat.items(tools.DATETIME_FORMATS_MAP): format = format.replace(pattern, replacement) return str(format) @@ -318,5 +319,5 @@ def intersperse(string, counts, separator=''): left, rest, right = intersperse_pat.match(string).groups() def reverse(s): return s[::-1] splits = split(reverse(rest), counts) - res = separator.join(map(reverse, reverse(splits))) + res = separator.join(reverse(s) for s in reverse(splits)) return left + res + right, len(splits) > 0 and len(splits) -1 or 0 diff --git a/odoo/addons/base/res/res_partner.py b/odoo/addons/base/res/res_partner.py index fe758d36de1..17e6507a01e 100644 --- a/odoo/addons/base/res/res_partner.py +++ b/odoo/addons/base/res/res_partner.py @@ -680,7 +680,7 @@ class Partner(models.Model): query += ' limit %s' where_clause_params.append(limit) self.env.cr.execute(query, where_clause_params) - partner_ids = map(lambda x: x[0], self.env.cr.fetchall()) + partner_ids = [row[0] for row in self.env.cr.fetchall()] if partner_ids: return self.browse(partner_ids).name_get() diff --git a/odoo/addons/base/res/res_users.py b/odoo/addons/base/res/res_users.py index ae8863eee4d..106fc32d56f 100644 --- a/odoo/addons/base/res/res_users.py +++ b/odoo/addons/base/res/res_users.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import pytz import datetime +import itertools import logging from collections import defaultdict @@ -13,7 +14,7 @@ from odoo import api, fields, models, tools, SUPERUSER_ID, _ from odoo.exceptions import AccessDenied, AccessError, UserError, ValidationError from odoo.osv import expression from odoo.service.db import check_super -from odoo.tools import partition +from odoo.tools import partition, pycompat _logger = logging.getLogger(__name__) @@ -29,7 +30,7 @@ def name_boolean_group(id): return 'in_group_' + str(id) def name_selection_groups(ids): - return 'sel_groups_' + '_'.join(map(str, ids)) + return 'sel_groups_' + '_'.join(str(it) for it in ids) def is_boolean_group(name): return name.startswith('in_group_') @@ -44,7 +45,7 @@ def get_boolean_group(name): return int(name[9:]) def get_selection_groups(name): - return map(int, name[11:].split('_')) + return [int(v) for v in name[11:].split('_')] def parse_m2m(commands): "return a list of ids corresponding to a many2many value" @@ -92,7 +93,7 @@ class Groups(models.Model): @api.depends('category_id.name', 'name') def _compute_full_name(self): # Important: value must be stored in environment of group, not group1! - for group, group1 in zip(self, self.sudo()): + for group, group1 in pycompat.izip(self, self.sudo()): if group1.category_id: group.full_name = '%s / %s' % (group1.category_id.name, group1.name) else: @@ -111,7 +112,7 @@ class Groups(models.Model): operand = [operand] where = [] for group in operand: - values = filter(bool, group.split('/')) + values = [v for v in group.split('/') if v] group_name = values.pop().strip() category_name = values and '/'.join(values).strip() or group_name group_domain = [('name', operator, lst and [group_name] or group_name)] @@ -299,13 +300,11 @@ class Users(models.Model): canwrite = self.env['ir.model.access'].check('res.users', 'write', False) if not canwrite: - def override_password(vals): - if (vals['id'] != self._uid): + for vals in result: + if vals['id'] != self._uid: for key in USER_PRIVATE_FIELDS: if key in vals: vals[key] = '********' - return vals - result = map(override_password, result) return result @@ -343,7 +342,7 @@ class Users(models.Model): raise UserError(_("You cannot deactivate the user you're currently logged in as.")) if self == self.env.user: - for key in values.keys(): + for key in list(values): if not (key in self.SELF_WRITEABLE_FIELDS or key.startswith('context_')): break else: @@ -624,7 +623,7 @@ class GroupsImplied(models.Model): if values.get('users') or values.get('implied_ids'): # add all implied groups (to all users of each group) for group in self: - vals = {'users': zip(repeat(4), group.with_context(active_test=False).users.ids)} + vals = {'users': list(pycompat.izip(repeat(4), group.with_context(active_test=False).users.ids))} super(GroupsImplied, group.trans_implied_ids).write(vals) return res @@ -766,7 +765,7 @@ class GroupsView(models.Model): # determine sequence order: a group appears after its implied groups order = {g: len(g.trans_implied_ids & gs) for g in gs} # check whether order is total, i.e., sequence orders are distinct - if len(set(order.itervalues())) == len(gs): + if len(set(pycompat.values(order))) == len(gs): return (app, 'selection', gs.sorted(key=order.get)) else: return (app, 'boolean', gs) @@ -780,7 +779,7 @@ class GroupsView(models.Model): others += g # build the result res = [] - for app, gs in sorted(by_app.iteritems(), key=lambda it: it[0].sequence or 0): + for app, gs in sorted(pycompat.items(by_app), key=lambda it: it[0].sequence or 0): res.append(linearize(app, gs)) if others: res.append((self.env['ir.module.category'], 'boolean', others)) @@ -820,7 +819,7 @@ class UsersView(models.Model): add, rem = [], [] values1 = {} - for key, val in values.iteritems(): + for key, val in pycompat.items(values): if is_boolean_group(key): (add if val else rem).append(get_boolean_group(key)) elif is_selection_groups(key): @@ -832,7 +831,10 @@ class UsersView(models.Model): if 'groups_id' not in values and (add or rem): # remove group ids in `rem` and add group ids in `add` - values1['groups_id'] = zip(repeat(3), rem) + zip(repeat(4), add) + values1['groups_id'] = list(itertools.chain( + pycompat.izip(repeat(3), rem), + pycompat.izip(repeat(4), add) + )) return values1 @@ -847,7 +849,7 @@ class UsersView(models.Model): @api.multi def read(self, fields=None, load='_classic_read'): # determine whether reified groups fields are required, and which ones - fields1 = fields or self.fields_get().keys() + fields1 = fields or list(self.fields_get()) group_fields, other_fields = partition(is_reified_group, fields1) # read regular fields (other_fields); add 'groups_id' if necessary diff --git a/odoo/addons/base/tests/test_api.py b/odoo/addons/base/tests/test_api.py index 8270d6086d5..55cf1ff26b5 100644 --- a/odoo/addons/base/tests/test_api.py +++ b/odoo/addons/base/tests/test_api.py @@ -79,11 +79,11 @@ class TestAPI(common.TransactionCase): domain = [('name', 'ilike', 'j')] partners = self.env['res.partner'].search(domain) self.assertTrue(partners) - ids = map(int, partners) + ids = partners.ids # modify those partners, and check that partners has not changed partners.write({'active': False}) - self.assertEqual(ids, map(int, partners)) + self.assertEqual(ids, partners.ids) # redo the search, and check that the result is now empty partners2 = self.env['res.partner'].search(domain) @@ -98,7 +98,7 @@ class TestAPI(common.TransactionCase): self.assertIsRecordset(user.groups_id, 'res.groups') partners = self.env['res.partner'].search([]) - for name, field in partners._fields.iteritems(): + for name, field in pycompat.items(partners._fields): if field.type == 'many2one': for p in partners: self.assertIsRecord(p[name], field.comodel_name) @@ -296,7 +296,7 @@ class TestAPI(common.TransactionCase): self.assertItemsEqual(partners.ids, country_id_cache) # partners' countries are ready for prefetching - country_ids = set(cid for cids in country_id_cache.itervalues() for cid in cids) + country_ids = set(cid for cids in pycompat.values(country_id_cache) for cid in cids) self.assertTrue(len(country_ids) > 1) self.assertItemsEqual(country_ids, partners._prefetch['res.country']) diff --git a/odoo/addons/base/tests/test_expression.py b/odoo/addons/base/tests/test_expression.py index 4dea4797b0c..87099e78754 100644 --- a/odoo/addons/base/tests/test_expression.py +++ b/odoo/addons/base/tests/test_expression.py @@ -5,7 +5,7 @@ import psycopg2 from odoo.models import BaseModel from odoo.tests.common import TransactionCase -from odoo.tools import mute_logger +from odoo.tools import mute_logger, pycompat import odoo.osv.expression as expression @@ -85,10 +85,10 @@ class TestExpression(TransactionCase): 'b ab': [cids['B'], cids['AB']], } pids = {} - for name, cat_ids in partners_config.iteritems(): + for name, cat_ids in pycompat.items(partners_config): pids[name] = partners.create({'name': name, 'category_id': [(6, 0, cat_ids)]}).id - base_domain = [('id', 'in', pids.values())] + base_domain = [('id', 'in', list(pycompat.values(pids)))] def test(op, value, expected): found_ids = partners.search(base_domain + [('category_id', op, value)]).ids diff --git a/odoo/addons/base/tests/test_float.py b/odoo/addons/base/tests/test_float.py index 8c4ee380bd2..725ba48365b 100644 --- a/odoo/addons/base/tests/test_float.py +++ b/odoo/addons/base/tests/test_float.py @@ -98,10 +98,10 @@ class TestFloatPrecision(TransactionCase): precisions = [2, 2, 2, 2, 2, 2, 3, 4] # Note: max precision for double floats is 53 bits of precision or # 17 significant decimal digits - for magnitude in pycompat.range(7): - for frac, exp, prec in zip(fractions, expecteds, precisions): + for magnitude in range(7): + for frac, exp, prec in pycompat.izip(fractions, expecteds, precisions): for sign in [-1,1]: - for x in pycompat.range(0, 10000, 97): + for x in range(0, 10000, 97): n = x * 10 ** magnitude f = sign * (n + frac) f_exp = ('-' if f != 0 and sign == -1 else '') + str(n) + exp diff --git a/odoo/addons/base/tests/test_ir_filters.py b/odoo/addons/base/tests/test_ir_filters.py index d932f16be11..b8de7590ad2 100644 --- a/odoo/addons/base/tests/test_ir_filters.py +++ b/odoo/addons/base/tests/test_ir_filters.py @@ -4,11 +4,12 @@ from odoo import exceptions from odoo.tests.common import TransactionCase, ADMIN_USER_ID -def noid(d): +def noid(seq): """ Removes values that are not relevant for the test comparisons """ - d.pop('id', None) - d.pop('action_id', None) - return d + for d in seq: + d.pop('id', None) + d.pop('action_id', None) + return seq class FiltersCase(TransactionCase): @@ -34,7 +35,7 @@ class TestGetFilters(FiltersCase): filters = self.env['ir.filters'].sudo(self.USER_ID).get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'), dict(name='b', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'), dict(name='c', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'), @@ -52,7 +53,7 @@ class TestGetFilters(FiltersCase): filters = self.env['ir.filters'].sudo(self.USER_ID).get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', is_default=False, user_id=False, domain='[]', context='{}', sort='[]'), dict(name='b', is_default=False, user_id=False, domain='[]', context='{}', sort='[]'), dict(name='c', is_default=False, user_id=False, domain='[]', context='{}', sort='[]'), @@ -69,7 +70,7 @@ class TestGetFilters(FiltersCase): filters = self.env['ir.filters'].sudo(self.USER_ID).get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', is_default=False, user_id=False, domain='[]', context='{}', sort='[]'), dict(name='c', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'), ]) @@ -95,7 +96,7 @@ class TestOwnDefaults(FiltersCase): }) filters = Filters.get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', user_id=self.USER_NG, is_default=True, domain='[]', context='{}', sort='[]') ]) @@ -120,7 +121,7 @@ class TestOwnDefaults(FiltersCase): }) filters = Filters.get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='b', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='c', user_id=self.USER_NG, is_default=True, domain='[]', context='{}', sort='[]'), @@ -146,7 +147,7 @@ class TestOwnDefaults(FiltersCase): }) filters = Filters.get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='b', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='c', user_id=self.USER_NG, is_default=True, domain='[]', context='{}', sort='[]'), @@ -172,7 +173,7 @@ class TestOwnDefaults(FiltersCase): }) filters = Filters.get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', user_id=self.USER_NG, is_default=True, domain='[]', context='{}', sort='[]'), dict(name='b', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'), ]) @@ -204,7 +205,7 @@ class TestGlobalDefaults(FiltersCase): }) filters = Filters.get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', user_id=False, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='b', user_id=False, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='c', user_id=False, is_default=True, domain='[]', context='{}', sort='[]'), @@ -271,7 +272,7 @@ class TestGlobalDefaults(FiltersCase): }) filters = Filters.get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', user_id=False, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='b', user_id=False, is_default=True, domain='[]', context=context_value, sort='[]'), ]) diff --git a/odoo/addons/base/tests/test_orm.py b/odoo/addons/base/tests/test_orm.py index 0da71a84ad5..bf95bee08f7 100644 --- a/odoo/addons/base/tests/test_orm.py +++ b/odoo/addons/base/tests/test_orm.py @@ -141,7 +141,7 @@ class TestORM(TransactionCase): partner_ids_by_year = defaultdict(list) partners = self.env['res.partner'] - for name, date in partners_data.items(): + for name, date in pycompat.items(partners_data): p = partners.create(dict(name=name, date=date)) partner_ids.append(p.id) partner_ids_by_day[date].append(p.id) @@ -297,7 +297,7 @@ class TestO2MSerialization(TransactionCase): def test_CREATE_commands(self): " returns the VALUES dict as-is " values = [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}] - results = self.env['res.partner'].resolve_2many_commands('child_ids', map(CREATE, values)) + results = self.env['res.partner'].resolve_2many_commands('child_ids', [CREATE(v) for v in values]) self.assertEqual(results, values) def test_LINK_TO_command(self): @@ -307,7 +307,7 @@ class TestO2MSerialization(TransactionCase): self.env['res.partner'].create({'name': 'bar'}).id, self.env['res.partner'].create({'name': 'baz'}).id, ] - commands = map(LINK_TO, ids) + commands = [LINK_TO(v) for v in ids] results = self.env['res.partner'].resolve_2many_commands('child_ids', commands, ['name']) self.assertItemsEqual(results, [ @@ -356,7 +356,7 @@ class TestO2MSerialization(TransactionCase): self.env['res.partner'].create({'name': 'bar'}).id, self.env['res.partner'].create({'name': 'baz'}).id, ] - commands = map(DELETE, ids) + commands = [DELETE(v) for v in ids] results = self.env['res.partner'].resolve_2many_commands('child_ids', commands, ['name']) self.assertEqual(results, []) diff --git a/odoo/addons/base/tests/test_qweb.py b/odoo/addons/base/tests/test_qweb.py index dcf41b092d6..cd459fe7669 100644 --- a/odoo/addons/base/tests/test_qweb.py +++ b/odoo/addons/base/tests/test_qweb.py @@ -13,6 +13,7 @@ from itertools import chain from odoo.modules import get_module_resource from odoo.tests.common import TransactionCase from odoo.addons.base.ir.ir_qweb import QWebException +from odoo.tools import pycompat def dedent_and_strip(string): @@ -411,7 +412,7 @@ class TestQWebNS(TransactionCase): 'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2', 'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2' } - self.assertSetEqual(set(expected_ns.items()) - set(result_etree.nsmap.items()), set()) + self.assertSetEqual(set(pycompat.items(expected_ns)) - set(pycompat.items(result_etree.nsmap)), set()) # check that the t-call did its work cac_lines = result_etree.findall('.//cac:line', namespaces={'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2'}) diff --git a/odoo/addons/base/tests/test_search.py b/odoo/addons/base/tests/test_search.py index d52cd9db9c4..97670fdd805 100644 --- a/odoo/addons/base/tests/test_search.py +++ b/odoo/addons/base/tests/test_search.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.tests.common import TransactionCase +from odoo.tools import pycompat class test_search(TransactionCase): @@ -107,7 +108,7 @@ class test_search(TransactionCase): user_ids[u] = Users.create({'name': u, 'login': u}).id cron_ids[u] = Cron.create({'name': u, 'model_id': self.env.ref('base.model_res_partner').id, 'user_id': user_ids[u]}).id - ids = Cron.search([('id', 'in', cron_ids.values())], order='user_id').ids + ids = Cron.search([('id', 'in', list(pycompat.values(cron_ids)))], order='user_id').ids expected_ids = [cron_ids[l] for l in 'ABC'] self.assertEqual(ids, expected_ids) @@ -127,7 +128,7 @@ class test_search(TransactionCase): create('F', parent_id=cat_ids['D']) expected_ids = [cat_ids[x] for x in 'ADEFBC'] - found_ids = Cats.search([('id', 'in', cat_ids.values())]).ids + found_ids = Cats.search([('id', 'in', list(pycompat.values(cat_ids)))]).ids self.assertEqual(found_ids, expected_ids) def test_13_m2o_order_loop_multi(self): diff --git a/odoo/addons/base/tests/test_views.py b/odoo/addons/base/tests/test_views.py index bd462932e5f..40cd922340c 100644 --- a/odoo/addons/base/tests/test_views.py +++ b/odoo/addons/base/tests/test_views.py @@ -638,7 +638,7 @@ class TestViews(ViewCase): kw.setdefault('mode', 'extension' if kw.get('inherit_id') else 'primary') kw.setdefault('active', True) - keys = sorted(kw.keys()) + keys = sorted(kw) fields = ','.join('"%s"' % (k.replace('"', r'\"'),) for k in keys) params = ','.join('%%(%s)s' % (k,) for k in keys) diff --git a/odoo/addons/test_impex/tests/test_export.py b/odoo/addons/test_impex/tests/test_export.py index 226ba9943f6..72a3b2a6f75 100644 --- a/odoo/addons/test_impex/tests/test_export.py +++ b/odoo/addons/test_impex/tests/test_export.py @@ -4,6 +4,7 @@ import itertools from odoo.tests import common +from odoo.tools import pycompat class CreatorCase(common.TransactionCase): @@ -494,9 +495,9 @@ class test_o2m_multiple(CreatorCase): """ fields = ['const', 'child1/value', 'child2/value'] child1 = [(0, False, {'value': v, 'str': 'record%.02d' % index}) - for index, v in zip(itertools.count(), [4, 42, 36, 4, 13])] + for index, v in pycompat.izip(itertools.count(), [4, 42, 36, 4, 13])] child2 = [(0, False, {'value': v, 'str': 'record%.02d' % index}) - for index, v in zip(itertools.count(10), [8, 12, 8, 55, 33, 13])] + for index, v in pycompat.izip(itertools.count(10), [8, 12, 8, 55, 33, 13])] self.assertEqual( self.export(child1=child1, child2=False, fields=fields), diff --git a/odoo/addons/test_limits/models.py b/odoo/addons/test_limits/models.py index 506578a811b..957e3c3d6f9 100644 --- a/odoo/addons/test_limits/models.py +++ b/odoo/addons/test_limits/models.py @@ -5,7 +5,6 @@ import time import sys from odoo import models, api -from odoo.tools import pycompat class m(models.Model): """ This model exposes a few methods that will consume between 'almost no @@ -39,7 +38,7 @@ class m(models.Model): t0 = time.clock() t1 = time.clock() while t1 - t0 < seconds: - for i in pycompat.range(10000000): + for i in range(10000000): x = i * i t1 = time.clock() return True diff --git a/odoo/addons/test_new_api/models.py b/odoo/addons/test_new_api/models.py index c84827d47ce..2f288e89f74 100644 --- a/odoo/addons/test_new_api/models.py +++ b/odoo/addons/test_new_api/models.py @@ -5,6 +5,7 @@ import datetime from odoo import models, fields, api, _ from odoo.exceptions import AccessError, ValidationError +from odoo.tools import pycompat class Category(models.Model): @@ -45,7 +46,7 @@ class Category(models.Model): categories.append(category[0]) categories.append(self) # assign parents following sequence - for parent, child in zip(categories, categories[1:]): + for parent, child in pycompat.izip(categories, categories[1:]): if parent and child: child.parent = parent # assign name of last category, and reassign display_name (to normalize it) @@ -364,4 +365,4 @@ class ComputeRecursive(models.Model): if rec.parent: rec.display_name = rec.parent.display_name + " / " + rec.name else: - rec.display_name = rec.name
\ No newline at end of file + rec.display_name = rec.name diff --git a/odoo/addons/test_new_api/tests/test_one2many.py b/odoo/addons/test_new_api/tests/test_one2many.py index 9ade2c0fe19..e1ad547e95e 100644 --- a/odoo/addons/test_new_api/tests/test_one2many.py +++ b/odoo/addons/test_new_api/tests/test_one2many.py @@ -45,7 +45,7 @@ class One2manyCase(TransactionCase): # Check the lines first self.assertItemsEqual( self.multi.lines.mapped('name'), - map(str, range(10))) + [str(i) for i in range(10)]) # Modify the first line and drop the last one self.multi.lines[0].name = "hello" self.multi.lines = self.multi.lines[:-1] diff --git a/odoo/addons/test_pylint/tests/test_pylint.py b/odoo/addons/test_pylint/tests/test_pylint.py index 1ae544e56a6..f98dcf88b3c 100644 --- a/odoo/addons/test_pylint/tests/test_pylint.py +++ b/odoo/addons/test_pylint/tests/test_pylint.py @@ -30,22 +30,33 @@ class TestPyLint(TransactionCase): 'relative-import', 'deprecated-module', 'import-star-module-level', - # 'bad-python3-import', # TODO: more stuff used in report + + 'bad-builtin', + + 'dict-iter-method', + 'dict-view-method', 'long-suffix', - 'apply-builtin', - 'cmp-builtin', - 'coerce-builtin', - 'execfile-builtin', - 'input-builtin', - 'intern-builtin', - 'long-builtin', - 'raw_input-builtin', - 'reload-builtin', - 'xrange-builtin', + ] + + BAD_FUNCTIONS = [ + 'apply', + 'cmp', + 'coerce', + 'execfile', + 'input', + 'intern', + 'long', + 'raw_input', + 'reload', + 'xrange', + 'long', + 'map', + 'filter', + 'zip', # TODO: enable once report has been removed - # 'file-builtin', - # 'reduce-builtin', + # 'file', + # 'reduce', ] def _skip_test(self, reason): @@ -69,6 +80,8 @@ class TestPyLint(TransactionCase): '--enable=%s' % ','.join(self.ENABLED_CODES), '--reports=n', "--msg-template='{msg} ({msg_id}) at {path}:{line}'", + '--load-plugins=pylint.extensions.bad_builtin', + '--bad-functions=%s' % ','.join(self.BAD_FUNCTIONS), ] try: |
