diff --git a/CHANGELOG b/CHANGELOG index c706f370e3..a24d2131ec 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Use a sequence of records, values in write * set_context of Transaction.set_user is restricted to root * Add a "copy" action to One2Many and Many2Many's set method * Force UTC as timezone (migration script available on tryton-tools) diff --git a/doc/ref/models/fields.rst b/doc/ref/models/fields.rst index b3ac9ffb6b..7af2715666 100644 --- a/doc/ref/models/fields.rst +++ b/doc/ref/models/fields.rst @@ -495,8 +495,8 @@ This field accepts as written value a list of tuples like this: - ``('create', [{: value, ...}, ...])``: it will create new target records and link them to this one. - - ``('write'[, ids, ...], {: value, ...})``: it will write - values to target ids. + - ``('write'[[, ids, ...], {: value, ...}, ...])``: it will + write values to target ids. - ``('delete'[, ids, ...])``: it will delete the target ids. diff --git a/doc/ref/models/models.rst b/doc/ref/models/models.rst index 54b092c802..345a06b892 100644 --- a/doc/ref/models/models.rst +++ b/doc/ref/models/models.rst @@ -238,7 +238,7 @@ CLass methods: Return a list of values for the ids. If ``fields_names`` is set, there will be only values for these fields otherwise it will be for all fields. -.. classmethod:: ModelStorage.write(records, values) +.. classmethod:: ModelStorage.write(records, values, [[records, values], ...]) Write ``values`` on the list of records. ``values`` is a dictionary with fields names as key and writen values as value. diff --git a/doc/ref/rpc.rst b/doc/ref/rpc.rst index cb7f2e3003..ba0e6f9ee8 100644 --- a/doc/ref/rpc.rst +++ b/doc/ref/rpc.rst @@ -17,7 +17,7 @@ Instance attributes are: .. attribute:: RPC.instantiate - The position of the argument to be instanciated + The position or the slice of the argument to be instanciated .. attribute:: RPC.result diff --git a/trytond/convert.py b/trytond/convert.py index 259db7f37a..36d5950ac8 100644 --- a/trytond/convert.py +++ b/trytond/convert.py @@ -402,6 +402,8 @@ class TrytondXmlHandler(sax.handler.ContentHandler): self.module_state = module_state self.grouped = None self.grouped_creations = defaultdict(dict) + self.grouped_write = defaultdict(list) + self.grouped_model_data = [] self.skip_data = False # Tag handlders are used to delegate the processing @@ -475,6 +477,13 @@ class TrytondXmlHandler(sax.handler.ContentHandler): for model, values in self.grouped_creations.iteritems(): self.create_records(model, values.values(), values.keys()) self.grouped_creations.clear() + for key, actions in self.grouped_write.iteritems(): + module, model = key + self.write_records(module, model, *actions) + self.grouped_write.clear() + if self.grouped_model_data: + self.ModelData.write(*self.grouped_model_data) + del self.grouped_model_data[:] # Closing tag found, if we are in a delegation the handler # know what to do: @@ -664,42 +673,12 @@ class TrytondXmlHandler(sax.handler.ContentHandler): # and no user changed the value in the db: to_update[key] = values[key] - # if there is values to update: - if to_update: - # write the values in the db: - with Transaction().set_context( - module=module, language='en_US'): - Model.write([record], to_update) - self.fs2db.reset_browsercord( - module, Model.__name__, [record.id]) - - if to_update: - # re-read it: this ensure that we store the real value - # in the model_data table: - record = self.fs2db.get_browserecord( - module, Model.__name__, record.id) - if not record: - record = Model(record.id) - for key in to_update: - values[key] = self._clean_value(key, record) - - if module != self.module: - temp_values = old_values.copy() - temp_values.update(values) - values = temp_values - - if values != old_values: - self.ModelData.write([self.ModelData(mdata_id)], { - 'fs_id': fs_id, - 'model': model, - 'module': module, - 'db_id': record.id, - 'values': str(values), - 'date_update': datetime.datetime.now(), - }) - # reset_browsercord to keep cache memory low - self.fs2db.reset_browsercord(module, Model.__name__, [record.id]) - + if self.grouped: + self.grouped_write[(module, model)].extend( + (record, to_update, old_values, fs_id, mdata_id)) + else: + self.write_records(module, model, record, to_update, + old_values, fs_id, mdata_id) else: if self.grouped: self.grouped_creations[model][fs_id] = values @@ -739,6 +718,58 @@ class TrytondXmlHandler(sax.handler.ContentHandler): self.fs2db.reset_browsercord(self.module, model, [r.id for r in records]) + def write_records(self, module, model, + record, values, old_values, fs_id, mdata_id, *args): + args = (record, values, old_values, fs_id, mdata_id) + args + Model = self.pool.get(model) + + actions = iter(args) + to_update = [] + for record, values, _, _, _ in zip(*((actions,) * 5)): + if values: + to_update += [[record], values] + # if there is values to update: + if to_update: + # write the values in the db: + with Transaction().set_context( + module=module, language='en_US'): + Model.write(*to_update) + self.fs2db.reset_browsercord( + module, Model.__name__, sum(to_update[::2], [])) + + actions = iter(to_update) + for records, values in zip(actions, actions): + record, = records + # re-read it: this ensure that we store the real value + # in the model_data table: + record = self.fs2db.get_browserecord( + module, Model.__name__, record.id) + if not record: + record = Model(record.id) + for key in values: + values[key] = self._clean_value(key, record) + + actions = iter(args) + for record, values, old_values, fs_id, mdata_id in zip( + *((actions,) * 5)): + if module != self.module: + temp_values = old_values.copy() + temp_values.update(values) + values = temp_values + + if values != old_values: + self.grouped_model_data.extend(([self.ModelData(mdata_id)], { + 'fs_id': fs_id, + 'model': model, + 'module': module, + 'db_id': record.id, + 'values': str(values), + 'date_update': datetime.datetime.now(), + })) + + # reset_browsercord to keep cache memory low + self.fs2db.reset_browsercord(module, Model.__name__, args[::5]) + def post_import(pool, module, to_delete): """ diff --git a/trytond/ir/action.py b/trytond/ir/action.py index 4821f9ac00..d7fe0abada 100644 --- a/trytond/ir/action.py +++ b/trytond/ir/action.py @@ -56,9 +56,9 @@ class Action(ModelSQL, ModelView): return True @classmethod - def write(cls, actions, values): + def write(cls, actions, values, *args): pool = Pool() - super(Action, cls).write(actions, values) + super(Action, cls).write(actions, values, *args) pool.get('ir.action.keyword')._get_keyword_cache.clear() @classmethod @@ -194,9 +194,12 @@ class ActionKeyword(ModelSQL, ModelView): return super(ActionKeyword, cls).create(new_vlist) @classmethod - def write(cls, keywords, vals): - vals = cls._convert_vals(vals) - super(ActionKeyword, cls).write(keywords, vals) + def write(cls, keywords, values, *args): + actions = iter((keywords, values) + args) + args = [] + for keywords, values in zip(actions, actions): + args.extend((keywords, cls._convert_vals(values))) + super(ActionKeyword, cls).write(*args) ModelView._fields_view_get_cache.clear() ModelView._view_toolbar_get_cache.clear() cls._get_keyword_cache.clear() @@ -329,10 +332,10 @@ class ActionMixin(ModelSQL): return new_records @classmethod - def write(cls, records, values): + def write(cls, records, values, *args): pool = Pool() ActionKeyword = pool.get('ir.action.keyword') - super(ActionMixin, cls).write(records, values) + super(ActionMixin, cls).write(records, values, *args) ActionKeyword._get_keyword_cache.clear() @classmethod @@ -645,13 +648,18 @@ class ActionReport(ActionMixin, ModelSQL, ModelView): return new_reports @classmethod - def write(cls, reports, vals): + def write(cls, reports, values, *args): context = Transaction().context if 'module' in context: - vals = vals.copy() - vals['module'] = context['module'] - - super(ActionReport, cls).write(reports, vals) + actions = iter((reports, values) + args) + args = [] + for reports, values in zip(actions, actions): + values = values.copy() + values['module'] = context['module'] + args.extend((reports, values)) + reports, values = args[:2] + args = args[2:] + super(ActionReport, cls).write(reports, values, *args) class ActionActWindow(ActionMixin, ModelSQL, ModelView): @@ -908,9 +916,9 @@ class ActionActWindowView(ModelSQL, ModelView): return windows @classmethod - def write(cls, windows, values): + def write(cls, windows, values, *args): pool = Pool() - super(ActionActWindowView, cls).write(windows, values) + super(ActionActWindowView, cls).write(windows, values, *args) pool.get('ir.action.keyword')._get_keyword_cache.clear() @classmethod diff --git a/trytond/ir/attachment.py b/trytond/ir/attachment.py index 09e340e817..dbb982e800 100644 --- a/trytond/ir/attachment.py +++ b/trytond/ir/attachment.py @@ -231,10 +231,14 @@ class Attachment(ModelSQL, ModelView): super(Attachment, cls).delete(attachments) @classmethod - def write(cls, attachments, vals): - cls.check_access([a.id for a in attachments], mode='write') - super(Attachment, cls).write(attachments, vals) - cls.check_access(attachments, mode='write') + def write(cls, attachments, values, *args): + all_attachments = [] + actions = iter((attachments, values) + args) + for records, _ in zip(actions, actions): + all_attachments += records + cls.check_access([a.id for a in all_attachments], mode='write') + super(Attachment, cls).write(attachments, values, *args) + cls.check_access(all_attachments, mode='write') @classmethod def create(cls, vlist): diff --git a/trytond/ir/lang.py b/trytond/ir/lang.py index d2270b93d5..a841274d38 100644 --- a/trytond/ir/lang.py +++ b/trytond/ir/lang.py @@ -213,12 +213,12 @@ class Lang(ModelSQL, ModelView): return languages @classmethod - def write(cls, langs, vals): + def write(cls, langs, values, *args): pool = Pool() Translation = pool.get('ir.translation') # Clear cache cls._lang_cache.clear() - super(Lang, cls).write(langs, vals) + super(Lang, cls).write(langs, values, *args) Translation._get_language_cache.clear() @classmethod diff --git a/trytond/ir/model.py b/trytond/ir/model.py index 72e7a284f2..1245180c24 100644 --- a/trytond/ir/model.py +++ b/trytond/ir/model.py @@ -139,10 +139,10 @@ class Model(ModelSQL, ModelView): return res @classmethod - def write(cls, models, vals): + def write(cls, models, values, *args): pool = Pool() Property = pool.get('ir.property') - super(Model, cls).write(models, vals) + super(Model, cls).write(models, values, *args) # Restart the cache of models_get Property._models_get_cache.clear() @@ -527,8 +527,8 @@ class ModelAccess(ModelSQL, ModelView): return True @classmethod - def write(cls, accesses, vals): - super(ModelAccess, cls).write(accesses, vals) + def write(cls, accesses, values, *args): + super(ModelAccess, cls).write(accesses, values, *args) # Restart the cache cls._get_access_cache.clear() ModelView._fields_view_get_cache.clear() @@ -681,8 +681,8 @@ class ModelFieldAccess(ModelSQL, ModelView): return True @classmethod - def write(cls, field_accesses, vals): - super(ModelFieldAccess, cls).write(field_accesses, vals) + def write(cls, field_accesses, values, *args): + super(ModelFieldAccess, cls).write(field_accesses, values, *args) # Restart the cache cls._get_access_cache.clear() ModelView._fields_view_get_cache.clear() @@ -730,8 +730,8 @@ class ModelButton(ModelSQL, ModelView): return result @classmethod - def write(cls, buttons, values): - super(ModelButton, cls).write(buttons, values) + def write(cls, buttons, values, *args): + super(ModelButton, cls).write(buttons, values, *args) # Restart the cache for get_groups cls._groups_cache.clear() @@ -813,8 +813,8 @@ class ModelData(ModelSQL, ModelView): return False @classmethod - def write(cls, data, values): - super(ModelData, cls).write(data, values) + def write(cls, data, values, *args): + super(ModelData, cls).write(data, values, *args) # Restart the cache for get_id cls._get_id_cache.clear() diff --git a/trytond/ir/rule.py b/trytond/ir/rule.py index 99c0f14e41..45138073ed 100644 --- a/trytond/ir/rule.py +++ b/trytond/ir/rule.py @@ -86,8 +86,8 @@ class RuleGroup(ModelSQL, ModelView): return res @classmethod - def write(cls, groups, vals): - super(RuleGroup, cls).write(groups, vals) + def write(cls, groups, vals, *args): + super(RuleGroup, cls).write(groups, vals, *args) # Restart the cache on the domain_get method of ir.rule Pool().get('ir.rule')._domain_get_cache.clear() @@ -276,7 +276,7 @@ class Rule(ModelSQL, ModelView): return res @classmethod - def write(cls, rules, vals): - super(Rule, cls).write(rules, vals) + def write(cls, rules, vals, *args): + super(Rule, cls).write(rules, vals, *args) # Restart the cache on the domain_get method cls._domain_get_cache.clear() diff --git a/trytond/ir/sequence.py b/trytond/ir/sequence.py index 8637f483d0..d1b3c73e63 100644 --- a/trytond/ir/sequence.py +++ b/trytond/ir/sequence.py @@ -184,12 +184,13 @@ class Sequence(ModelSQL, ModelView): return sequences @classmethod - def write(cls, sequences, values): - result = super(Sequence, cls).write(sequences, values) + def write(cls, sequences, values, *args): + super(Sequence, cls).write(sequences, values, *args) if sql_sequence and not cls._strict: - for sequence in sequences: - sequence.update_sql_sequence(values.get('number_next')) - return result + actions = iter((sequences, values) + args) + for sequences, values in zip(actions, actions): + for sequence in sequences: + sequence.update_sql_sequence(values.get('number_next')) @classmethod def delete(cls, sequences): diff --git a/trytond/ir/translation.py b/trytond/ir/translation.py index 3f13f68554..0f91fb1d2d 100644 --- a/trytond/ir/translation.py +++ b/trytond/ir/translation.py @@ -731,13 +731,17 @@ class Translation(ModelSQL, ModelView): return super(Translation, cls).create(vlist) @classmethod - def write(cls, translations, vals): + def write(cls, translations, values, *args): cls._translation_cache.clear() ModelView._fields_view_get_cache.clear() - if 'src' in vals: - vals = vals.copy() - vals['src_md5'] = cls.get_src_md5(vals.get('src')) - return super(Translation, cls).write(translations, vals) + actions = iter((translations, values) + args) + args = [] + for translations, values in zip(actions, actions): + if 'src' in values: + values = values.copy() + values['src_md5'] = cls.get_src_md5(values.get('src')) + args.extend((translations, values)) + return super(Translation, cls).write(*args) @classmethod def extra_model_data(cls, model_data): diff --git a/trytond/ir/trigger.py b/trytond/ir/trigger.py index a0c78c4c69..a821ca0fd0 100644 --- a/trytond/ir/trigger.py +++ b/trytond/ir/trigger.py @@ -270,11 +270,10 @@ class Trigger(ModelSQL, ModelView): return res @classmethod - def write(cls, ids, values): - res = super(Trigger, cls).write(ids, values) + def write(cls, triggers, values, *args): + super(Trigger, cls).write(triggers, values, *args) # Restart the cache on the get_triggers method of ir.trigger cls._get_triggers_cache.clear() - return res @classmethod def delete(cls, records): diff --git a/trytond/ir/ui/view.py b/trytond/ir/ui/view.py index 614d48f09b..5e16398080 100644 --- a/trytond/ir/ui/view.py +++ b/trytond/ir/ui/view.py @@ -196,8 +196,8 @@ class View(ModelSQL, ModelView): return views @classmethod - def write(cls, views, vals): - super(View, cls).write(views, vals) + def write(cls, views, values, *args): + super(View, cls).write(views, values, *args) # Restart the cache ModelView._fields_view_get_cache.clear() @@ -260,8 +260,8 @@ class ViewTreeWidth(ModelSQL, ModelView): return res @classmethod - def write(cls, records, vals): - super(ViewTreeWidth, cls).write(records, vals) + def write(cls, records, values, *args): + super(ViewTreeWidth, cls).write(records, values, *args) ModelView._fields_view_get_cache.clear() @classmethod diff --git a/trytond/model/fields/many2many.py b/trytond/model/fields/many2many.py index b6020f6342..d9d47c2840 100644 --- a/trytond/model/fields/many2many.py +++ b/trytond/model/fields/many2many.py @@ -101,7 +101,7 @@ class Many2Many(Field): values: A list of tuples: (``create``, ``[{: value}, ...]``), - (``write``, ````, ``{: value}``), + (``write``, [````, ``{: value}``, ...]), (``delete``, ````), (``delete_all``), (``unlink``, ````), @@ -142,7 +142,9 @@ class Many2Many(Field): if to_create: Relation.create(to_create) elif act[0] == 'write': - Target.write(Target.browse(act[1]), act[2]) + actions = iter(act[1:]) + Target.write(*sum(((Target.browse(ids), values) + for ids, values in zip(actions, actions)), ())) elif act[0] == 'delete': Target.delete(Target.browse(act[1])) elif act[0] == 'delete_all': diff --git a/trytond/model/fields/one2many.py b/trytond/model/fields/one2many.py index 4f7ba7f484..15fb2df235 100644 --- a/trytond/model/fields/one2many.py +++ b/trytond/model/fields/one2many.py @@ -107,7 +107,7 @@ class One2Many(Field): Set the values. values: A list of tuples: (``create``, ``[{: value}, ...]``), - (``write``, ````, ``{: value}``), + (``write``, [````, ``{: value}``, ...]), (``delete``, ````), (``delete_all``), (``unlink``, ````), @@ -146,7 +146,9 @@ class One2Many(Field): if to_create: Target.create(to_create) elif act[0] == 'write': - Target.write(Target.browse(act[1]), act[2]) + actions = iter(act[1:]) + Target.write(*sum(((Target.browse(ids), values) + for ids, values in zip(actions, actions)), ())) elif act[0] == 'delete': Target.delete(Target.browse(act[1])) elif act[0] == 'delete_all': @@ -169,10 +171,14 @@ class One2Many(Field): target_ids = map(int, act[1]) if not target_ids: continue + targets = Target.browse(target_ids) + actions = [] for record_id in ids: - Target.write(Target.browse(target_ids), { + actions.append(targets) + actions.append({ self.field: field_value(record_id), }) + Target.write(*actions) elif act[0] == 'unlink_all': targets = Target.search([ search_clause(ids), @@ -185,18 +191,22 @@ class One2Many(Field): target_ids = [-1] else: target_ids = map(int, act[1]) + actions = [] for record_id in ids: targets = Target.search([ search_clause([record_id]), ('id', 'not in', target_ids), ]) - Target.write(targets, { + actions.append(targets) + actions.append({ self.field: None, }) if act[1]: - Target.write(Target.browse(target_ids), { + actions.append(Target.browse(target_ids)) + actions.append({ self.field: field_value(record_id), }) + Target.write(*actions) elif act[0] == 'copy': copy_ids = map(int, act[1]) diff --git a/trytond/model/modelsingleton.py b/trytond/model/modelsingleton.py index 266e18700e..e1347f65c8 100644 --- a/trytond/model/modelsingleton.py +++ b/trytond/model/modelsingleton.py @@ -49,11 +49,15 @@ class ModelSingleton(ModelStorage): return res @classmethod - def write(cls, records, values): + def write(cls, records, values, *args): singleton = cls.get_singleton() if not singleton: - return cls.create([values]) - return super(ModelSingleton, cls).write([singleton], values) + singleton, = cls.create([values]) + actions = (records, values) + args + args = [] + for values in actions[1:None:2]: + args.extend(([singleton], values)) + return super(ModelSingleton, cls).write(*args) @classmethod def delete(cls, records): diff --git a/trytond/model/modelsql.py b/trytond/model/modelsql.py index ab68913822..756035cca0 100644 --- a/trytond/model/modelsql.py +++ b/trytond/model/modelsql.py @@ -629,94 +629,97 @@ class ModelSQL(ModelStorage): return result @classmethod - def write(cls, records, values): + def write(cls, records, values, *args): DatabaseIntegrityError = backend.get('DatabaseIntegrityError') transaction = Transaction() cursor = transaction.cursor pool = Pool() Translation = pool.get('ir.translation') Config = pool.get('ir.configuration') - ids = map(int, records) in_max = cursor.IN_MAX + assert not len(args) % 2 + all_records = sum(((records, values) + args)[0:None:2], []) + all_ids = [r.id for r in all_records] + # Call before cursor cache cleaning trigger_eligibles = cls.trigger_write_get_eligibles(records) - super(ModelSQL, cls).write(records, values) - - if not records: - return + super(ModelSQL, cls).write(records, values, *args) if cls.table_query(): return table = cls.__table__() - values = values.copy() + cls.__check_timestamp(all_ids) - cls.__check_timestamp(ids) + actions = iter((records, values) + args) + for records, values in zip(actions, actions): + ids = [r.id for r in records] + values = values.copy() - # Clean values - for key in ('create_uid', 'create_date', - 'write_uid', 'write_date', 'id'): - if key in values: - del values[key] + # Clean values + for key in ('create_uid', 'create_date', + 'write_uid', 'write_date', 'id'): + if key in values: + del values[key] - columns = [table.write_uid, table.write_date] - update_values = [transaction.user, Now()] - store_translation = Transaction().language == Config.get_language() - for fname, value in values.iteritems(): - field = cls._fields[fname] - if not hasattr(field, 'set'): - if not getattr(field, 'translate', False) or store_translation: - columns.append(Column(table, fname)) - update_values.append(field.sql_format(value)) + columns = [table.write_uid, table.write_date] + update_values = [transaction.user, Now()] + store_translation = Transaction().language == Config.get_language() + for fname, value in values.iteritems(): + field = cls._fields[fname] + if not hasattr(field, 'set'): + if (not getattr(field, 'translate', False) + or store_translation): + columns.append(Column(table, fname)) + update_values.append(field.sql_format(value)) - domain = pool.get('ir.rule').domain_get(cls.__name__, mode='write') - for i in range(0, len(ids), in_max): - sub_ids = ids[i:i + in_max] - red_sql = reduce_ids(table.id, sub_ids) - where = red_sql - if domain: - where &= table.id.in_(domain) - cursor.execute(*table.select(table.id, where=where)) - rowcount = cursor.rowcount - if rowcount == -1 or rowcount is None: - rowcount = len(cursor.fetchall()) - if not rowcount == len({}.fromkeys(sub_ids)): + domain = pool.get('ir.rule').domain_get(cls.__name__, mode='write') + for i in range(0, len(ids), in_max): + sub_ids = ids[i:i + in_max] + red_sql = reduce_ids(table.id, sub_ids) + where = red_sql if domain: - cursor.execute(*table.select(table.id, where=red_sql)) - rowcount = cursor.rowcount - if rowcount == -1 or rowcount is None: - rowcount = len(cursor.fetchall()) - if rowcount == len({}.fromkeys(sub_ids)): - cls.raise_user_error('access_error', cls.__name__) - cls.raise_user_error('write_error', cls.__name__) - try: - cursor.execute(*table.update(columns, update_values, - where=red_sql)) - except DatabaseIntegrityError, exception: - with contextlib.nested(Transaction().new_cursor(), - Transaction().set_user(0)): - cls.__raise_integrity_error(exception, values, - values.keys()) - raise + where &= table.id.in_(domain) + cursor.execute(*table.select(table.id, where=where)) + rowcount = cursor.rowcount + if rowcount == -1 or rowcount is None: + rowcount = len(cursor.fetchall()) + if not rowcount == len({}.fromkeys(sub_ids)): + if domain: + cursor.execute(*table.select(table.id, where=red_sql)) + rowcount = cursor.rowcount + if rowcount == -1 or rowcount is None: + rowcount = len(cursor.fetchall()) + if rowcount == len({}.fromkeys(sub_ids)): + cls.raise_user_error('access_error', cls.__name__) + cls.raise_user_error('write_error', cls.__name__) + try: + cursor.execute(*table.update(columns, update_values, + where=red_sql)) + except DatabaseIntegrityError, exception: + with contextlib.nested(Transaction().new_cursor(), + Transaction().set_user(0)): + cls.__raise_integrity_error(exception, values, + values.keys()) + raise - for fname, value in values.iteritems(): - field = cls._fields[fname] - if (getattr(field, 'translate', False) - and not hasattr(field, 'set')): - Translation.set_ids( - '%s,%s' % (cls.__name__, fname), 'model', - transaction.language, ids, [value] * len(ids)) - if hasattr(field, 'set'): - field.set(ids, cls, fname, value) + for fname, value in values.iteritems(): + field = cls._fields[fname] + if (getattr(field, 'translate', False) + and not hasattr(field, 'set')): + Translation.set_ids( + '%s,%s' % (cls.__name__, fname), 'model', + transaction.language, ids, [value] * len(ids)) + if hasattr(field, 'set'): + field.set(ids, cls, fname, value) - cls.__insert_history(ids) - cls._validate(records) - - field_names = cls._fields.keys() - cls._update_mptt(field_names, [ids] * len(field_names), values) + field_names = cls._fields.keys() + cls._update_mptt(field_names, [ids] * len(field_names), values) + cls.__insert_history(all_ids) + cls._validate(all_records) cls.trigger_write(trigger_eligibles) @classmethod diff --git a/trytond/model/modelstorage.py b/trytond/model/modelstorage.py index 48bc9347e6..c7ee59f390 100644 --- a/trytond/model/modelstorage.py +++ b/trytond/model/modelstorage.py @@ -53,7 +53,8 @@ class ModelStorage(Model): 'create': RPC(readonly=False, result=lambda r: map(int, r)), 'read': RPC(), - 'write': RPC(readonly=False, instantiate=0), + 'write': RPC(readonly=False, + instantiate=slice(0, None, 2)), 'delete': RPC(readonly=False, instantiate=0), 'copy': RPC(readonly=False, instantiate=0, result=lambda r: map(int, r)), @@ -126,7 +127,7 @@ class ModelStorage(Model): return [] @classmethod - def write(cls, records, values): + def write(cls, records, values, *args): ''' Write values on records. ''' @@ -137,15 +138,21 @@ class ModelStorage(Model): ModelAccess.check(cls.__name__, 'write') ModelFieldAccess.check(cls.__name__, [x for x in values if x in cls._fields], 'write') - if not cls.check_xml_record(records, values): - cls.raise_user_error('write_xml_record', - error_description='xml_record_desc') + + assert not len(args) % 2 + actions = iter((records, values) + args) + all_records = [] + for records, values in zip(actions, actions): + if not cls.check_xml_record(records, values): + cls.raise_user_error('write_xml_record', + error_description='xml_record_desc') + all_records += records # Increase transaction counter Transaction().counter += 1 # Clean local cache - for record in records: + for record in all_records: local_cache = record._local_cache.get(record.id) if local_cache: local_cache.clear() @@ -153,7 +160,7 @@ class ModelStorage(Model): # Clean cursor cache for cache in Transaction().cursor.cache.itervalues(): if cls.__name__ in cache: - for record in records: + for record in all_records: if record.id in cache[cls.__name__]: cache[cls.__name__][record.id].clear() diff --git a/trytond/res/group.py b/trytond/res/group.py index 936a2355bd..800b13d5c1 100644 --- a/trytond/res/group.py +++ b/trytond/res/group.py @@ -89,8 +89,8 @@ class Group(ModelSQL, ModelView): return res @classmethod - def write(cls, groups, vals): - super(Group, cls).write(groups, vals) + def write(cls, groups, values, *args): + super(Group, cls).write(groups, values, *args) pool = Pool() # Restart the cache on the domain_get method pool.get('ir.rule')._domain_get_cache.clear() diff --git a/trytond/res/ir.py b/trytond/res/ir.py index 784ca5343a..a867611a9d 100644 --- a/trytond/res/ir.py +++ b/trytond/res/ir.py @@ -44,8 +44,8 @@ class UIMenuGroup(ModelSQL): return res @classmethod - def write(cls, records, vals): - super(UIMenuGroup, cls).write(records, vals) + def write(cls, records, values, *args): + super(UIMenuGroup, cls).write(records, values, *args) # Restart the cache on the domain_get method Pool().get('ir.rule')._domain_get_cache.clear() @@ -91,12 +91,16 @@ class ActionGroup(ModelSQL): return res @classmethod - def write(cls, records, vals): + def write(cls, records, values, *args): Action = Pool().get('ir.action') - if vals.get('action'): - vals = vals.copy() - vals['action'] = Action.get_action_id(vals['action']) - super(ActionGroup, cls).write(records, vals) + actions = iter((records, values) + args) + args = [] + for records, values in zip(actions, actions): + if values.get('action'): + values = values.copy() + values['action'] = Action.get_action_id(values['action']) + args.extend((records, values)) + super(ActionGroup, cls).write(*args) # Restart the cache on the domain_get method Pool().get('ir.rule')._domain_get_cache.clear() @@ -153,9 +157,9 @@ class ModelButtonGroup(ModelSQL): return result @classmethod - def write(cls, records, values): + def write(cls, records, values, *args): pool = Pool() - super(ModelButtonGroup, cls).write(records, values) + super(ModelButtonGroup, cls).write(records, values, *args) # Restart the cache for get_groups pool.get('ir.model.button')._groups_cache.clear() @@ -219,8 +223,8 @@ class Lang: __name__ = 'ir.lang' @classmethod - def write(cls, langs, vals): - super(Lang, cls).write(langs, vals) + def write(cls, langs, values, *args): + super(Lang, cls).write(langs, values, *args) # Restart the cache for get_preferences Pool().get('res.user')._get_preferences_cache.clear() @@ -256,9 +260,9 @@ class SequenceTypeGroup(ModelSQL): return res @classmethod - def write(cls, records, vals): + def write(cls, records, values, *args): Rule = Pool().get('ir.rule') - super(SequenceTypeGroup, cls).write(records, vals) + super(SequenceTypeGroup, cls).write(records, values, *args) # Restart the cache on the domain_get method Rule._domain_get_cache.clear() @@ -314,10 +318,10 @@ class ModuleConfigWizardItem: return result @classmethod - def write(cls, items, values): + def write(cls, items, values, *args): pool = Pool() User = pool.get('res.user') - super(ModuleConfigWizardItem, cls).write(items, values) + super(ModuleConfigWizardItem, cls).write(items, values, *args) # Restart the cache for get_preferences User._get_preferences_cache.clear() diff --git a/trytond/res/user.py b/trytond/res/user.py index d3e714042e..8fd6c5274e 100644 --- a/trytond/res/user.py +++ b/trytond/res/user.py @@ -211,13 +211,18 @@ class User(ModelSQL, ModelView): return res @classmethod - def write(cls, users, vals): - vals = cls._convert_vals(vals) - super(User, cls).write(users, vals) + def write(cls, users, values, *args): + actions = iter((users, values) + args) + all_users = [] + args = [] + for users, values in zip(actions, actions): + all_users += users + args.extend((users, cls._convert_vals(values))) + super(User, cls).write(*args) # Clean cursor cache as it could be filled by domain_get for cache in Transaction().cursor.cache.itervalues(): if cls.__name__ in cache: - for user in users: + for user in all_users: if user.id in cache[cls.__name__]: cache[cls.__name__][user.id].clear() # Restart the cache for domain_get method @@ -532,9 +537,12 @@ class UserAction(ModelSQL): return super(UserAction, cls).create(vlist) @classmethod - def write(cls, records, values): - values = cls._convert_values(values) - super(UserAction, cls).write(records, values) + def write(cls, records, values, *args): + actions = iter((records, values) + args) + args = [] + for records, values in zip(actions, actions): + args.extend((records, cls._convert_values(values))) + super(UserAction, cls).write(*args) class UserGroup(ModelSQL): diff --git a/trytond/rpc.py b/trytond/rpc.py index afa0827c92..1e3e2a93f3 100644 --- a/trytond/rpc.py +++ b/trytond/rpc.py @@ -8,7 +8,7 @@ class RPC(object): '''Define RPC behavior readonly: The transaction mode - instantiate: The position of the argument to be instanciated + instantiate: The position or the slice of the arguments to be instanciated result: The function to transform the result ''' @@ -41,6 +41,12 @@ class RPC(object): return obj(**data) else: return obj.browse(data) - data = args[self.instantiate] - args[self.instantiate] = instance(data) + if isinstance(self.instantiate, slice): + for i, data in enumerate(args[self.instantiate]): + start, _, step = self.instantiate.indices(len(args)) + i = i * step + start + args[i] = instance(data) + else: + data = args[self.instantiate] + args[self.instantiate] = instance(data) return args, kwargs, context, timestamp