proteus 7.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
proteus/__init__.py ADDED
@@ -0,0 +1,1421 @@
1
+ # This file is part of Tryton. The COPYRIGHT file at the top level of
2
+ # this repository contains the full copyright notices and license terms.
3
+ '''
4
+ A library to access Tryton's models like a client.
5
+ '''
6
+ import datetime
7
+ import functools
8
+ import threading
9
+ from decimal import Decimal
10
+ from functools import total_ordering
11
+
12
+ import proteus.config
13
+
14
+ __version__ = "7.8.0"
15
+ __all__ = ['Model', 'Wizard', 'Report']
16
+
17
+ _MODELS = threading.local()
18
+
19
+
20
+ class _EvalEnvironment(dict):
21
+ 'Dictionary for evaluation'
22
+ __slots__ = ('parent', 'eval_type')
23
+
24
+ def __init__(self, parent, eval_type='eval'):
25
+ super().__init__()
26
+ self.parent = parent
27
+ assert eval_type in ('eval', 'on_change')
28
+ self.eval_type = eval_type
29
+
30
+ def __getitem__(self, item):
31
+ if item == 'id':
32
+ return self.parent.id
33
+ if item == '_parent_' + self.parent._parent_name \
34
+ and self.parent._parent:
35
+ return _EvalEnvironment(self.parent._parent,
36
+ eval_type=self.eval_type)
37
+ if self.eval_type == 'eval':
38
+ return self.parent._get_eval()[item]
39
+ else:
40
+ return self.parent._get_on_change_values(fields=[item])[item]
41
+
42
+ def __getattr__(self, item):
43
+ try:
44
+ return self.__getitem__(item)
45
+ except KeyError:
46
+ raise AttributeError(item)
47
+
48
+ def get(self, item, default=None):
49
+ try:
50
+ return self.__getitem__(item)
51
+ except KeyError:
52
+ pass
53
+ return super().get(item, default)
54
+
55
+ def __bool__(self):
56
+ return True
57
+
58
+ def __str__(self):
59
+ return str(self.parent)
60
+
61
+ def __repr__(self):
62
+ return repr(self.parent)
63
+
64
+ def __contains__(self, item):
65
+ if item == 'id':
66
+ return True
67
+ if item == '_parent_' + self.parent._parent_name \
68
+ and self.parent._parent:
69
+ return True
70
+ if self.eval_type == 'eval':
71
+ return item in self.parent._get_eval()
72
+ else:
73
+ return item in self.parent._fields
74
+
75
+
76
+ class dualmethod(object):
77
+ """Descriptor implementing combination of class and instance method
78
+
79
+ When called on an instance, the class is passed as the first argument and a
80
+ list with the instance as the second.
81
+ When called on a class, the class itself is passed as the first argument.
82
+
83
+ >>> class Example(object):
84
+ ... @dualmethod
85
+ ... def method(cls, instances):
86
+ ... print(len(instances))
87
+ ...
88
+ >>> Example.method([Example()])
89
+ 1
90
+ >>> Example().method()
91
+ 1
92
+ """
93
+ __slots__ = ('func')
94
+
95
+ def __init__(self, func):
96
+ self.func = func
97
+
98
+ def __get__(self, instance, owner):
99
+
100
+ @functools.wraps(self.func)
101
+ def newfunc(*args, **kwargs):
102
+ if instance:
103
+ return self.func(owner, [instance], *args, **kwargs)
104
+ else:
105
+ return self.func(owner, *args, **kwargs)
106
+ return newfunc
107
+
108
+
109
+ class FieldDescriptor(object):
110
+ default = None
111
+
112
+ def __init__(self, name, definition):
113
+ super().__init__()
114
+ self.name = name
115
+ self.definition = definition
116
+ self.__doc__ = definition['string']
117
+
118
+ def __get__(self, instance, owner):
119
+ if instance.id >= 0:
120
+ instance._read(self.name)
121
+ return instance._values.get(self.name, self.default)
122
+
123
+ def __set__(self, instance, value):
124
+ if instance.id >= 0:
125
+ instance._read(self.name)
126
+ previous = getattr(instance, self.name)
127
+ instance._values[self.name] = value
128
+ if previous != getattr(instance, self.name):
129
+ instance._changed.add(self.name)
130
+ instance._on_change([self.name])
131
+ if instance._parent:
132
+ instance._parent._changed.add(instance._parent_field_name)
133
+ instance._parent._on_change([instance._parent_field_name])
134
+
135
+
136
+ class BooleanDescriptor(FieldDescriptor):
137
+ default = False
138
+
139
+ def __set__(self, instance, value):
140
+ assert isinstance(value, bool)
141
+ super().__set__(instance, value)
142
+
143
+
144
+ class CharDescriptor(FieldDescriptor):
145
+ default = None
146
+
147
+ def __set__(self, instance, value):
148
+ assert isinstance(value, str) or value is None
149
+ if self.definition.get('strip') and value:
150
+ if self.definition['strip'] == 'leading':
151
+ value = value.lstrip()
152
+ elif self.definition['strip'] == 'trailing':
153
+ value = value.rstrip()
154
+ else:
155
+ value = value.strip()
156
+ super().__set__(instance, value or '')
157
+
158
+
159
+ class BinaryDescriptor(FieldDescriptor):
160
+ default = None
161
+
162
+ def __set__(self, instance, value):
163
+ assert isinstance(value, (bytes, bytearray)) or value is None
164
+ super().__set__(instance, value)
165
+
166
+
167
+ class IntegerDescriptor(FieldDescriptor):
168
+
169
+ def __set__(self, instance, value):
170
+ assert isinstance(value, (int, type(None)))
171
+ super().__set__(instance, value)
172
+
173
+
174
+ class FloatDescriptor(FieldDescriptor):
175
+
176
+ def __set__(self, instance, value):
177
+ assert isinstance(value, (int, float, Decimal, type(None)))
178
+ if value is not None:
179
+ value = float(value)
180
+ super().__set__(instance, value)
181
+
182
+
183
+ class NumericDescriptor(FieldDescriptor):
184
+
185
+ def __set__(self, instance, value):
186
+ assert isinstance(value, (type(None), Decimal))
187
+ # TODO add digits validation
188
+ super().__set__(instance, value)
189
+
190
+
191
+ class SelectionDescriptor(FieldDescriptor):
192
+
193
+ def __set__(self, instance, value):
194
+ assert isinstance(value, str) or value is None
195
+ # TODO add selection validation
196
+ super().__set__(instance, value)
197
+
198
+
199
+ class MultiSelectionDescriptor(FieldDescriptor):
200
+
201
+ def __set__(self, instance, value):
202
+ assert isinstance(value, (list, type(None)))
203
+ # TODO add selection validation
204
+ super().__set__(instance, value)
205
+
206
+
207
+ class ReferenceDescriptor(FieldDescriptor):
208
+ def __get__(self, instance, owner):
209
+ value = super().__get__(instance, owner)
210
+ if isinstance(value, str):
211
+ model_name, id = value.split(',', 1)
212
+ if model_name:
213
+ Relation = Model.get(model_name, instance._config)
214
+ config = Relation._config
215
+ with config.reset_context(), \
216
+ config.set_context(instance._context):
217
+ value = Relation(int(id))
218
+ instance._values[self.name] = value
219
+ return value
220
+
221
+ def __set__(self, instance, value):
222
+ assert isinstance(value, (Model, type(None), str))
223
+ if isinstance(value, str):
224
+ assert value.startswith(',')
225
+ elif isinstance(value, Model):
226
+ assert value._config == instance._config
227
+ super().__set__(instance, value)
228
+
229
+
230
+ class DateDescriptor(FieldDescriptor):
231
+ def __get__(self, instance, owner):
232
+ value = super().__get__(instance, owner)
233
+ if isinstance(value, datetime.datetime):
234
+ value = value.date()
235
+ instance._values[self.name] = value
236
+ return value
237
+
238
+ def __set__(self, instance, value):
239
+ assert isinstance(value, datetime.date) or value is None
240
+ super().__set__(instance, value)
241
+
242
+
243
+ class DateTimeDescriptor(FieldDescriptor):
244
+ def __set__(self, instance, value):
245
+ assert isinstance(value, datetime.datetime) or value is None
246
+ super().__set__(instance, value)
247
+
248
+
249
+ class TimeDescriptor(FieldDescriptor):
250
+ def __set__(self, instance, value):
251
+ assert isinstance(value, datetime.time) or value is None
252
+ super().__set__(instance, value)
253
+
254
+
255
+ class TimeDeltaDescriptor(FieldDescriptor):
256
+ def __set__(self, instance, value):
257
+ assert isinstance(value, datetime.timedelta) or value is None
258
+ super().__set__(instance, value)
259
+
260
+
261
+ class DictDescriptor(FieldDescriptor):
262
+ def __get__(self, instance, owner):
263
+ value = super().__get__(instance, owner)
264
+ if value:
265
+ value = value.copy()
266
+ return value
267
+
268
+ def __set__(self, instance, value):
269
+ assert isinstance(value, dict) or value is None
270
+ super().__set__(instance, value)
271
+
272
+
273
+ class Many2OneDescriptor(FieldDescriptor):
274
+ def __get__(self, instance, owner):
275
+ Relation = Model.get(self.definition['relation'], instance._config)
276
+ value = super().__get__(instance, owner)
277
+ if isinstance(value, int):
278
+ config = Relation._config
279
+ with config.reset_context(), config.set_context(instance._context):
280
+ value = Relation(value)
281
+ if self.name in instance._values:
282
+ instance._values[self.name] = value
283
+ return value
284
+
285
+ def __set__(self, instance, value):
286
+ assert isinstance(value, (Model, type(None)))
287
+ if value:
288
+ assert value._config == instance._config
289
+ super().__set__(instance, value)
290
+
291
+
292
+ class One2OneDescriptor(Many2OneDescriptor):
293
+ pass
294
+
295
+
296
+ class One2ManyDescriptor(FieldDescriptor):
297
+ default = []
298
+
299
+ def __get__(self, instance, owner):
300
+ from .pyson import PYSONDecoder
301
+ Relation = Model.get(self.definition['relation'], instance._config)
302
+ value = super().__get__(instance, owner)
303
+ if not isinstance(value, ModelList):
304
+ ctx = instance._context.copy() if instance._context else {}
305
+ if self.definition.get('context'):
306
+ decoder = PYSONDecoder(_EvalEnvironment(instance))
307
+ ctx.update(decoder.decode(self.definition.get('context')))
308
+ config = Relation._config
309
+ with config.reset_context(), config.set_context(ctx):
310
+ value = ModelList(self.definition, (Relation(id)
311
+ for id in value or []), instance, self.name)
312
+ instance._values[self.name] = value
313
+ return value
314
+
315
+ def __set__(self, instance, value):
316
+ raise AttributeError
317
+
318
+
319
+ class Many2ManyDescriptor(One2ManyDescriptor):
320
+ pass
321
+
322
+
323
+ class ValueDescriptor(object):
324
+ def __init__(self, name, definition):
325
+ super().__init__()
326
+ self.name = name
327
+ self.definition = definition
328
+
329
+ def __get__(self, instance, owner):
330
+ return getattr(instance, self.name)
331
+
332
+
333
+ class ReferenceValueDescriptor(ValueDescriptor):
334
+ def __get__(self, instance, owner):
335
+ value = super().__get__(instance, owner)
336
+ if isinstance(value, Model):
337
+ value = '%s,%s' % (value.__class__.__name__, value.id)
338
+ return value or None
339
+
340
+
341
+ class Many2OneValueDescriptor(ValueDescriptor):
342
+ def __get__(self, instance, owner):
343
+ value = super().__get__(instance, owner)
344
+ return value and value.id or None
345
+
346
+
347
+ class One2OneValueDescriptor(Many2OneValueDescriptor):
348
+ pass
349
+
350
+
351
+ class One2ManyValueDescriptor(ValueDescriptor):
352
+ def __get__(self, instance, owner):
353
+ value = []
354
+ value_list = getattr(instance, self.name)
355
+ parent_name = self.definition.get('relation_field', '')
356
+ to_add = []
357
+ to_create = []
358
+ to_write = []
359
+ for record in value_list:
360
+ if record.id >= 0:
361
+ values = record._get_values(fields=record._changed)
362
+ values.pop(parent_name, None)
363
+ if record._changed and values:
364
+ to_write.extend(([record.id], values))
365
+ to_add.append(record.id)
366
+ else:
367
+ values = record._get_values()
368
+ values.pop(parent_name, None)
369
+ to_create.append(values)
370
+ if to_add:
371
+ value.append(('add', to_add))
372
+ if to_create:
373
+ value.append(('create', to_create))
374
+ if to_write:
375
+ value.append(('write',) + tuple(to_write))
376
+ if value_list.record_removed:
377
+ value.append(('remove', [x.id for x in value_list.record_removed]))
378
+ if value_list.record_deleted:
379
+ value.append(('delete', [x.id for x in value_list.record_deleted]))
380
+ return value
381
+
382
+
383
+ class Many2ManyValueDescriptor(One2ManyValueDescriptor):
384
+ pass
385
+
386
+
387
+ class EvalDescriptor(object):
388
+ def __init__(self, name, definition):
389
+ super().__init__()
390
+ self.name = name
391
+ self.definition = definition
392
+
393
+ def __get__(self, instance, owner):
394
+ return getattr(instance, self.name)
395
+
396
+
397
+ class ReferenceEvalDescriptor(EvalDescriptor):
398
+ def __get__(self, instance, owner):
399
+ value = super().__get__(instance, owner)
400
+ if isinstance(value, Model):
401
+ value = '%s,%s' % (value.__class__.__name__, value.id)
402
+ return value or None
403
+
404
+
405
+ class Many2OneEvalDescriptor(EvalDescriptor):
406
+ def __get__(self, instance, owner):
407
+ value = super().__get__(instance, owner)
408
+ if value:
409
+ return value.id
410
+ return None
411
+
412
+
413
+ class One2OneEvalDescriptor(Many2OneEvalDescriptor):
414
+ pass
415
+
416
+
417
+ class One2ManyEvalDescriptor(EvalDescriptor):
418
+ def __get__(self, instance, owner):
419
+ # Directly use _values to prevent infinite recursion with
420
+ # One2ManyDescriptor which could evaluate this field to decode the
421
+ # context
422
+ value = instance._values.get(self.name, [])
423
+ if isinstance(value, ModelList):
424
+ return [x.id for x in value]
425
+ else:
426
+ return value
427
+
428
+
429
+ class Many2ManyEvalDescriptor(One2ManyEvalDescriptor):
430
+ pass
431
+
432
+
433
+ class MetaModelFactory(object):
434
+ descriptors = {
435
+ 'boolean': BooleanDescriptor,
436
+ 'char': CharDescriptor,
437
+ 'text': CharDescriptor,
438
+ 'binary': BinaryDescriptor,
439
+ 'selection': SelectionDescriptor,
440
+ 'multiselection': MultiSelectionDescriptor,
441
+ 'integer': IntegerDescriptor,
442
+ 'biginteger': IntegerDescriptor,
443
+ 'float': FloatDescriptor,
444
+ 'numeric': NumericDescriptor,
445
+ 'reference': ReferenceDescriptor,
446
+ 'date': DateDescriptor,
447
+ 'datetime': DateTimeDescriptor,
448
+ 'timestamp': DateTimeDescriptor,
449
+ 'time': TimeDescriptor,
450
+ 'timedelta': TimeDeltaDescriptor,
451
+ 'dict': DictDescriptor,
452
+ 'many2one': Many2OneDescriptor,
453
+ 'one2many': One2ManyDescriptor,
454
+ 'many2many': Many2ManyDescriptor,
455
+ 'one2one': One2OneDescriptor,
456
+ }
457
+ value_descriptors = {
458
+ 'reference': ReferenceValueDescriptor,
459
+ 'many2one': Many2OneValueDescriptor,
460
+ 'one2many': One2ManyValueDescriptor,
461
+ 'many2many': Many2ManyValueDescriptor,
462
+ 'one2one': One2OneValueDescriptor,
463
+ }
464
+ eval_descriptors = {
465
+ 'reference': ReferenceEvalDescriptor,
466
+ 'many2one': Many2OneEvalDescriptor,
467
+ 'one2many': One2ManyEvalDescriptor,
468
+ 'many2many': Many2ManyEvalDescriptor,
469
+ 'one2one': One2OneEvalDescriptor,
470
+ }
471
+
472
+ def __init__(self, model_name, config=None):
473
+ super().__init__()
474
+ self.model_name = model_name
475
+ self.config = config or proteus.config.get_config()
476
+
477
+ def __call__(self):
478
+ models_key = 'c%su%s' % (id(self.config), self.config.user)
479
+ if not hasattr(_MODELS, models_key):
480
+ setattr(_MODELS, models_key, {})
481
+
482
+ class MetaModel(type):
483
+ 'Meta class for Model'
484
+ __slots__ = ()
485
+
486
+ def __new__(mcs, name, bases, dict):
487
+ if self.model_name in getattr(_MODELS, models_key):
488
+ return getattr(_MODELS, models_key)[self.model_name]
489
+ proxy = self.config.get_proxy(self.model_name)
490
+ context = self.config.context
491
+ name = self.model_name
492
+ dict['_proxy'] = proxy
493
+ dict['_config'] = self.config
494
+ dict['_fields'] = proxy.fields_get(None, context)
495
+ for field_name, definition in dict['_fields'].items():
496
+ if field_name == 'id':
497
+ continue
498
+ Descriptor = self.descriptors.get(definition['type'],
499
+ FieldDescriptor)
500
+ dict[field_name] = Descriptor(field_name, definition)
501
+ VDescriptor = self.value_descriptors.get(
502
+ definition['type'], ValueDescriptor)
503
+ dict['__%s_value' % field_name] = VDescriptor(
504
+ field_name, definition)
505
+ EDescriptor = self.eval_descriptors.get(
506
+ definition['type'], EvalDescriptor)
507
+ dict['__%s_eval' % field_name] = EDescriptor(
508
+ field_name, definition)
509
+ for method in self.config.get_proxy_methods(self.model_name):
510
+ setattr(mcs, method, getattr(proxy, method))
511
+ res = type.__new__(mcs, name, bases, dict)
512
+ getattr(_MODELS, models_key)[self.model_name] = res
513
+ return res
514
+ __new__.__doc__ = type.__new__.__doc__
515
+ return MetaModel
516
+
517
+
518
+ class ModelList(list):
519
+ 'List for Model'
520
+ __slots__ = ('model_name', 'parent', 'parent_field_name', 'parent_name',
521
+ 'domain', 'context', 'add_remove', 'search_order', 'search_context',
522
+ 'record_removed', 'record_deleted')
523
+
524
+ def __init__(self, definition, sequence=None, parent=None,
525
+ parent_field_name=''):
526
+ self.model_name = definition['relation']
527
+ if sequence is None:
528
+ sequence = []
529
+ self.parent = parent
530
+ if parent:
531
+ assert parent_field_name
532
+ self.parent_field_name = parent_field_name
533
+ self.parent_name = definition.get('relation_field', '')
534
+ self.domain = definition.get('domain', [])
535
+ self.context = definition.get('context')
536
+ self.add_remove = definition.get('add_remove')
537
+ self.search_order = definition.get('search_order', 'null')
538
+ self.search_context = definition.get('search_context', '{}')
539
+ self.record_removed = set()
540
+ self.record_deleted = set()
541
+ result = super().__init__(sequence)
542
+ assert len(self) == len(set(self))
543
+ self.__check(self, on_change=False)
544
+ return result
545
+ __init__.__doc__ = list.__init__.__doc__
546
+
547
+ def _changed(self):
548
+ 'Signal change to parent'
549
+ if self.parent:
550
+ self.parent._changed.add(self.parent_field_name)
551
+ self.parent._on_change([self.parent_field_name])
552
+
553
+ def _get_context(self):
554
+ from .pyson import PYSONDecoder
555
+ decoder = PYSONDecoder(_EvalEnvironment(self.parent))
556
+ ctx = self.parent._context.copy() if self.parent._context else {}
557
+ ctx.update(decoder.decode(self.context) if self.context else {})
558
+ return ctx
559
+
560
+ def __check(self, records, on_change=True):
561
+ config = None
562
+ for record in records:
563
+ assert isinstance(record, Model)
564
+ assert record.__class__.__name__ == self.model_name
565
+ if self.parent:
566
+ assert record._config == self.parent._config
567
+ elif self:
568
+ assert record._config == self[0]._config
569
+ elif config:
570
+ assert record._config == config
571
+ else:
572
+ config = record._config
573
+ if record._group is not self:
574
+ assert record._group is None
575
+ record._group = self
576
+ for record in records:
577
+ # Set parent field to trigger on_change
578
+ if (on_change
579
+ and self.parent
580
+ and self.parent_name in record._fields):
581
+ definition = record._fields[self.parent_name]
582
+ if definition['type'] in ('many2one', 'reference'):
583
+ setattr(record, self.parent_name, self.parent)
584
+ self.record_removed.difference_update(records)
585
+ self.record_deleted.difference_update(records)
586
+
587
+ def append(self, record):
588
+ self.__check([record])
589
+ if record not in self:
590
+ super().append(record)
591
+ self._changed()
592
+ append.__doc__ = list.append.__doc__
593
+
594
+ def extend(self, iterable):
595
+ iterable = list(iterable)
596
+ self.__check(iterable)
597
+ set_ = set(self)
598
+ super().extend(r for r in iterable if r not in set_)
599
+ self._changed()
600
+ extend.__doc__ = list.extend.__doc__
601
+
602
+ def insert(self, index, record):
603
+ raise NotImplementedError
604
+ insert.__doc__ = list.insert.__doc__
605
+
606
+ def pop(self, index=-1, _changed=True):
607
+ self.record_removed.add(self[index])
608
+ self[index]._group = None
609
+ res = super().pop(index)
610
+ if _changed:
611
+ self._changed()
612
+ return res
613
+ pop.__doc__ = list.pop.__doc__
614
+
615
+ def remove(self, record, _changed=True):
616
+ if record.id >= 0:
617
+ self.record_deleted.add(record)
618
+ record._group = None
619
+ res = super().remove(record)
620
+ if _changed:
621
+ self._changed()
622
+ return res
623
+ remove.__doc__ = list.remove.__doc__
624
+
625
+ def reverse(self):
626
+ raise NotImplementedError
627
+ reverse.__doc__ = list.reverse.__doc__
628
+
629
+ def sort(self):
630
+ raise NotImplementedError
631
+ sort.__doc__ = list.sort.__doc__
632
+
633
+ def new(self, **kwargs):
634
+ 'Adds a new record to the ModelList and returns it'
635
+ Relation = Model.get(self.model_name, self.parent._config)
636
+ config = Relation._config
637
+ with config.reset_context(), config.set_context(self._get_context()):
638
+ # Set parent for on_change calls from default_get
639
+ new_record = Relation(_group=self, **kwargs)
640
+ self.append(new_record)
641
+ return new_record
642
+
643
+ def find(self, condition=None, offset=0, limit=None, order=None):
644
+ 'Returns records matching condition taking into account list domain'
645
+ from .pyson import PYSONDecoder
646
+ decoder = PYSONDecoder(_EvalEnvironment(self.parent))
647
+ Relation = Model.get(self.model_name, self.parent._config)
648
+ if condition is None:
649
+ condition = []
650
+ field_domain = decoder.decode(self.domain)
651
+ add_remove_domain = (decoder.decode(self.add_remove)
652
+ if self.add_remove else [])
653
+ new_domain = [field_domain, add_remove_domain, condition]
654
+ context = self._get_context()
655
+ context.update(decoder.decode(self.search_context))
656
+ order = order if order else decoder.decode(self.search_order)
657
+ config = Relation._config
658
+ with config.reset_context(), config.set_context(context):
659
+ return Relation.find(new_domain, offset, limit, order)
660
+
661
+ def set_sequence(self, field='sequence'):
662
+ changed = False
663
+ prev = None
664
+ for record in self:
665
+ if prev:
666
+ index = getattr(prev, field)
667
+ else:
668
+ index = None
669
+ update = False
670
+ value = getattr(record, field)
671
+ if value is None:
672
+ if index:
673
+ update = True
674
+ elif prev and record.id >= 0:
675
+ update = record.id < prev.id
676
+ elif value == index:
677
+ if prev and record.id >= 0:
678
+ update = record.id < prev.id
679
+ elif value <= (index or 0):
680
+ update = True
681
+ if update:
682
+ if index is None:
683
+ index = 0
684
+ index += 1
685
+ setattr(record, field, index)
686
+ changed = record
687
+ prev = record
688
+ if changed:
689
+ self._changed()
690
+
691
+
692
+ @total_ordering
693
+ class Model(object):
694
+ 'Model class for Tryton records'
695
+ __slots__ = ('__id', '_values', '_changed', '_group', '__context')
696
+
697
+ __counter = -1
698
+ _proxy = None
699
+ _config = None
700
+ _fields = None
701
+
702
+ def __init__(self, id=None, _default=True, _group=None, **kwargs):
703
+ super().__init__()
704
+ if id is not None:
705
+ assert not kwargs
706
+ self.__id = id if id is not None else Model.__counter
707
+ if self.__id < 0:
708
+ Model.__counter -= 1
709
+ self._values = {} # store the values of fields
710
+ self._changed = set() # store the changed fields
711
+ self._group = _group # store the parent group
712
+ self.__context = self._config.context # store the context
713
+ if self.id < 0 and _default:
714
+ self._default_get()
715
+
716
+ for field_name, value in kwargs.items():
717
+ definition = self._fields[field_name]
718
+ if definition['type'] in ('one2many', 'many2many'):
719
+ relation = Model.get(definition['relation'], self._config)
720
+
721
+ def instantiate(v):
722
+ if isinstance(v, int):
723
+ return relation(v)
724
+ elif isinstance(v, dict):
725
+ return relation(_default=_default, **v)
726
+ else:
727
+ return v
728
+ value = [instantiate(x) for x in value]
729
+ getattr(self, field_name).extend(value)
730
+ else:
731
+ if definition['type'] == 'many2one':
732
+ if isinstance(value, int):
733
+ relation = Model.get(
734
+ definition['relation'], self._config)
735
+ value = relation(value)
736
+ setattr(self, field_name, value)
737
+ __init__.__doc__ = object.__init__.__doc__
738
+
739
+ @property
740
+ def _parent(self):
741
+ if self._group is not None:
742
+ return self._group.parent
743
+
744
+ @property
745
+ def _parent_field_name(self):
746
+ if self._group is not None:
747
+ return self._group.parent_field_name
748
+ return ''
749
+
750
+ @property
751
+ def _parent_name(self):
752
+ if self._group is not None:
753
+ return self._group.parent_name
754
+ return ''
755
+
756
+ @property
757
+ def _context(self):
758
+ if self._group:
759
+ context = self._group._get_context()
760
+ else:
761
+ context = self.__context
762
+ return context
763
+
764
+ @classmethod
765
+ def get(cls, name, config=None):
766
+ 'Get a class for the named Model'
767
+ if (bytes == str) and isinstance(name, str):
768
+ name = name.encode('utf-8')
769
+
770
+ class Spam(Model, metaclass=MetaModelFactory(name, config=config)()):
771
+ __slots__ = ()
772
+ return Spam
773
+
774
+ @classmethod
775
+ def reset(cls, config=None, *names):
776
+ 'Reset class definition for Models named'
777
+ config = config or proteus.config.get_config()
778
+ models_key = 'c%su%s' % (id(config), config.user)
779
+ if not names:
780
+ setattr(_MODELS, models_key, {})
781
+ else:
782
+ models = getattr(_MODELS, models_key, {})
783
+ for name in names:
784
+ del models[name]
785
+
786
+ def __str__(self):
787
+ return '%s,%d' % (self.__class__.__name__, self.id)
788
+
789
+ def __repr__(self):
790
+ if self._config == proteus.config.get_config():
791
+ return "proteus.Model.get('%s')(%d)" % (self.__class__.__name__,
792
+ self.id)
793
+ return "proteus.Model.get('%s', %s)(%d)" % (self.__class__.__name__,
794
+ repr(self._config), self.id)
795
+
796
+ def __eq__(self, other):
797
+ if isinstance(other, Model):
798
+ return ((self.__class__.__name__, self.id)
799
+ == (other.__class__.__name__, other.id))
800
+ return NotImplemented
801
+
802
+ def __lt__(self, other):
803
+ if not isinstance(other, Model) or self.__class__ != other.__class__:
804
+ return NotImplemented
805
+ return self.id < other.id
806
+
807
+ def __hash__(self):
808
+ return hash(self.__class__.__name__) ^ hash(self.id)
809
+
810
+ def __int__(self):
811
+ return self.id
812
+
813
+ @property
814
+ def id(self):
815
+ 'The unique ID'
816
+ return self.__id
817
+
818
+ @id.setter
819
+ def id(self, value):
820
+ assert self.__id < 0
821
+ self.__id = int(value)
822
+
823
+ @classmethod
824
+ def find(cls, condition=None, offset=0, limit=None, order=None):
825
+ 'Return records matching condition'
826
+ if condition is None:
827
+ condition = []
828
+ ids = cls._proxy.search(condition, offset, limit, order,
829
+ cls._config.context)
830
+ return [cls(id) for id in ids]
831
+
832
+ @dualmethod
833
+ def reload(cls, records):
834
+ 'Reload record'
835
+ for record in records:
836
+ record._values = {}
837
+ record._changed = set()
838
+
839
+ @dualmethod
840
+ def save(cls, records):
841
+ 'Save records'
842
+ if not records:
843
+ return
844
+ proxy = records[0]._proxy
845
+ config = records[0]._config
846
+ context = records[0]._context.copy()
847
+ create, write = [], []
848
+ for record in records:
849
+ assert proxy == record._proxy
850
+ assert config == record._config
851
+ assert context == record._context
852
+ if record.id < 0:
853
+ create.append(record)
854
+ elif record._changed:
855
+ write.append(record)
856
+
857
+ if create:
858
+ values = [r._get_values() for r in create]
859
+ ids = proxy.create(values, context)
860
+ for record, id_ in zip(create, ids):
861
+ record.id = id_
862
+ if write:
863
+ values = []
864
+ context['_timestamp'] = {}
865
+ for record in write:
866
+ values.append([record.id])
867
+ values.append(record._get_values(fields=record._changed))
868
+ context['_timestamp'].update(record._get_timestamp())
869
+ values.append(context)
870
+ proxy.write(*values)
871
+ for record in records:
872
+ record.reload()
873
+
874
+ @dualmethod
875
+ def delete(cls, records):
876
+ 'Delete records'
877
+ if not records:
878
+ return
879
+ proxy = records[0]._proxy
880
+ config = records[0]._config
881
+ context = records[0]._context.copy()
882
+ timestamp = {}
883
+ delete = []
884
+ for record in records:
885
+ assert proxy == record._proxy
886
+ assert config == record._config
887
+ assert context == record._context
888
+ if record.id >= 0:
889
+ timestamp.update(record._get_timestamp())
890
+ delete.append(record.id)
891
+ context['_timestamp'] = timestamp
892
+ if delete:
893
+ proxy.delete(delete, context)
894
+ cls.reload(records)
895
+
896
+ @dualmethod
897
+ def duplicate(cls, records, default=None):
898
+ 'Duplicate the record'
899
+ ids = cls._proxy.copy([r.id for r in records], default,
900
+ cls._config.context)
901
+ return [cls(id) for id in ids]
902
+
903
+ @dualmethod
904
+ def click(cls, records, button, change=None):
905
+ 'Click on button'
906
+ if not records:
907
+ return
908
+
909
+ proxy = records[0]._proxy
910
+ config = records[0]._config
911
+ context = records[0]._context.copy()
912
+ for record in records:
913
+ assert proxy == record._proxy
914
+ assert config == record._config
915
+ assert context == record._context
916
+
917
+ if change is None:
918
+ cls.save(records)
919
+ cls.reload(records) # Force reload because save doesn't always
920
+ record_ids = [r.id for r in records]
921
+ action = getattr(proxy, button)(record_ids, context)
922
+ if action and not isinstance(action, str):
923
+ if isinstance(action, int):
924
+ action = config.get_proxy('ir.action').get_action_value(
925
+ action, context)
926
+ return _convert_action(
927
+ action, records, context=context, config=config)
928
+ return action
929
+ else:
930
+ record, = records
931
+ values = record._on_change_args(change)
932
+ values['id'] = record.id
933
+ changes = getattr(proxy, button)(values, context)
934
+ record._set_on_change(changes)
935
+
936
+ def _get_values(self, fields=None):
937
+ 'Return dictionary values'
938
+ if fields is None:
939
+ fields = self._values.keys()
940
+ values = {}
941
+ for name in fields:
942
+ if name in ['id', '_timestamp']:
943
+ continue
944
+ definition = self._fields[name]
945
+ if definition.get('readonly') and definition['type'] != 'one2many':
946
+ continue
947
+ values[name] = getattr(self, '__%s_value' % name)
948
+ # Sending an empty X2Many fields breaks ModelFieldAccess.check
949
+ if (definition['type'] in {'one2many', 'many2many'}
950
+ and not values[name]):
951
+ del values[name]
952
+ return values
953
+
954
+ @property
955
+ def _timestamp(self):
956
+ 'Get _timestamp'
957
+ return self._values.get('_timestamp')
958
+
959
+ def _get_timestamp(self):
960
+ 'Return dictionary with timestamps'
961
+ result = {'%s,%s' % (self.__class__.__name__, self.id):
962
+ self._timestamp}
963
+ for field, definition in self._fields.items():
964
+ if field not in self._values:
965
+ continue
966
+ if definition['type'] in ('one2many', 'many2many'):
967
+ for record in getattr(self, field):
968
+ result.update(record._get_timestamp())
969
+ return result
970
+
971
+ def _read(self, name):
972
+ 'Read field'
973
+ fields = [name]
974
+ if name in self._values:
975
+ return
976
+ loading = self._fields[name]['loading']
977
+ if loading == 'eager':
978
+ fields = [x for x, y in self._fields.items()
979
+ if y['loading'] == 'eager']
980
+ fields.append('_timestamp')
981
+ self._set(self._proxy.read([self.id], fields, self._context)[0])
982
+
983
+ def _default_get(self):
984
+ 'Set default values'
985
+ fields = list(self._fields.keys())
986
+ self._default_set(
987
+ self._proxy.default_get(fields, False, self._context))
988
+
989
+ def _set(self, values, _default=False):
990
+ fieldnames = []
991
+ for field, value in values.items():
992
+ if '.' in field:
993
+ continue
994
+ if ((definition := self._fields.get(field))
995
+ and definition['type'] in {'one2many', 'many2many'}):
996
+ if value and len(value) and isinstance(value[0], int):
997
+ self._values[field] = value
998
+ else:
999
+ Relation = Model.get(definition['relation'], self._config)
1000
+ self._values[field] = records = ModelList(
1001
+ definition, [], self, field)
1002
+ for vals in (value or []):
1003
+ record = Relation()
1004
+ record._set(vals, _default=_default)
1005
+ records.append(record)
1006
+ else:
1007
+ self._values[field] = value
1008
+ fieldnames.append(field)
1009
+ if _default:
1010
+ self._on_change(sorted(fieldnames))
1011
+
1012
+ def _default_set(self, values):
1013
+ return self._set(values, _default=True)
1014
+
1015
+ def _get_eval(self):
1016
+ values = dict((x, getattr(self, '__%s_eval' % x))
1017
+ for x in self._fields if x != 'id')
1018
+ values['id'] = self.id
1019
+ return values
1020
+
1021
+ def _get_on_change_values(self, skip=None, fields=None):
1022
+ values = {'id': self.id}
1023
+ if fields:
1024
+ definitions = ((f, self._fields[f]) for f in fields)
1025
+ else:
1026
+ definitions = self._fields.items()
1027
+ for field, definition in definitions:
1028
+ if field == 'id':
1029
+ continue
1030
+ if not fields:
1031
+ if skip and field in skip:
1032
+ continue
1033
+ if (self.id >= 0
1034
+ and (field not in self._values
1035
+ or field not in self._changed)):
1036
+ continue
1037
+ if definition['type'] == 'one2many':
1038
+ values[field] = [x._get_on_change_values(
1039
+ skip={definition.get('relation_field', '')})
1040
+ for x in getattr(self, field)]
1041
+ elif (definition['type'] in ('many2one', 'reference')
1042
+ and self._parent_name == definition['name']
1043
+ and self._parent):
1044
+ values[field] = self._parent._get_on_change_values(
1045
+ skip={self._parent_field_name})
1046
+ if definition['type'] == 'reference':
1047
+ values[field] = (
1048
+ self._parent.__class__.__name__, values[field])
1049
+ else:
1050
+ values[field] = getattr(self, '__%s_eval' % field)
1051
+ return values
1052
+
1053
+ def _on_change_args(self, args):
1054
+ # Ensure arguments has been read
1055
+ for arg in args:
1056
+ record = self
1057
+ for i in arg.split('.'):
1058
+ if i in record._fields:
1059
+ getattr(record, i)
1060
+ elif i == '_parent_' + record._parent_name:
1061
+ getattr(record, record._parent_name)
1062
+ record = record._parent
1063
+
1064
+ res = {}
1065
+ values = _EvalEnvironment(self, 'on_change')
1066
+ for arg in args:
1067
+ scope = values
1068
+ for i in arg.split('.'):
1069
+ if i not in scope:
1070
+ break
1071
+ scope = scope[i]
1072
+ else:
1073
+ res[arg] = scope
1074
+ return res
1075
+
1076
+ def _on_change_set(self, field, value):
1077
+ if (self._fields[field]['type'] in ('one2many', 'many2many')
1078
+ and not isinstance(value, (list, tuple))):
1079
+ if value and value.get('delete'):
1080
+ for record_id in value['delete']:
1081
+ for record in getattr(self, field):
1082
+ if record.id == record_id:
1083
+ getattr(self, field).remove(record, _changed=False)
1084
+ if value and value.get('remove'):
1085
+ for record_id in value['remove']:
1086
+ for i, record in enumerate(getattr(self, field)):
1087
+ if record.id == record_id:
1088
+ getattr(self, field).pop(i, _changed=False)
1089
+ if value and (value.get('add') or value.get('update')):
1090
+ for index, vals in value.get('add', []):
1091
+ group = getattr(self, field)
1092
+ Relation = Model.get(
1093
+ self._fields[field]['relation'], self._config)
1094
+ config = Relation._config
1095
+ id_ = vals.pop('id', None)
1096
+ with config.reset_context(), \
1097
+ config.set_context(self._context):
1098
+ record = Relation(id=id_, _group=group, _default=False)
1099
+ try:
1100
+ idx = group.index(record)
1101
+ except ValueError:
1102
+ # append without signal
1103
+ if index == -1:
1104
+ list.append(group, record)
1105
+ else:
1106
+ list.insert(group, index, record)
1107
+ else:
1108
+ record = group[idx]
1109
+ group.record_removed.discard(record)
1110
+ group.record_deleted.discard(record)
1111
+ record._set_on_change(vals)
1112
+ for vals in value.get('update', []):
1113
+ if 'id' not in vals:
1114
+ continue
1115
+ for record in getattr(self, field):
1116
+ if record.id == vals['id']:
1117
+ record._set_on_change(vals)
1118
+ elif (self._fields[field]['type'] in ('one2many', 'many2many')
1119
+ and len(value) and not isinstance(value[0], int)):
1120
+ self._values[field] = []
1121
+ for vals in value:
1122
+ Relation = Model.get(
1123
+ self._fields[field]['relation'], self._config)
1124
+ config = Relation._config
1125
+ records = getattr(self, field)
1126
+ with config.reset_context(), \
1127
+ config.set_context(records._get_context()):
1128
+ record = Relation(_default=False, **vals)
1129
+ records.append(record)
1130
+ else:
1131
+ self._values[field] = value
1132
+ self._changed.add(field)
1133
+
1134
+ def _set_on_change(self, values):
1135
+ later = {}
1136
+ for field, value in values.items():
1137
+ if field not in self._fields:
1138
+ continue
1139
+ if self._fields[field]['type'] in ('one2many', 'many2many'):
1140
+ later[field] = value
1141
+ continue
1142
+ self._on_change_set(field, value)
1143
+ for field, value in later.items():
1144
+ self._on_change_set(field, value)
1145
+
1146
+ def _on_change(self, names):
1147
+ 'Call on_change for field'
1148
+ # Import locally to not break installation
1149
+ from proteus.pyson import PYSONDecoder
1150
+
1151
+ values = {}
1152
+ for name in names:
1153
+ definition = self._fields[name]
1154
+ on_change = definition.get('on_change')
1155
+ if not on_change:
1156
+ continue
1157
+ if isinstance(on_change, str):
1158
+ definition['on_change'] = on_change = PYSONDecoder().decode(
1159
+ on_change)
1160
+ values.update(self._on_change_args(on_change))
1161
+ if values:
1162
+ values['id'] = self.id
1163
+ context = self._context
1164
+ change = getattr(self._proxy, 'on_change')(values, names, context)
1165
+ self._set_on_change(change)
1166
+
1167
+ values = {}
1168
+ fieldnames = set(names)
1169
+ to_change = set()
1170
+ later = set()
1171
+ for field, definition in self._fields.items():
1172
+ on_change_with = definition.get('on_change_with')
1173
+ if not on_change_with:
1174
+ continue
1175
+ if not fieldnames & set(on_change_with):
1176
+ continue
1177
+ if to_change & set(on_change_with):
1178
+ later.add(field)
1179
+ continue
1180
+ to_change.add(field)
1181
+ values.update(self._on_change_args(on_change_with + [field]))
1182
+ if to_change:
1183
+ values['id'] = self.id
1184
+ context = self._context
1185
+ changes = getattr(self._proxy, 'on_change_with')(values,
1186
+ list(to_change), context)
1187
+ self._set_on_change(changes)
1188
+ if later:
1189
+ values = {}
1190
+ for field in later:
1191
+ on_change_with = self._fields[field]['on_change_with']
1192
+ values.update(self._on_change_args(on_change_with + [field]))
1193
+ values['id'] = self.id
1194
+ context = self._context
1195
+ changes = getattr(self._proxy, 'on_change_with')(
1196
+ values, list(later), context)
1197
+ self._set_on_change(changes)
1198
+
1199
+ if self._parent:
1200
+ self._parent._changed.add(self._parent_field_name)
1201
+ self._parent._on_change([self._parent_field_name])
1202
+
1203
+ def notifications(self):
1204
+ values = self._get_on_change_values()
1205
+ return getattr(self._proxy, 'on_change_notify')(values, self._context)
1206
+
1207
+
1208
+ class Wizard(object):
1209
+ 'Wizard class for Tryton wizards'
1210
+ __slots__ = ('name', 'form', 'form_state', 'actions', '_config',
1211
+ '_context', '_proxy', 'session_id', 'start_state', 'end_state',
1212
+ 'states', 'state', 'models', 'action')
1213
+
1214
+ def __init__(self, name, models=None, action=None, config=None,
1215
+ context=None):
1216
+ if models:
1217
+ assert len(set(type(x) for x in models)) == 1
1218
+ super().__init__()
1219
+ self.name = name
1220
+ self.form = None
1221
+ self.form_state = None
1222
+ self.actions = []
1223
+ self._config = config or proteus.config.get_config()
1224
+ self._context = self._config.context
1225
+ if context:
1226
+ self._context.update(context)
1227
+ self._proxy = self._config.get_proxy(name, type='wizard')
1228
+ result = self._proxy.create(self._context)
1229
+ self.session_id, self.start_state, self.end_state = result
1230
+ self.states = [self.start_state]
1231
+ self.models = models
1232
+ self.action = action
1233
+ self.execute(self.start_state)
1234
+
1235
+ def execute(self, state):
1236
+ assert state in self.states
1237
+
1238
+ self.state = state
1239
+ while self.state != self.end_state:
1240
+ ctx = self._context.copy()
1241
+ if self.models:
1242
+ ctx['active_id'] = self.models[0].id
1243
+ ctx['active_ids'] = [model.id for model in self.models]
1244
+ ctx['active_model'] = self.models[0].__class__.__name__
1245
+ elif isinstance(self.models, ModelList):
1246
+ ctx['active_id'] = None
1247
+ ctx['active_ids'] = None
1248
+ ctx['active_model'] = self.models.model_name
1249
+ else:
1250
+ ctx['active_id'] = None
1251
+ ctx['active_ids'] = None
1252
+ ctx['active_model'] = None
1253
+ if self.action:
1254
+ ctx['action_id'] = self.action['id']
1255
+ else:
1256
+ ctx['action_id'] = None
1257
+
1258
+ if self.form:
1259
+ data = {self.form_state: self.form._get_on_change_values()}
1260
+ else:
1261
+ data = {}
1262
+
1263
+ result = self._proxy.execute(self.session_id, data, self.state,
1264
+ ctx)
1265
+
1266
+ if 'view' in result:
1267
+ view = result['view']
1268
+ self.form = Model.get(
1269
+ view['fields_view']['model'], self._config)(_default=False)
1270
+ if 'defaults' in view:
1271
+ self.form._default_set(view['defaults'])
1272
+ if 'values' in view:
1273
+ self.form._set(view['values'])
1274
+ self.states = [b['state'] for b in view['buttons']]
1275
+ self.form_state = view['state']
1276
+ else:
1277
+ self.state = self.end_state
1278
+
1279
+ self.actions = []
1280
+ for action in result.get('actions', []):
1281
+ proteus_action = _convert_action(
1282
+ *action, context=self._context, config=self._config)
1283
+ if proteus_action is not None:
1284
+ self.actions.append(proteus_action)
1285
+
1286
+ if 'view' in result:
1287
+ return
1288
+
1289
+ if self.state == self.end_state:
1290
+ self._proxy.delete(self.session_id, self._context)
1291
+ if self.models:
1292
+ for record in self.models:
1293
+ record.reload()
1294
+
1295
+
1296
+ class Report(object):
1297
+ 'Report class for Tryton reports'
1298
+ __slots__ = ('name', '_config', '_context', '_proxy')
1299
+
1300
+ def __init__(self, name, config=None, context=None):
1301
+ super().__init__()
1302
+ self.name = name
1303
+ self._config = config or proteus.config.get_config()
1304
+ self._context = self._config.context
1305
+ if context:
1306
+ self._context.update(context)
1307
+ self._proxy = self._config.get_proxy(name, type='report')
1308
+
1309
+ def execute(self, models=None, data=None):
1310
+ if models:
1311
+ ids = [m.id for m in models]
1312
+ elif data:
1313
+ ids = data.get('ids', [])
1314
+ else:
1315
+ ids = []
1316
+ if data is None:
1317
+ data = {
1318
+ 'id': ids[0] if ids else None,
1319
+ 'ids': ids,
1320
+ }
1321
+ if models:
1322
+ data['model'] = models[0].__class__.__name__
1323
+ return self._proxy.execute(ids, data, self._context)
1324
+
1325
+
1326
+ def launch_action(xml_id, records, context=None, config=None):
1327
+ if records:
1328
+ assert len({type(x) for x in records}) == 1
1329
+ if context is None:
1330
+ context = {}
1331
+
1332
+ if isinstance(xml_id, str):
1333
+ ModelData = Model.get('ir.model.data')
1334
+
1335
+ if not config:
1336
+ config = proteus.config.get_config()
1337
+ context = config.context
1338
+
1339
+ action_id = ModelData.get_id(*xml_id.split('.'), context)
1340
+ elif isinstance(xml_id, int):
1341
+ action_id = xml_id
1342
+
1343
+ Action = Model.get('ir.action')
1344
+ action = Action.get_action_value(action_id, context)
1345
+ return _convert_action(action, records, context=context, config=config)
1346
+
1347
+
1348
+ def _convert_action(action, data=None, *, context=None, config=None):
1349
+ if config is None:
1350
+ config = proteus.config.get_config()
1351
+ records = None
1352
+ if data is None:
1353
+ data = {}
1354
+ elif isinstance(data, (list, tuple)):
1355
+ records = data
1356
+ data = {
1357
+ 'model': records[0].__class__.__name__,
1358
+ 'id': records[0].id,
1359
+ 'ids': [r.id for r in records],
1360
+ }
1361
+ else:
1362
+ data = data.copy()
1363
+
1364
+ if 'type' not in (action or {}):
1365
+ return None
1366
+
1367
+ data['action_id'] = action['id']
1368
+ if action['type'] == 'ir.action.act_window':
1369
+ from .pyson import PYSONDecoder
1370
+
1371
+ action.setdefault('pyson_domain', '[]')
1372
+ ctx = {
1373
+ 'active_model': data.get('model'),
1374
+ 'active_id': data.get('id'),
1375
+ 'active_ids': data.get('ids', []),
1376
+ }
1377
+ ctx.update(config.context)
1378
+ ctx['_user'] = config.user
1379
+ decoder = PYSONDecoder(ctx)
1380
+ action_ctx = decoder.decode(action.get('pyson_context') or '{}')
1381
+ ctx.update(action_ctx)
1382
+ ctx.update(context)
1383
+ action_ctx.update(context)
1384
+ if 'date_format' not in action_ctx:
1385
+ action_ctx['date_format'] = config.context.get(
1386
+ 'locale', {}).get('date', '%x')
1387
+
1388
+ ctx['context'] = ctx
1389
+ decoder = PYSONDecoder(ctx)
1390
+ domain = decoder.decode(action['pyson_domain'])
1391
+
1392
+ res_model = action.get('res_model', data.get('res_model'))
1393
+ res_id = action.get('res_id', data.get('res_id'))
1394
+ Model_ = Model.get(res_model, config)
1395
+ config = Model_._config
1396
+ with config.reset_context(), config.set_context(action_ctx):
1397
+ if res_id is None:
1398
+ return Model_.find(domain)
1399
+ elif isinstance(res_id, int):
1400
+ return [Model_(res_id)]
1401
+ else:
1402
+ return [Model_(id_) for id_ in res_id]
1403
+ elif action['type'] == 'ir.action.wizard':
1404
+ kwargs = {
1405
+ 'action': action,
1406
+ 'config': config,
1407
+ 'context': context,
1408
+ }
1409
+ if records is not None:
1410
+ kwargs['models'] = records
1411
+ elif 'model' in data:
1412
+ Model_ = Model.get(data['model'], config)
1413
+ config = Model_._config
1414
+ with config.reset_context(), config.set_context(context):
1415
+ kwargs['models'] = [Model_(id_) for id_ in data.get('ids', [])]
1416
+ return Wizard(action['wiz_name'], **kwargs)
1417
+ elif action['type'] == 'ir.action.report':
1418
+ ActionReport = Report(action['report_name'], context=context)
1419
+ return ActionReport.execute(data=data)
1420
+ elif action['type'] == 'ir.action.url':
1421
+ return action.get('url')