diff --git a/addons/web_editor/controllers/main.py b/addons/web_editor/controllers/main.py index 92d79774bf0c35d1f18d4d3d110333cfbabfc0d5..2a811702d65f6394941ec3d8a5cb939c438a05cf 100644 --- a/addons/web_editor/controllers/main.py +++ b/addons/web_editor/controllers/main.py @@ -98,7 +98,7 @@ class Web_Editor(http.Controller): @http.route('/web_editor/checklist', type='json', auth='user') def update_checklist(self, res_model, res_id, filename, checklistId, checked, **kwargs): record = request.env[res_model].browse(res_id) - value = getattr(record, filename, False) + value = filename in record._fields and record[filename] htmlelem = etree.fromstring("<div>%s</div>" % value, etree.HTMLParser()) checked = bool(checked) diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py index c33aba3d9dd0518ecc40932095a953fcbf1a284a..3577e11eaee98821d36658a8ec852d5cb91ef604 100644 --- a/odoo/addons/base/models/res_users.py +++ b/odoo/addons/base/models/res_users.py @@ -13,10 +13,10 @@ import logging import os import time from collections import defaultdict +from functools import wraps from hashlib import sha256 from itertools import chain, repeat -import decorator import passlib.context import pytz from lxml import etree @@ -94,8 +94,7 @@ def _jsonable(o): except TypeError: return False else: return True -@decorator.decorator -def check_identity(fn, self): +def check_identity(fn): """ Wrapped method should be an *action method* (called from a button type=object), and requires extra security to be executed. This decorator checks if the identity (password) has been checked in the last 10mn, and @@ -103,32 +102,36 @@ def check_identity(fn, self): Prevents access outside of interactive contexts (aka with a request) """ - if not request: - raise UserError(_("This method can only be accessed over HTTP")) - - if request.session.get('identity-check-last', 0) > time.time() - 10 * 60: - # update identity-check-last like github? - return fn(self) - - w = self.sudo().env['res.users.identitycheck'].create({ - 'request': json.dumps([ - { # strip non-jsonable keys (e.g. mapped to recordsets like binary_field_real_user) - k: v for k, v in self.env.context.items() - if _jsonable(v) - }, - self._name, - self.ids, - fn.__name__ - ]) - }) - return { - 'type': 'ir.actions.act_window', - 'res_model': 'res.users.identitycheck', - 'res_id': w.id, - 'name': _("Security Control"), - 'target': 'new', - 'views': [(False, 'form')], - } + @wraps(fn) + def wrapped(self): + if not request: + raise UserError(_("This method can only be accessed over HTTP")) + + if request.session.get('identity-check-last', 0) > time.time() - 10 * 60: + # update identity-check-last like github? + return fn(self) + + w = self.sudo().env['res.users.identitycheck'].create({ + 'request': json.dumps([ + { # strip non-jsonable keys (e.g. mapped to recordsets like binary_field_real_user) + k: v for k, v in self.env.context.items() + if _jsonable(v) + }, + self._name, + self.ids, + fn.__name__ + ]) + }) + return { + 'type': 'ir.actions.act_window', + 'res_model': 'res.users.identitycheck', + 'res_id': w.id, + 'name': _("Security Control"), + 'target': 'new', + 'views': [(False, 'form')], + } + wrapped.__has_check_identity = True + return wrapped #---------------------------------------------------------- # Basic res.groups and res.users @@ -1521,7 +1524,9 @@ class CheckIdentity(models.TransientModel): request.session['identity-check-last'] = time.time() ctx, model, ids, method = json.loads(self.sudo().request) - return getattr(self.env(context=ctx)[model].browse(ids), method)() + method = getattr(self.env(context=ctx)[model].browse(ids), method) + assert getattr(method, '__has_check_identity', False) + return method() #---------------------------------------------------------- # change password wizard diff --git a/odoo/fields.py b/odoo/fields.py index fb5b3956fd7786b1bfb2b9e99cdef3180b0d2788..55ce2c9158f551904ff50d586a6f8db315b8757e 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -64,6 +64,29 @@ def resolve_mro(model, name, predicate): return result +def determine(needle, records, *args): + """ Simple helper for calling a method given as a string or a function. + + :param needle: callable or name of method to call on ``records`` + :param BaseModel records: recordset to call ``needle`` on or with + :params args: additional arguments to pass to the determinant + :returns: the determined value if the determinant is a method name or callable + :raise TypeError: if ``records`` is not a recordset, or ``needle`` is not + a callable or valid method name + """ + if not isinstance(records, BaseModel): + raise TypeError("Determination requires a subject recordset") + if isinstance(needle, str): + needle = getattr(records, needle) + if needle.__name__.find('__'): + return needle(*args) + elif callable(needle): + if needle.__name__.find('__'): + return needle(records, *args) + + raise TypeError("Determination requires a callable or method name") + + class MetaField(type): """ Metaclass for field classes. """ by_type = {} @@ -253,10 +276,6 @@ class Field(MetaField('DummyField', (object,), {})): self._sequence = kwargs['_sequence'] = next(_global_seq) self.args = {key: val for key, val in kwargs.items() if val is not Default} - def new(self, **kwargs): - """ Return a field of the same type as ``self``, with its own parameters. """ - return type(self)(**kwargs) - def __str__(self): return "%s.%s" % (self.model_name, self.name) @@ -482,7 +501,7 @@ class Field(MetaField('DummyField', (object,), {})): for attr, prop in self.related_attrs: # check whether 'attr' is explicitly set on self (from its field # definition), and ignore its class-level value (only a default) - if attr not in self.__dict__: + if attr not in self.__dict__ and prop.startswith('_related_'): setattr(self, attr, getattr(field, prop)) for attr, value in field.__dict__.items(): @@ -662,6 +681,8 @@ class Field(MetaField('DummyField', (object,), {})): """ Return a dictionary that describes the field ``self``. """ desc = {'type': self.type} for attr, prop in self.description_attrs: + if not prop.startswith('_description_'): + continue value = getattr(self, prop) if callable(value): value = value(env) @@ -1183,22 +1204,11 @@ class Field(MetaField('DummyField', (object,), {})): def determine_inverse(self, records): """ Given the value of ``self`` on ``records``, inverse the computation. """ - if isinstance(self.inverse, str): - getattr(records, self.inverse)() - else: - self.inverse(records) + determine(self.inverse, records) def determine_domain(self, records, operator, value): """ Return a domain representing a condition on ``self``. """ - if isinstance(self.search, str): - return getattr(records, self.search)(operator, value) - else: - return self.search(records, operator, value) - - ############################################################################ - # - # Notification when fields are modified - # + return determine(self.search, records, operator, value) class Boolean(Field): @@ -2383,10 +2393,8 @@ class Selection(Field): translated according to context language """ selection = self.selection - if isinstance(selection, str): - return getattr(env[self.model_name], selection)() - if callable(selection): - return selection(env[self.model_name]) + if isinstance(selection, str) or callable(selection): + return determine(selection, env[self.model_name]) # translate selection labels if env.lang: @@ -2397,10 +2405,8 @@ class Selection(Field): def get_values(self, env): """Return a list of the possible values.""" selection = self.selection - if isinstance(selection, str): - selection = getattr(env[self.model_name], selection)() - elif callable(selection): - selection = selection(env[self.model_name]) + if isinstance(selection, str) or callable(selection): + selection = determine(selection, env[self.model_name]) return [value for value, _ in selection] def convert_to_column(self, value, record, values=None, validate=True): diff --git a/odoo/models.py b/odoo/models.py index 89aa01d23152a78f57800103ddc17aa502cb156c..f3ef92e8db13cad016696057abb4926794206451 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -2692,7 +2692,8 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): # following specific properties: # - reading inherited fields should not bypass access rights # - copy inherited fields iff their original field is copied - self._add_field(name, field.new( + Field = type(field) + self._add_field(name, Field( inherited=True, inherited_field=field, related=(parent_fname, name), @@ -2778,7 +2779,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): if not field.related: self._add_field(name, field) else: - self._add_field(name, field.new(**field.args)) + self._add_field(name, type(field)(**field.args)) cls._model_fields = list(cls._fields) else: @@ -2790,7 +2791,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): for name, field in sorted(getmembers(cls, Field.__instancecheck__), key=lambda f: f[1]._sequence): # do not retrieve magic, custom and inherited fields if not any(field.args.get(k) for k in ('automatic', 'manual', 'inherited')): - self._add_field(name, field.new()) + self._add_field(name, type(field)()) self._add_magic_fields() cls._model_fields = list(cls._fields) @@ -4072,12 +4073,7 @@ Fields: return records def _compute_field_value(self, field): - # This is for base automation, to have something to override to catch - # the changes of values for stored compute fields. - if isinstance(field.compute, str): - getattr(self, field.compute)() - else: - field.compute(self) + odoo.fields.determine(field.compute, self) if field.store and any(self._ids): # check constraints of the fields that have been computed