summaryrefslogtreecommitdiff
path: root/odoo/addons/base/ir
diff options
context:
space:
mode:
authorxmo-odoo <xmo@odoo.com>2017-05-10 09:39:55 +0200
committerGitHub <noreply@github.com>2017-05-10 09:39:55 +0200
commitfffaf735f58f1ac57781c095eb552de6d1fcdc47 (patch)
tree88ab0ab65430d871862a836524e0ecc890d1674d /odoo/addons/base/ir
parent10eee18a047d35305dc46282e610e88936d81ffc (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/base/ir')
-rw-r--r--odoo/addons/base/ir/ir_actions.py25
-rw-r--r--odoo/addons/base/ir/ir_attachment.py10
-rw-r--r--odoo/addons/base/ir/ir_config_parameter.py4
-rw-r--r--odoo/addons/base/ir/ir_fields.py16
-rw-r--r--odoo/addons/base/ir/ir_http.py5
-rw-r--r--odoo/addons/base/ir/ir_mail_server.py17
-rw-r--r--odoo/addons/base/ir/ir_model.py30
-rw-r--r--odoo/addons/base/ir/ir_qweb/assetsbundle.py8
-rw-r--r--odoo/addons/base/ir/ir_qweb/ir_qweb.py8
-rw-r--r--odoo/addons/base/ir/ir_qweb/qweb.py55
-rw-r--r--odoo/addons/base/ir/ir_sequence.py3
-rw-r--r--odoo/addons/base/ir/ir_translation.py6
-rw-r--r--odoo/addons/base/ir/ir_ui_view.py38
-rw-r--r--odoo/addons/base/ir/ir_values.py6
14 files changed, 127 insertions, 104 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))