diff --git a/CHANGELOG b/CHANGELOG index 14b3ee44c6..3b0d7d0acf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +* set/get function on Fields take a list of ids +* New interface for Function/Property fields + Take a Field as first argument instead of many separate arguments + Remove static arguments * Fix Float and Numeric for lost of precision * Add decimal digits validation on fields * Improve unittests to run trytond from tests diff --git a/doc/models.rst b/doc/models.rst index 79d837e0cc..48b2d9e77f 100644 --- a/doc/models.rst +++ b/doc/models.rst @@ -549,26 +549,6 @@ The framework is able to check, if ``names`` (instead of ``name``) is used in the method definition, hence adapting the way the method is called. -Another way to tackle Function implementation is to pass a dictionary to the -``args`` argument on the field definition. It will be forwarded to the function -call: - -.. highlight:: python - -:: - - state = fields.Function( - 'get_state', type='selection', string='Total Service', - args={'key':'value'}, - selection=[('draft','Draft'),('done','Done')], - ) - - def get_state(self, cursor, user, ids, names, arg, context=None): - # [...] - if arg.get('key'): - pass # do something with 'value' - - Search on Function fields +++++++++++++++++++++++++ diff --git a/trytond/ir/action.py b/trytond/ir/action.py index e8b2650fb0..9afb2f86be 100644 --- a/trytond/ir/action.py +++ b/trytond/ir/action.py @@ -216,15 +216,14 @@ class ActionReport(ModelSQL, ModelView): report_name = fields.Char('Internal Name', required=True) report = fields.Char('Path') report_content_data = fields.Binary('Content') - report_content = fields.Function('get_report_content', - fnct_inv='report_content_inv', type='binary', - string='Content') + report_content = fields.Function(fields.Binary('Content'), + 'get_report_content', setter='set_report_content') action = fields.Many2One('ir.action', 'Action', required=True, ondelete='CASCADE') - style = fields.Property(type='char', string='Style', - help='Define the style to apply on the report.') - style_content = fields.Function('get_style_content', - type='binary', string='Style') + style = fields.Property(fields.Char('Style', + help='Define the style to apply on the report.')) + style_content = fields.Function(fields.Binary('Style'), + 'get_style_content') direct_print = fields.Boolean('Direct Print') extension = fields.Selection( [('odt', 'ODT Document'), @@ -281,7 +280,7 @@ class ActionReport(ModelSQL, ModelView): def default_module(self, cursor, user, context=None): return context and context.get('module', '') or '' - def get_report_content(self, cursor, user, ids, name, arg, context=None): + def get_report_content(self, cursor, user, ids, name, context=None): res = {} for report in self.browse(cursor, user, ids, context=context): data = report[name + '_data'] @@ -295,11 +294,12 @@ class ActionReport(ModelSQL, ModelView): res[report.id] = data return res - def report_content_inv(self, cursor, user, obj_id, name, value, arg, + def set_report_content(self, cursor, user, ids, name, value, context=None): - self.write(cursor, user, obj_id, {name+'_data': value}, context=context) + self.write(cursor, user, ids, {'%s_data' % name: value}, + context=context) - def get_style_content(self, cursor, user, ids, name, arg, context=None): + def get_style_content(self, cursor, user, ids, name, context=None): res = {} for report in self.browse(cursor, user, ids, context=context): try: @@ -396,8 +396,7 @@ class ActionActWindow(ModelSQL, ModelView): ], string='Type of view') act_window_views = fields.One2Many('ir.action.act_window.view', 'act_window', 'Views') - views = fields.Function('views_get_fnc', type='binary', - string='Views') + views = fields.Function(fields.Binary('Views'), 'get_views') limit = fields.Integer('Limit', help='Default limit for the list view') auto_refresh = fields.Integer('Auto-Refresh', @@ -408,12 +407,11 @@ class ActionActWindow(ModelSQL, ModelView): help='Use the action name as window name') search_value = fields.Char('Search Criteria', help='Default search criteria for the list view') - pyson_domain = fields.Function('get_pyson', type='string', - string='PySON Domain') - pyson_context = fields.Function('get_pyson', type='string', - string='PySON Context') - pyson_search_value = fields.Function('get_pyson', type='string', - string='PySON Search Criteria') + pyson_domain = fields.Function(fields.Char('PySON Domain'), 'get_pyson') + pyson_context = fields.Function(fields.Char('PySON Context'), + 'get_pyson') + pyson_search_value = fields.Function(fields.Char( + 'PySON Search Criteria'), 'get_pyson') def __init__(self): super(ActionActWindow, self).__init__() @@ -504,14 +502,14 @@ class ActionActWindow(ModelSQL, ModelView): return False return True - def views_get_fnc(self, cursor, user, ids, name, arg, context=None): + def get_views(self, cursor, user, ids, name, context=None): res = {} for act in self.browse(cursor, user, ids, context=context): res[act.id] = [(view.view.id, view.view.type) \ for view in act.act_window_views] return res - def get_pyson(self, cursor, user, ids, name, arg, context=None): + def get_pyson(self, cursor, user, ids, name, context=None): res = {} encoder = PYSONEncoder() field = name[6:] diff --git a/trytond/ir/attachment.py b/trytond/ir/attachment.py index 4a693ed359..5a3680673d 100644 --- a/trytond/ir/attachment.py +++ b/trytond/ir/attachment.py @@ -16,16 +16,15 @@ class Attachment(ModelSQL, ModelView): _name = 'ir.attachment' _description = __doc__ name = fields.Char('Attachment Name', required=True) - datas = fields.Function('get_datas', fnct_inv='set_datas', - type='binary', string='Datas') + datas = fields.Function(fields.Binary('Datas'), 'get_datas', + setter='set_datas') description = fields.Text('Description') res_model = fields.Char('Resource Model', readonly=True) res_id = fields.Integer('Resource ID', readonly=True) link = fields.Char('Link') digest = fields.Char('Digest', size=32) collision = fields.Integer('Collision') - datas_size = fields.Function('get_datas', type='integer', - string='Datas size') + datas_size = fields.Function(fields.Integer('Datas size'), 'get_datas') def __init__(self): super(Attachment, self).__init__() @@ -38,7 +37,7 @@ class Attachment(ModelSQL, ModelView): def default_collision(self, cursor, user, context=None): return 0 - def get_datas(self, cursor, user, ids, name, arg, context=None): + def get_datas(self, cursor, user, ids, name, context=None): res = {} db_name = cursor.dbname for attachment in self.browse(cursor, user, ids, context=context): @@ -64,7 +63,7 @@ class Attachment(ModelSQL, ModelView): res[attachment.id] = value return res - def set_datas(self, cursor, user, obj_id, name, value, args, context=None): + def set_datas(self, cursor, user, ids, name, value, context=None): if value is False or value is None: return db_name = cursor.dbname @@ -113,10 +112,10 @@ class Attachment(ModelSQL, ModelView): file_p = open(filename, 'wb') file_p.write(data) file_p.close() - cursor.execute('UPDATE ir_attachment ' \ - 'SET digest = %s, ' \ - 'collision = %s ' \ - 'WHERE id = %s', (digest, collision, obj_id)) + self.write(cursor, user, ids, { + 'digest': digest, + 'collision': collision, + }, context=context) def check_access(self, cursor, user, ids, mode='read', context=None): model_access_obj = self.pool.get('ir.model.access') diff --git a/trytond/ir/module/module.py b/trytond/ir/module/module.py index a014b2a58a..92ffbbcc8e 100644 --- a/trytond/ir/module/module.py +++ b/trytond/ir/module/module.py @@ -133,7 +133,7 @@ class Module(ModelSQL, ModelView): description = fields.Text("Description", readonly=True, translate=True) author = fields.Char("Author", readonly=True) website = fields.Char("Website", readonly=True) - version = fields.Function('get_version', string='Version', type='char') + version = fields.Function(fields.Char('Version'), 'get_version') dependencies = fields.One2Many('ir.module.module.dependency', 'module', 'Dependencies', readonly=True) state = fields.Selection([ @@ -186,8 +186,7 @@ class Module(ModelSQL, ModelView): return {} return info - def get_version(self, cursor, user, ids, name, arg, - context=None): + def get_version(self, cursor, user, ids, name, context=None): res = {} for module in self.browse(cursor, user, ids, context=context): res[module.id] = Module.get_module_info( @@ -465,15 +464,14 @@ class ModuleDependency(ModelSQL, ModelView): name = fields.Char('Name') module = fields.Many2One('ir.module.module', 'Module', select=1, ondelete='CASCADE', required=True) - state = fields.Function('get_state', type='selection', - selection=[ - ('uninstalled','Not Installed'), - ('installed','Installed'), - ('to upgrade','To be upgraded'), - ('to remove','To be removed'), - ('to install','To be installed'), - ('unknown', 'Unknown'), - ], string='State', readonly=True) + state = fields.Function(fields.Selection([ + ('uninstalled','Not Installed'), + ('installed','Installed'), + ('to upgrade','To be upgraded'), + ('to remove','To be removed'), + ('to install','To be installed'), + ('unknown', 'Unknown'), + ], 'State', readonly=True), 'get_state') def __init__(self): super(ModuleDependency, self).__init__() @@ -482,7 +480,7 @@ class ModuleDependency(ModelSQL, ModelView): 'Dependency must be unique by module!'), ] - def get_state(self, cursor, user, ids, name, args, context=None): + def get_state(self, cursor, user, ids, name, context=None): result = {} module_obj = self.pool.get('ir.module.module') for dependency in self.browse(cursor, user, ids): diff --git a/trytond/ir/property.py b/trytond/ir/property.py index 395a5bd6e5..9a9f2c081a 100644 --- a/trytond/ir/property.py +++ b/trytond/ir/property.py @@ -147,14 +147,14 @@ class Property(ModelSQL, ModelView): 'field': field_id, } - def set(self, cursor, user, name, model, res_id, val, context=None): + def set(self, cursor, user, name, model, ids, val, context=None): """ - Set property value for res_id + Set property value for ids :param cursor: the database cursor :param user: the user id :param names: property name :param model: object name - :param res_id: a record ids + :param ids: a list of ids :param val: the value :param context: the context :return: the id of the record created @@ -177,7 +177,7 @@ class Property(ModelSQL, ModelView): property_ids = self.search(cursor, user, [ ('field', '=', field_id), - ('res', '=', model + ',' + str(res_id)), + ('res', 'in', [model + ',' + str(res_id) for res_id in ids]), ], order=[], context=context) self.delete(cursor, 0, property_ids, context=ctx) @@ -208,9 +208,10 @@ class Property(ModelSQL, ModelView): res = False if (val != default_val): - vals = self._set_values(cursor, user, name, model, res_id, val, - field_id, context=context) - res = self.create(cursor, 0, vals, context=ctx) + for res_id in ids: + vals = self._set_values(cursor, user, name, model, res_id, val, + field_id, context=context) + res = self.create(cursor, 0, vals, context=ctx) return res Property() diff --git a/trytond/ir/translation.py b/trytond/ir/translation.py index d1cd3a9d62..5b3a9c424c 100644 --- a/trytond/ir/translation.py +++ b/trytond/ir/translation.py @@ -53,8 +53,8 @@ class Translation(ModelSQL, ModelView, Cacheable): value = fields.Text('Translation Value') module = fields.Char('Module', readonly=True) fuzzy = fields.Boolean('Fuzzy') - model = fields.Function('get_model', fnct_search='model_search', - type='char', string='Model') + model = fields.Function(fields.Char('Model'), 'get_model', + searcher='search_model') def __init__(self): super(Translation, self).__init__() @@ -75,13 +75,13 @@ class Translation(ModelSQL, ModelView, Cacheable): def default_fuzzy(self, cursor, user, context=None): return False - def get_model(self, cursor, user, ids, name, arg, context=None): + def get_model(self, cursor, user, ids, name, context=None): res = {} for translation in self.browse(cursor, user, ids, context=context): res[translation.id] = translation.name.split(',')[0] return res - def model_search(self, cursor, user, name, args, context=None): + def search_model(self, cursor, user, name, args, context=None): args2 = [] i = 0 while i < len(args): diff --git a/trytond/ir/ui/menu.py b/trytond/ir/ui/menu.py index 15e8c52182..f7e86beae8 100644 --- a/trytond/ir/ui/menu.py +++ b/trytond/ir/ui/menu.py @@ -82,17 +82,16 @@ class UIMenu(ModelSQL, ModelView): parent = fields.Many2One('ir.ui.menu', 'Parent Menu', select=1) groups = fields.Many2Many('ir.ui.menu-res.group', 'menu_id', 'gid', 'Groups') - complete_name = fields.Function('get_full_name', - string='Complete Name', type='char', order_field='name') + complete_name = fields.Function(fields.Char('Complete Name', + order_field='name'), 'get_full_name') icon = fields.Selection(ICONS, 'Icon', translate=False) - action = fields.Function('get_action', fnct_inv='action_inv', - type='reference', string='Action', - selection=[ - ('ir.action.report', 'ir.action.report'), - ('ir.action.act_window', 'ir.action.act_window'), - ('ir.action.wizard', 'ir.action.wizard'), - ('ir.action.url', 'ir.action.url'), - ]) + action = fields.Function(fields.Reference('Action', + selection=[ + ('ir.action.report', 'ir.action.report'), + ('ir.action.act_window', 'ir.action.act_window'), + ('ir.action.wizard', 'ir.action.wizard'), + ('ir.action.url', 'ir.action.url'), + ]), 'get_action', setter='set_action') active = fields.Boolean('Active') def __init__(self): @@ -108,7 +107,7 @@ class UIMenu(ModelSQL, ModelView): def default_active(self, cursor, user, context=None): return True - def get_full_name(self, cursor, user, ids, name, args, context): + def get_full_name(self, cursor, user, ids, name, context): res = {} for menu in self.browse(cursor, user, ids, context=context): res[menu.id] = self._get_one_full_name(menu) @@ -146,7 +145,7 @@ class UIMenu(ModelSQL, ModelView): res = check_menu(res) return res - def get_action(self, cursor, user, ids, name, arg, context=None): + def get_action(self, cursor, user, ids, name, context=None): action_keyword_obj = self.pool.get('ir.action.keyword') if context is None: @@ -176,8 +175,7 @@ class UIMenu(ModelSQL, ModelView): res[model_id] = action_keyword.action.type + ',' + str(action_id) return res - def action_inv(self, cursor, user, menu_id, name, value, arg, - context=None): + def set_action(self, cursor, user, ids, name, value, context=None): if context is None: context = {} if not value: @@ -186,10 +184,14 @@ class UIMenu(ModelSQL, ModelView): if '_timestamp' in ctx: del ctx['_timestamp'] action_keyword_obj = self.pool.get('ir.action.keyword') - action_keyword_ids = action_keyword_obj.search(cursor, user, [ - ('keyword', '=', 'tree_open'), - ('model', '=', self._name + ',' + str(menu_id)), - ], context=context) + action_keyword_ids = [] + for i in range(0, len(ids), cursor.IN_MAX): + sub_ids = ids[i:i + cursor.IN_MAX] + action_keyword_ids += action_keyword_obj.search(cursor, user, [ + ('keyword', '=', 'tree_open'), + ('model', 'in', [self._name + ',' + str(menu_id) + for menu_id in sub_ids]), + ], context=context) if action_keyword_ids: action_keyword_obj.delete(cursor, user, action_keyword_ids, context=ctx) @@ -199,10 +201,11 @@ class UIMenu(ModelSQL, ModelView): action_obj = self.pool.get(action_type) action = action_obj.browse(cursor, user, int(action_id), context=context) - action_keyword_obj.create(cursor, user, { - 'keyword': 'tree_open', - 'model': self._name + ',' + str(menu_id), - 'action': action.action.id, - }, context=ctx) + for menu_id in ids: + action_keyword_obj.create(cursor, user, { + 'keyword': 'tree_open', + 'model': self._name + ',' + str(menu_id), + 'action': action.action.id, + }, context=ctx) UIMenu() diff --git a/trytond/model/fields/function.py b/trytond/model/fields/function.py index 2d3320df25..64f70a5d1b 100644 --- a/trytond/model/fields/function.py +++ b/trytond/model/fields/function.py @@ -1,93 +1,55 @@ #This file is part of Tryton. The COPYRIGHT file at the top level of #this repository contains the full copyright notices and license terms. +import inspect +import copy from trytond.model.fields.field import Field from trytond.model.fields.float import digits_validate from trytond.model.fields.one2many import add_remove_validate from trytond.pyson import PYSON -import inspect class Function(Field): ''' Define function field (any). ''' - _type = 'float' - def __init__(self, fnct, arg=None, fnct_inv='', fnct_inv_arg=None, - type='float', fnct_search='', model_name=None, selection=None, - digits=None, relation=None, add_remove=None, datetime_field=None, - string='', help='', required=False, readonly=False, domain=None, - states=None, priority=0, change_default=False, translate=False, - select=0, on_change=None, on_change_with=None, depends=None, - order_field=None, context=None): + def __init__(self, field, getter, setter=None, searcher=None): ''' - :param fnct: The name of the function. - :param arg: Argument for the function. - :param fnct_inv: The name of the function to write. - :param fnct_inv_arg: Argument for the function to write. - :param type: The type of field. - :param fnct_search: The name of the function to search. - :param model_name: See Many2One. - :param selection: See Selection. - :param digits: See Float. - :param relation: Like model_name. - :param add_remove: See Many2One. - :param datetime_field: The name of the field that contains the datetime - value to read the target records. + :param field: The field of the function. + :param getter: The name of the function for getting values. + :param setter: The name of the function to set value. + :param searcher: The name of the function to search. ''' - if datetime_field: - if depends: - depends.append(datetime_field) - else: - depends = [datetime_field] - super(Function, self).__init__(string=string, help=help, - required=required, readonly=readonly, domain=domain, - states=states, priority=priority, change_default=change_default, - translate=translate, select=select, on_change=on_change, - on_change_with=on_change_with, depends=depends, - order_field=order_field, context=context) - self.model_name = model_name - self.fnct = fnct - self.arg = arg - self.fnct_inv = fnct_inv - self.fnct_inv_arg = fnct_inv_arg - if not self.fnct_inv: - self.readonly = True - self._type = type - self.fnct_search = fnct_search - self.selection = selection - self.__digits = None - self.digits = digits - if relation: - self.model_name = relation - self.__add_remove = None - self.add_remove = add_remove - self.datetime_field = datetime_field + assert isinstance(field, Field) + self._field = field + self._type = field._type + self.getter = getter + self.setter = setter + if not self.setter: + self._field.readonly = True + self.searcher = searcher __init__.__doc__ += Field.__init__.__doc__ - def _get_digits(self): - return self.__digits + def __copy__(self): + return Function(copy.copy(self._field), self.getter, + setter=self.setter, searcher=self.searcher) - def _set_digits(self, value): - digits_validate(value) - self.__digits = value + def __getattr__(self, name): + return getattr(self._field, name) - digits = property(_get_digits, _set_digits) + def __getitem__(self, name): + return self._field[name] - def _get_add_remove(self): - return self.__add_remove - - def _set_add_remove(self, value): - add_remove_validate(value) - self.__add_remove = value - - add_remove = property(_get_add_remove, _set_add_remove) + def __setattr__(self, name, value): + if name in ('_field', '_type', 'getter', 'setter', 'searcher'): + return object.__setattr__(self, name, value) + return setattr(self._field, name, value) def search(self, cursor, user, model, name, args, context=None): ''' - Call the fnct_search. + Call the searcher. :param cursor: The database cursor. :param user: The user id. @@ -97,15 +59,15 @@ class Function(Field): :param context: The context. :return: New list of domain. ''' - if not self.fnct_search: + if not self.searcher: model.raise_user_error(cursor, 'search_function_missing', name, context=context) - return getattr(model, self.fnct_search)(cursor, user, name, args, + return getattr(model, self.searcher)(cursor, user, name, args, context=context) def get(self, cursor, user, ids, model, name, values=None, context=None): ''' - Call the fnct. + Call the getter. If the function has ``names`` in the function definition then it will call it with a list of name. @@ -123,34 +85,34 @@ class Function(Field): if isinstance(name, list): names = name # Test is the function works with a list of names - if 'names' in inspect.getargspec(getattr(model, self.fnct))[0]: - return getattr(model, self.fnct)(cursor, user, ids, names, - self.arg, context=context) + if 'names' in inspect.getargspec(getattr(model, self.getter))[0]: + return getattr(model, self.getter)(cursor, user, ids, names, + context=context) res = {} for name in names: - res[name] = getattr(model, self.fnct)(cursor, user, ids, name, - self.arg, context=context) + res[name] = getattr(model, self.getter)(cursor, user, ids, + name, context=context) return res else: # Test is the function works with a list of names - if 'names' in inspect.getargspec(getattr(model, self.fnct))[0]: + if 'names' in inspect.getargspec(getattr(model, self.getter))[0]: name = [name] - return getattr(model, self.fnct)(cursor, user, ids, name, self.arg, + return getattr(model, self.getter)(cursor, user, ids, name, context=context) - def set(self, cursor, user, record_id, model, name, value, context=None): + def set(self, cursor, user, ids, model, name, value, context=None): ''' - Call the fnct_inv. + Call the setter. :param cursor: The database cursor. :param user: The user id. - :param record_id: The record id. + :param ids: A list of ids. :param model: The model. :param name: The name of the field. :param value: The value to set. :param context: The context. ''' - if self.fnct_inv: - getattr(model, self.fnct_inv)(cursor, user, record_id, name, value, - self.fnct_inv_arg, context=context) + if self.setter: + getattr(model, self.setter)(cursor, user, ids, name, value, + context=context) diff --git a/trytond/model/fields/many2many.py b/trytond/model/fields/many2many.py index 2bf2ed64b4..8cbf6018c2 100644 --- a/trytond/model/fields/many2many.py +++ b/trytond/model/fields/many2many.py @@ -82,13 +82,13 @@ class Many2Many(Field): res[relation[self.origin]].append(relation[self.target]) return res - def set(self, cursor, user, record_id, model, name, values, context=None): + def set(self, cursor, user, ids, model, name, values, context=None): ''' Set the values. :param cursor: The database cursor :param user: The user id - :param record_id: The record id + :param ids: A list of ids :param model: A string with the name of the model :param name: A string with the name of the field :param values: A list of tuples: @@ -107,75 +107,77 @@ class Many2Many(Field): target_obj = self.get_target(model.pool) for act in values: if act[0] == 'create': - relation_obj.create(cursor, user, { - self.origin: record_id, - self.target: target_obj.create(cursor, user, act[1], - context=context), - }, context=context) + for record_id in ids: + relation_obj.create(cursor, user, { + self.origin: record_id, + self.target: target_obj.create(cursor, user, act[1], + context=context), + }, context=context) elif act[0] == 'write': target_obj.write(cursor, user, act[1] , act[2], context=context) elif act[0] == 'delete': target_obj.delete(cursor, user, act[1], context=context) elif act[0] == 'unlink': if isinstance(act[1], (int, long)): - ids = [act[1]] + target_ids = [act[1]] else: - ids = list(act[1]) - if not ids: + target_ids = list(act[1]) + if not target_ids: continue relation_ids = [] - for i in range(0, len(ids), cursor.IN_MAX): - sub_ids = ids[i:i + cursor.IN_MAX] + for i in range(0, len(target_ids), cursor.IN_MAX): + sub_ids = target_ids[i:i + cursor.IN_MAX] relation_ids += relation_obj.search(cursor, user, [ - (self.origin, '=', record_id), + (self.origin, 'in', ids), (self.target, 'in', sub_ids), ], context=context) relation_obj.delete(cursor, user, relation_ids, context=context) elif act[0] == 'add': if isinstance(act[1], (int, long)): - ids = [act[1]] + target_ids = [act[1]] else: - ids = list(act[1]) - if not ids: + target_ids = list(act[1]) + if not target_ids: continue existing_ids = [] - for i in range(0, len(ids), cursor.IN_MAX): - sub_ids = ids[i:i + cursor.IN_MAX] + for i in range(0, len(target_ids), cursor.IN_MAX): + sub_ids = target_ids[i:i + cursor.IN_MAX] relation_ids = relation_obj.search(cursor, user, [ - (self.origin, '=', record_id), + (self.origin, 'in', ids), (self.target, 'in', sub_ids), ], context=context) for relation in relation_obj.browse(cursor, user, relation_ids, context=context): existing_ids.append(relation[self.target].id) - new_ids = [x for x in ids if x not in existing_ids] - for new_id in new_ids: - relation_obj.create(cursor, user, { - self.origin: record_id, - self.target: new_id, - }, context=context) + for new_id in (x for x in target_ids if x not in existing_ids): + for record_id in ids: + relation_obj.create(cursor, user, { + self.origin: record_id, + self.target: new_id, + }, context=context) elif act[0] == 'unlink_all': - ids = relation_obj.search(cursor, user, [ - (self.origin, '=', record_id), + target_ids = relation_obj.search(cursor, user, [ + (self.origin, 'in', ids), (self.target + '.id', '!=', False), ], context=context) - relation_obj.delete(cursor, user, ids, context=context) + relation_obj.delete(cursor, user, target_ids, context=context) elif act[0] == 'set': if not act[1]: - ids = [] + target_ids = [] else: - ids = list(act[1]) - ids2 = relation_obj.search(cursor, user, [ - (self.origin, '=', record_id), + target_ids = list(act[1]) + target_ids2 = relation_obj.search(cursor, user, [ + (self.origin, 'in', ids), (self.target + '.id', '!=', False), ], context=context) - relation_obj.delete(cursor, user, ids2, context=context) + relation_obj.delete(cursor, user, target_ids2, context=context) - for new_id in ids: - relation_obj.create(cursor, user, { - self.origin: record_id, - self.target: new_id, - }, context=context) + for new_id in target_ids: + for record_id in ids: + relation_obj.create(cursor, user, { + self.origin: record_id, + self.target: new_id, + }, context=context) else: raise Exception('Bad arguments') diff --git a/trytond/model/fields/one2many.py b/trytond/model/fields/one2many.py index 5b0269cf81..c945e0198a 100644 --- a/trytond/model/fields/one2many.py +++ b/trytond/model/fields/one2many.py @@ -106,13 +106,13 @@ class One2Many(Field): val.sort(lambda x, y: cmp(index_of_ids2[x], index_of_ids2[y])) return res - def set(self, cursor, user, record_id, model, name, values, context=None): + def set(self, cursor, user, ids, model, name, values, context=None): ''' Set the values. :param cursor: The database cursor :param user: The user id - :param record_id: The record id + :param ids: A list of ids :param model: A string with the name of the model :param name: A string with the name of the field :param values: A list of tuples: @@ -131,64 +131,66 @@ class One2Many(Field): model = model.pool.get(self.model_name) for act in values: if act[0] == 'create': - act[1][self.field] = record_id - model.create(cursor, user, act[1], context=context) + for record_id in ids: + act[1][self.field] = record_id + model.create(cursor, user, act[1], context=context) elif act[0] == 'write': - act[2][self.field] = record_id model.write(cursor, user, act[1] , act[2], context=context) elif act[0] == 'delete': model.delete(cursor, user, act[1], context=context) elif act[0] == 'delete_all': - ids = model.search(cursor, user, [ - (self.field, '=', record_id), + target_ids = model.search(cursor, user, [ + (self.field, 'in', ids), ], context=context) - model.delete(cursor, user, ids, context=context) + model.delete(cursor, user, target_ids, context=context) elif act[0] == 'unlink': if isinstance(act[1], (int, long)): - ids = [act[1]] + target_ids = [act[1]] else: - ids = list(act[1]) - if not ids: + target_ids = list(act[1]) + if not target_ids: continue - ids = model.search(cursor, user, [ - (self.field, '=', record_id), - ('id', 'in', ids), + target_ids = model.search(cursor, user, [ + (self.field, 'in', ids), + ('id', 'in', target_ids), ], context=context) - model.write(cursor, user, ids, { + model.write(cursor, user, target_ids, { self.field: False, }, context=context) elif act[0] == 'add': if isinstance(act[1], (int, long)): - ids = [act[1]] + target_ids = [act[1]] else: - ids = list(act[1]) - if not ids: + target_ids = list(act[1]) + if not target_ids: continue - ids = model.write(cursor, user, ids, { - self.field: record_id, - }, context=context) + for record_id in ids: + model.write(cursor, user, target_ids, { + self.field: record_id, + }, context=context) elif act[0] == 'unlink_all': - ids = model.search(cursor, user, [ - (self.field, '=', record_id), + target_ids = model.search(cursor, user, [ + (self.field, 'in', ids), ], context=context) - model.write(cursor, user, ids, { + model.write(cursor, user, target_ids, { self.field: False, }, context=context) elif act[0] == 'set': if not act[1]: - ids = [0] + target_ids = [0] else: - ids = list(act[1]) - ids2 = model.search(cursor, user, [ - (self.field, '=', record_id), - ('id', 'not in', ids), - ], context=context) - model.write(cursor, user, ids2, { - self.field: False, - }, context=context) - if act[1]: - model.write(cursor, user, ids, { - self.field: record_id, + target_ids = list(act[1]) + for record_id in ids: + target_ids2 = model.search(cursor, user, [ + (self.field, '=', record_id), + ('id', 'not in', target_ids), + ], context=context) + model.write(cursor, user, target_ids2, { + self.field: False, }, context=context) + if act[1]: + model.write(cursor, user, target_ids, { + self.field: record_id, + }, context=context) else: raise Exception('Bad arguments') diff --git a/trytond/model/fields/property.py b/trytond/model/fields/property.py index c624d25751..e45f781d3b 100644 --- a/trytond/model/fields/property.py +++ b/trytond/model/fields/property.py @@ -1,6 +1,7 @@ #This file is part of Tryton. The COPYRIGHT file at the top level of #this repository contains the full copyright notices and license terms. +import copy from trytond.model.fields.function import Function from trytond.model.fields.field import Field from trytond import backend @@ -11,32 +12,17 @@ class Property(Function): Define a property field that is stored in ir.property (any). ''' - def __init__(self, type='float', model_name=None, selection=None, - digits=None, relation=None, add_remove=None, string='', help='', - required=False, readonly=False, domain=None, states=None, - priority=0, change_default=False, translate=False, select=0, - on_change=None, on_change_with=None, depends=None, - order_field=None, context=None): + def __init__(self, field): ''' - :param type: The type of the field. - :param fnct_search: The name of the search function. - :param model_name: See Many2One. - :param selection: See Selection. - :param digits: See Float. - :param relation: Like model_name. - :param add_remove: See Many2One. + :param field: The field of the function. ''' - super(Property, self).__init__('', type=type, model_name=model_name, - selection=selection, digits=digits, relation=relation, - add_remove=add_remove, string=string, help=help, - required=required, readonly=readonly, domain=domain, - states=states, priority=priority, change_default=change_default, - translate=translate, select=select, on_change=on_change, - on_change_with=on_change_with, depends=depends, - order_field=order_field, context=context) - self.readonly = False + super(Property, self).__init__(field, True, True, True) + __init__.__doc__ += Field.__init__.__doc__ + def __copy__(self): + return Property(copy.copy(self._field)) + def get(self, cursor, user, ids, model, name, values=None, context=None): ''' Retreive the property. @@ -56,21 +42,21 @@ class Property(Function): return res - def set(self, cursor, user, record_id, model, name, value, context=None): + def set(self, cursor, user, ids, model, name, value, context=None): ''' Set the property. :param cursor: The database cursor. :param user: The user id. - :param record_id: The record id. + :param ids: A list of ids. :param model: The model. :param name: The name of the field. :param value: The value to set. :param context: The context. ''' property_obj = model.pool.get('ir.property') - return property_obj.set(cursor, user, name, model._name, record_id, - (value and (self.model_name or '') + ',' + str(value)) or + return property_obj.set(cursor, user, name, model._name, ids, + (value and getattr(self, 'model_name', '') + ',' + str(value)) or False, context=context) diff --git a/trytond/model/fields/reference.py b/trytond/model/fields/reference.py index 565c435da1..a3be301a8c 100644 --- a/trytond/model/fields/reference.py +++ b/trytond/model/fields/reference.py @@ -27,6 +27,7 @@ class Reference(Field): on_change_with=on_change_with, depends=depends, order_field=order_field, context=context) self.selection = selection or None + __init__.__doc__ += Field.__init__.__doc__ def get(self, cursor, user, ids, model, name, values=None, context=None): diff --git a/trytond/model/modelsql.py b/trytond/model/modelsql.py index 2472f96dc3..e946fcf389 100644 --- a/trytond/model/modelsql.py +++ b/trytond/model/modelsql.py @@ -407,7 +407,7 @@ class ModelSQL(ModelStorage): upd_todo.sort(lambda x, y: self._columns[x].priority - \ self._columns[y].priority) for field in upd_todo: - self._columns[field].set(cursor, user, id_new, self, field, + self._columns[field].set(cursor, user, [id_new], self, field, values[field], context=context) if self._history: @@ -601,8 +601,8 @@ class ModelSQL(ModelStorage): func_fields = {} for field in fields_post: if isinstance(self._columns[field], fields.Function): - key = (self._columns[field].fnct, self._columns[field].arg, - self._columns[field].datetime_field) + key = (self._columns[field].getter, + getattr(self._columns[field], 'datetime_field', None)) func_fields.setdefault(key, []) func_fields[key].append(field) continue @@ -622,10 +622,10 @@ class ModelSQL(ModelStorage): values=res, context=context) for record in res: record[field] = res2[record['id']] - for i in func_fields: - field_list = func_fields[i] + for key in func_fields: + field_list = func_fields[key] field = field_list[0] - datetime_field = i[2] + _, datetime_field = key if datetime_field: ctx = context.copy() for record in res: @@ -636,8 +636,8 @@ class ModelSQL(ModelStorage): for field in res2: record[field] = res2[field][record['id']] continue - res2 = self._columns[field].get(cursor, user, ids, self, field_list, - values=res, context=context) + res2 = self._columns[field].get(cursor, user, ids, self, + field_list, values=res, context=context) for field in res2: for record in res: record[field] = res2[field][record['id']] @@ -923,13 +923,12 @@ class ModelSQL(ModelStorage): context.get('language') or 'en_US', ids, values[field]) - # call the 'set' method of fields which are not classic_write + # call the 'set' method of fields upd_todo.sort(lambda x, y: self._columns[x].priority - \ self._columns[y].priority) for field in upd_todo: - for select_id in ids: - self._columns[field].set(cursor, user, select_id, self, field, - values[field], context=context) + self._columns[field].set(cursor, user, ids, self, field, + values[field], context=context) if self._history: columns = ['"' + str(x) + '"' for x in self._columns diff --git a/trytond/model/modelstorage.py b/trytond/model/modelstorage.py index 9001419742..a35a761510 100644 --- a/trytond/model/modelstorage.py +++ b/trytond/model/modelstorage.py @@ -43,8 +43,8 @@ class ModelStorage(Model): create_date = fields.DateTime('Create Date', readonly=True) write_uid = fields.Many2One('res.user', 'Write User', readonly=True) write_date = fields.DateTime('Write Date', readonly=True) - rec_name = fields.Function('get_rec_name', type='char', string='Name', - fnct_search='search_rec_name') + rec_name = fields.Function(fields.Char('Name'), 'get_rec_name', + searcher='search_rec_name') def __init__(self): super(ModelStorage, self).__init__() @@ -437,7 +437,7 @@ class ModelStorage(Model): return domain return process(domain) - def get_rec_name(self, cursor, user, ids, name, arg, context=None): + def get_rec_name(self, cursor, user, ids, name, context=None): ''' Return a dictionary with id as key and rec_name as value. It is used by the Function field rec_name. @@ -446,7 +446,6 @@ class ModelStorage(Model): :param user: the user id :param ids: a list of ids :param name: the name of the Function field - :param arg: the argument of the Function field :param context: the context :return: a dictionary ''' diff --git a/trytond/res/ir.py b/trytond/res/ir.py index b9eff1bc64..7eb5505ae7 100644 --- a/trytond/res/ir.py +++ b/trytond/res/ir.py @@ -158,11 +158,10 @@ SequenceTypeGroup() class Sequence(ModelSQL, ModelView): _name = 'ir.sequence' - groups = fields.Function('get_groups', type='many2many', - relation='res.group', string='User Groups', - fnct_search='search_groups') + groups = fields.Function(fields.Many2Many('res.group', None, None, + 'User Groups'), 'get_groups', searcher='search_groups') - def get_groups(self, cursor, user, ids, name, arg, context=None): + def get_groups(self, cursor, user, ids, name, context=None): sequence_type_obj = self.pool.get('ir.sequence.type') sequences= self.browse(cursor, user, ids, context=context) code2seq = {} diff --git a/trytond/res/request.py b/trytond/res/request.py index df191bf758..8297d19aca 100644 --- a/trytond/res/request.py +++ b/trytond/res/request.py @@ -56,8 +56,8 @@ class Request(ModelSQL, ModelView): True, Not(Equal(Eval('act_from', 0), Eval('_user', 0)))), }) - number_references = fields.Function('get_number_references', type='integer', - string="Number of References", on_change_with=['references']) + number_references = fields.Function(fields.Integer('Number of References', + on_change_with=['references']), 'get_number_references') state = fields.Selection(_STATES, 'State', required=True, readonly=True) history = fields.One2Many('res.request.history', 'request', 'History', readonly=True) @@ -92,8 +92,7 @@ class Request(ModelSQL, ModelView): return len(vals['references']) return 0 - def get_number_references(self, cursor, user, ids, name, arg, - context=None): + def get_number_references(self, cursor, user, ids, name, context=None): res = {} for request in self.browse(cursor, user, ids, context=context): if request.references: diff --git a/trytond/res/user.py b/trytond/res/user.py index f448e732d5..a90488e94f 100644 --- a/trytond/res/user.py +++ b/trytond/res/user.py @@ -37,15 +37,14 @@ class User(ModelSQL, ModelView): domain=[('global_p', '!=', True), ('default_p', '!=', True)]) language = fields.Many2One('ir.lang', 'Language', domain=['OR', ('translatable', '=', True), ('code', '=', 'en_US')]) - language_direction = fields.Function('get_language_direction', type='char', - string='Language Direction') + language_direction = fields.Function(fields.Char('Language Direction'), + 'get_language_direction') timezone = fields.Selection('timezones', 'Timezone') email = fields.Char('Email') - status_bar = fields.Function('get_status_bar', type='char', - string="Status Bar") + status_bar = fields.Function(fields.Char('Status Bar'), 'get_status_bar') warnings = fields.One2Many('res.user.warning', 'user', 'Warnings') - connections = fields.Function('get_connections', type='integer', - string='Connections') + connections = fields.Function(fields.Integer('Connections'), + 'get_connections') def __init__(self): super(User, self).__init__() @@ -100,7 +99,7 @@ class User(ModelSQL, ModelView): def default_action(self, cursor, user, context=None): return self.default_menu(cursor, user, context=context) - def get_language_direction(self, cursor, user, ids, name, arg, context=None): + def get_language_direction(self, cursor, user, ids, name, context=None): res = {} lang_obj = self.pool.get('ir.lang') default_direction = lang_obj.default_direction(cursor, user, context=context) @@ -111,13 +110,13 @@ class User(ModelSQL, ModelView): res[user.id] = default_direction return res - def get_status_bar(self, cursor, user_id, ids, name, arg, context=None): + def get_status_bar(self, cursor, user_id, ids, name, context=None): res = {} for user in self.browse(cursor, user_id, ids, context=context): res[user.id] = user.name return res - def get_connections(self, cursor, user, ids, name, arg, context=None): + def get_connections(self, cursor, user, ids, name, context=None): res = {} for user_id in ids: res[user_id] = get_connections(cursor.database_name, user_id)