changeset e71764e0ec1b in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset;node=e71764e0ec1b
description:
Set context on record instantiated in Field.__set__ and read related
Field may have a context defined so when it instantiate a relational
record,
the field should set this context like when the record is read from the
database in ModelStorage.__getattr__.
And when such field is read as related field, the context should also
be set.
issue8225
review287171002
diffstat:
CHANGELOG | 2 +
trytond/model/fields/field.py | 17 +++++++-
trytond/model/fields/many2many.py | 7 ++-
trytond/model/fields/many2one.py | 13 +++--
trytond/model/fields/one2many.py | 7 ++-
trytond/model/fields/reference.py | 12 +++--
trytond/model/modelsql.py | 72 +++++++++++++++++-----------------
trytond/tests/field_many2many.py | 29 ++++++++++++++
trytond/tests/field_many2one.py | 21 ++++++++++
trytond/tests/field_one2many.py | 22 ++++++++++
trytond/tests/field_reference.py | 21 ++++++++++
trytond/tests/test_field_many2many.py | 37 +++++++++++++++++
trytond/tests/test_field_many2one.py | 41 +++++++++++++++++++
trytond/tests/test_field_one2many.py | 37 +++++++++++++++++
trytond/tests/test_field_reference.py | 41 +++++++++++++++++++
15 files changed, 327 insertions(+), 52 deletions(-)
diffs (667 lines):
diff -r f58bbfe0aefb -r e71764e0ec1b CHANGELOG
--- a/CHANGELOG Tue Apr 02 19:01:33 2019 +0200
+++ b/CHANGELOG Thu Apr 04 19:06:09 2019 +0200
@@ -1,3 +1,5 @@
+* Set context on record instantiated by Field.__set__
+* Set context when reading related fields
* Check read access on field in search order (issue8189)
* Add base64 converter to URL map
* Add HTML widget
diff -r f58bbfe0aefb -r e71764e0ec1b trytond/model/fields/field.py
--- a/trytond/model/fields/field.py Tue Apr 02 19:01:33 2019 +0200
+++ b/trytond/model/fields/field.py Thu Apr 04 19:06:09 2019 +0200
@@ -9,7 +9,7 @@
from sql.operators import Concat
from trytond import backend
-from trytond.pyson import PYSON, PYSONEncoder, Eval
+from trytond.pyson import PYSON, PYSONEncoder, PYSONDecoder, Eval
from trytond.const import OPERATORS
from trytond.tools.string_ import StringPartitioned
from trytond.transaction import Transaction
@@ -162,6 +162,21 @@
return tuple(instance(x) for x in (value or []))
+def instantiate_context(field, record):
+ from ..modelstorage import EvalEnvironment
+ ctx = {}
+ if field.context:
+ pyson_context = PYSONEncoder().encode(field.context)
+ ctx.update(PYSONDecoder(
+ EvalEnvironment(record, record.__class__)).decode(
+ pyson_context))
+ datetime_ = None
+ if getattr(field, 'datetime_field', None):
+ datetime_ = getattr(record, field.datetime_field, None)
+ ctx = {'_datetime': datetime_}
+ return ctx
+
+
def on_change_result(record):
return record._changed_values
diff -r f58bbfe0aefb -r e71764e0ec1b trytond/model/fields/many2many.py
--- a/trytond/model/fields/many2many.py Tue Apr 02 19:01:33 2019 +0200
+++ b/trytond/model/fields/many2many.py Thu Apr 04 19:06:09 2019 +0200
@@ -8,7 +8,7 @@
from trytond.pyson import PYSONEncoder
from .field import (Field, size_validate, instanciate_values, domain_validate,
- search_order_validate, context_validate)
+ search_order_validate, context_validate, instantiate_context)
from ...pool import Pool
from ...tools import grouped_slice
from ...transaction import Transaction
@@ -278,7 +278,10 @@
def __set__(self, inst, value):
Target = self.get_target()
- super(Many2Many, self).__set__(inst, instanciate_values(Target, value))
+ ctx = instantiate_context(self, inst)
+ with Transaction().set_context(ctx):
+ records = instanciate_values(Target, value)
+ super(Many2Many, self).__set__(inst, records)
def convert_domain_tree(self, domain, tables):
Target = self.get_target()
diff -r f58bbfe0aefb -r e71764e0ec1b trytond/model/fields/many2one.py
--- a/trytond/model/fields/many2one.py Tue Apr 02 19:01:33 2019 +0200
+++ b/trytond/model/fields/many2one.py Thu Apr 04 19:06:09 2019 +0200
@@ -7,7 +7,7 @@
from trytond.pyson import PYSONEncoder
from .field import (Field, search_order_validate, context_validate,
- with_inactive_records)
+ with_inactive_records, instantiate_context)
from ...pool import Pool
from ...tools import reduce_ids
from ...transaction import Transaction
@@ -100,10 +100,13 @@
def __set__(self, inst, value):
Target = self.get_target()
- if isinstance(value, dict):
- value = Target(**value)
- elif isinstance(value, int):
- value = Target(value)
+ if isinstance(value, (dict, int)):
+ ctx = instantiate_context(self, inst)
+ with Transaction().set_context(ctx):
+ if isinstance(value, dict):
+ value = Target(**value)
+ elif isinstance(value, int):
+ value = Target(value)
assert isinstance(value, (Target, type(None)))
super(Many2One, self).__set__(inst, value)
diff -r f58bbfe0aefb -r e71764e0ec1b trytond/model/fields/one2many.py
--- a/trytond/model/fields/one2many.py Tue Apr 02 19:01:33 2019 +0200
+++ b/trytond/model/fields/one2many.py Thu Apr 04 19:06:09 2019 +0200
@@ -7,7 +7,7 @@
from trytond.pyson import PYSONEncoder
from .field import (Field, size_validate, instanciate_values, domain_validate,
- search_order_validate, context_validate)
+ search_order_validate, context_validate, instantiate_context)
from ...pool import Pool
from ...tools import grouped_slice
from ...transaction import Transaction
@@ -253,7 +253,10 @@
def __set__(self, inst, value):
Target = self.get_target()
- super(One2Many, self).__set__(inst, instanciate_values(Target, value))
+ ctx = instantiate_context(self, inst)
+ with Transaction().set_context(ctx):
+ records = instanciate_values(Target, value)
+ super(One2Many, self).__set__(inst, records)
def convert_domain(self, domain, tables, Model):
from ..modelsql import convert_from
diff -r f58bbfe0aefb -r e71764e0ec1b trytond/model/fields/reference.py
--- a/trytond/model/fields/reference.py Tue Apr 02 19:01:33 2019 +0200
+++ b/trytond/model/fields/reference.py Thu Apr 04 19:06:09 2019 +0200
@@ -7,7 +7,7 @@
from trytond.pyson import PYSONEncoder
from .field import (Field, search_order_validate, context_validate,
- with_inactive_records)
+ with_inactive_records, instantiate_context)
from .selection import SelectionMixin
from ...transaction import Transaction
from ...pool import Pool
@@ -144,10 +144,12 @@
else:
target, value = value
Target = Pool().get(target)
- if isinstance(value, dict):
- value = Target(**value)
- else:
- value = Target(value)
+ ctx = instantiate_context(self, inst)
+ with Transaction().set_context(ctx):
+ if isinstance(value, dict):
+ value = Target(**value)
+ else:
+ value = Target(value)
super(Reference, self).__set__(inst, value)
def sql_format(self, value):
diff -r f58bbfe0aefb -r e71764e0ec1b trytond/model/modelsql.py
--- a/trytond/model/modelsql.py Tue Apr 02 19:01:33 2019 +0200
+++ b/trytond/model/modelsql.py Thu Apr 04 19:06:09 2019 +0200
@@ -1,7 +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 datetime
-from itertools import islice, chain, product
+from itertools import islice, chain, product, groupby
from collections import OrderedDict, defaultdict
from functools import wraps
@@ -19,6 +19,7 @@
from trytond.tools import reduce_ids, grouped_slice, cursor_dict
from trytond.transaction import Transaction
from trytond.pool import Pool
+from trytond.pyson import PYSONEncoder, PYSONDecoder
from trytond.cache import LRUDict
from trytond.exceptions import ConcurrencyException
from trytond.rpc import RPC
@@ -690,19 +691,21 @@
# construct a clause for the rules :
domain = Rule.domain_get(cls.__name__, mode='read')
- fields_related = {}
- datetime_fields = []
+ fields_related = defaultdict(set)
+ extra_fields = set()
for field_name in fields_names:
if field_name == '_timestamp':
continue
if '.' in field_name:
- field, field_related = field_name.split('.', 1)
- fields_related.setdefault(field, [])
- fields_related[field].append(field_related)
- else:
- field = cls._fields[field_name]
+ field_name, field_related = field_name.split('.', 1)
+ fields_related[field_name].add(field_related)
+ field = cls._fields[field_name]
if hasattr(field, 'datetime_field') and field.datetime_field:
- datetime_fields.append(field.datetime_field)
+ extra_fields.add(field.datetime_field)
+ if field.context:
+ extra_fields.update(fields.get_eval_fields(field.context))
+ all_fields = (
+ set(fields_names) | set(fields_related.keys()) | extra_fields)
result = []
table = cls.__table__()
@@ -722,7 +725,7 @@
history_limit = 1
columns = []
- for f in fields_names + list(fields_related.keys()) + datetime_fields:
+ for f in all_fields:
field = cls._fields.get(f)
if field and field.sql_type():
columns.append(field.sql_column(table).as_(f))
@@ -779,8 +782,7 @@
cachable_fields.append(fname)
# all fields for which there is a get attribute
- getter_fields = [f for f in
- fields_names + list(fields_related.keys()) + datetime_fields
+ getter_fields = [f for f in all_fields
if f in cls._fields and hasattr(cls._fields[f], 'get')]
if getter_fields and cachable_fields:
@@ -833,15 +835,6 @@
for row in result:
row[fname] = getter_result[row['id']]
- def group_by(rows, keyfunc=None):
- result = defaultdict(list)
- if keyfunc is None:
- result[None] = rows
- else:
- for row in rows:
- result[keyfunc(row)].append(row)
- return result
-
def read_related(field, Target, rows, fields):
name = field.name
target_ids = []
@@ -880,7 +873,7 @@
row[key] = None
to_del = set()
- for fname in chain(fields_related.keys(), datetime_fields):
+ for fname in set(fields_related.keys()) | extra_fields:
if fname not in fields_names:
to_del.add(fname)
if fname not in cls._fields:
@@ -889,27 +882,32 @@
continue
field = cls._fields[fname]
datetime_field = getattr(field, 'datetime_field', None)
- if field._type == 'reference':
- def keyfunc(row):
+
+ def keyfunc(row):
+ ctx = {}
+ if field.context:
+ pyson_context = PYSONEncoder().encode(field.context)
+ ctx.update(PYSONDecoder(row).decode(pyson_context))
+ if datetime_field:
+ ctx['_datetime'] = row.get(datetime_field)
+ if field._type == 'reference':
value = row[fname]
if not value:
- return (None, None)
- model, _ = value.split(',', 1)
- return (pool.get(model), row.get(datetime_field))
- else:
- Target = field.get_target()
+ Target = None
+ else:
+ model, _ = value.split(',', 1)
+ Target = pool.get(model)
+ else:
+ Target = field.get_target()
+ return Target, ctx
- def keyfunc(row):
- return (Target, row.get(datetime_field))
- for (Target, datetime_), rows in group_by(
- result, keyfunc).items():
- ctx = {}
- if datetime_:
- ctx['_datetime'] = datetime_
+ for (Target, ctx), rows in groupby(
+ sorted(result, key=keyfunc), key=keyfunc):
+ rows = list(rows)
with Transaction().set_context(ctx):
if Target:
targets = read_related(
- field, Target, rows, fields_related[fname])
+ field, Target, rows, list(fields_related[fname]))
targets = {t['id']: t for t in targets}
else:
targets = {}
diff -r f58bbfe0aefb -r e71764e0ec1b trytond/tests/field_many2many.py
--- a/trytond/tests/field_many2many.py Tue Apr 02 19:01:33 2019 +0200
+++ b/trytond/tests/field_many2many.py Thu Apr 04 19:06:09 2019 +0200
@@ -3,6 +3,7 @@
from trytond.model import ModelSQL, fields
from trytond.pool import Pool
+from trytond.transaction import Transaction
class Many2Many(ModelSQL):
@@ -154,6 +155,31 @@
child = fields.Many2One('test.many2many_tree', 'Child')
+class Many2ManyContext(ModelSQL):
+ "Many2Many Context"
+ __name__ = 'test.many2many_context'
+ targets = fields.Many2Many(
+ 'test.many2many_context.relation', 'origin', 'target', "Targets",
+ context={'test': 'foo'})
+
+
+class Many2ManyContextRelation(ModelSQL):
+ "Many2Many Context Relation"
+ __name__ = 'test.many2many_context.relation'
+ origin = fields.Many2One('test.many2many_context', "Origin")
+ target = fields.Many2One('test.many2many_context.target', "Target")
+
+
+class Many2ManyContextTarget(ModelSQL):
+ "Many2Many Context Target"
+ __name__ = 'test.many2many_context.target'
+ context = fields.Function(fields.Char("context"), 'get_context')
+
+ def get_context(self, name):
+ context = Transaction().context
+ return context.get('test')
+
+
def register(module):
Pool.register(
Many2Many,
@@ -176,4 +202,7 @@
Many2ManyFilterDomainRelation,
Many2ManyTree,
Many2ManyTreeRelation,
+ Many2ManyContext,
+ Many2ManyContextTarget,
+ Many2ManyContextRelation,
module=module, type_='model')
diff -r f58bbfe0aefb -r e71764e0ec1b trytond/tests/field_many2one.py
--- a/trytond/tests/field_many2one.py Tue Apr 02 19:01:33 2019 +0200
+++ b/trytond/tests/field_many2one.py Thu Apr 04 19:06:09 2019 +0200
@@ -3,6 +3,7 @@
from trytond.model import ModelSQL, ModelStorage, DeactivableMixin, fields
from trytond.pool import Pool
+from trytond.transaction import Transaction
class Many2OneTarget(DeactivableMixin, ModelSQL):
@@ -64,6 +65,24 @@
return 0
+class Many2OneContext(ModelSQL):
+ "Many2One Context"
+ __name__ = 'test.many2one_context'
+ target = fields.Many2One(
+ 'test.many2one_context.target', "target",
+ context={'test': 'foo'})
+
+
+class Many2OneTargetContext(ModelSQL):
+ "Many2One Target Context"
+ __name__ = 'test.many2one_context.target'
+ context = fields.Function(fields.Char("context"), 'get_context')
+
+ def get_context(self, name):
+ context = Transaction().context
+ return context.get('test')
+
+
def register(module):
Pool.register(
Many2OneTarget,
@@ -73,4 +92,6 @@
Many2OneTargetStorage,
Many2OneTree,
Many2OneMPTT,
+ Many2OneContext,
+ Many2OneTargetContext,
module=module, type_='model')
diff -r f58bbfe0aefb -r e71764e0ec1b trytond/tests/field_one2many.py
--- a/trytond/tests/field_one2many.py Tue Apr 02 19:01:33 2019 +0200
+++ b/trytond/tests/field_one2many.py Thu Apr 04 19:06:09 2019 +0200
@@ -4,6 +4,7 @@
from trytond.model import ModelSQL, fields
from trytond.pool import Pool
from trytond.pyson import Eval
+from trytond.transaction import Transaction
class One2Many(ModelSQL):
@@ -110,6 +111,25 @@
value = fields.Integer('Value')
+class One2ManyContext(ModelSQL):
+ "One2Many Context"
+ __name__ = 'test.one2many_context'
+ targets = fields.One2Many(
+ 'test.one2many_context.target', 'origin', "Targets",
+ context={'test': 'foo'})
+
+
+class One2ManyContextTarget(ModelSQL):
+ "One2Many Context Target"
+ __name__ = 'test.one2many_context.target'
+ origin = fields.Many2One('test.one2many_context', "Origin")
+ context = fields.Function(fields.Char("context"), 'get_context')
+
+ def get_context(self, name):
+ context = Transaction().context
+ return context.get('test')
+
+
def register(module):
Pool.register(
One2Many,
@@ -126,4 +146,6 @@
One2ManyFilterTarget,
One2ManyFilterDomain,
One2ManyFilterDomainTarget,
+ One2ManyContext,
+ One2ManyContextTarget,
module=module, type_='model')
diff -r f58bbfe0aefb -r e71764e0ec1b trytond/tests/field_reference.py
--- a/trytond/tests/field_reference.py Tue Apr 02 19:01:33 2019 +0200
+++ b/trytond/tests/field_reference.py Thu Apr 04 19:06:09 2019 +0200
@@ -3,6 +3,7 @@
from trytond.model import ModelSQL, fields
from trytond.pool import Pool
+from trytond.transaction import Transaction
class Reference(ModelSQL):
@@ -29,9 +30,29 @@
], required=True)
+class ReferenceContext(ModelSQL):
+ "Reference Context"
+ __name__ = 'test.reference_context'
+ target = fields.Reference("Reference", selection=[
+ ('test.reference_context.target', "Target"),
+ ], context={'test': 'foo'})
+
+
+class ReferenceContextTarget(ModelSQL):
+ "Reference Context Target"
+ __name__ = 'test.reference_context.target'
+ context = fields.Function(fields.Char("context"), 'get_context')
+
+ def get_context(self, name):
+ context = Transaction().context
+ return context.get('test')
+
+
def register(module):
Pool.register(
Reference,
ReferenceTarget,
ReferenceRequired,
+ ReferenceContext,
+ ReferenceContextTarget,
module=module, type_='model')
diff -r f58bbfe0aefb -r e71764e0ec1b trytond/tests/test_field_many2many.py
--- a/trytond/tests/test_field_many2many.py Tue Apr 02 19:01:33 2019 +0200
+++ b/trytond/tests/test_field_many2many.py Thu Apr 04 19:06:09 2019 +0200
@@ -625,6 +625,43 @@
self.first1, self.first2, self.first3, self.first4,
self.second1, self.second2, self.second3, self.second4]))
+ @with_transaction()
+ def test_context_attribute(self):
+ "Test context on many2many attribute"
+ pool = Pool()
+ Many2Many = pool.get('test.many2many_context')
+
+ record, = Many2Many.create([{
+ 'targets': [('create', [{}])],
+ }])
+
+ self.assertEqual(record.targets[0].context, 'foo')
+
+ @with_transaction()
+ def test_context_read(self):
+ "Test context on many2many read"
+ pool = Pool()
+ Many2Many = pool.get('test.many2many_context')
+
+ record, = Many2Many.create([{
+ 'targets': [('create', [{}])],
+ }])
+ data, = Many2Many.read([record.id], ['targets.context'])
+
+ self.assertEqual(data['targets.'][0]['context'], 'foo')
+
+ @with_transaction()
+ def test_context_set(self):
+ "Test context on many2many set"
+ pool = Pool()
+ Many2Many = pool.get('test.many2many_context')
+ Target = pool.get('test.many2many_context.target')
+
+ target, = Target.create([{}])
+ record = Many2Many(targets=[target.id])
+
+ self.assertEqual(record.targets[0].context, 'foo')
+
class FieldMany2ManyReferenceTestCase(unittest.TestCase, CommonTestCaseMixin):
"Test Field Many2Many Reference"
diff -r f58bbfe0aefb -r e71764e0ec1b trytond/tests/test_field_many2one.py
--- a/trytond/tests/test_field_many2one.py Tue Apr 02 19:01:33 2019 +0200
+++ b/trytond/tests/test_field_many2one.py Thu Apr 04 19:06:09 2019 +0200
@@ -467,6 +467,47 @@
"Test search many2one mptt not parent of empty"
self._test_search_not_parent_of_empty('test.many2one_mptt')
+ @with_transaction()
+ def test_context_attribute(self):
+ "Test context on many2one attribute"
+ pool = Pool()
+ Many2One = pool.get('test.many2one_context')
+ Target = pool.get('test.many2one_context.target')
+
+ target, = Target.create([{}])
+ record, = Many2One.create([{
+ 'target': target.id,
+ }])
+
+ self.assertEqual(record.target.context, 'foo')
+
+ @with_transaction()
+ def test_context_read(self):
+ "Test context on many2one read"
+ pool = Pool()
+ Many2One = pool.get('test.many2one_context')
+ Target = pool.get('test.many2one_context.target')
+
+ target, = Target.create([{}])
+ record, = Many2One.create([{
+ 'target': target.id,
+ }])
+ data, = Many2One.read([record.id], ['target.context'])
+
+ self.assertEqual(data['target.']['context'], 'foo')
+
+ @with_transaction()
+ def test_context_set(self):
+ "Test context on many2one set"
+ pool = Pool()
+ Many2One = pool.get('test.many2one_context')
+ Target = pool.get('test.many2one_context.target')
+
+ target, = Target.create([{}])
+ record = Many2One(target=target.id)
+
+ self.assertEqual(record.target.context, 'foo')
+
def suite():
return unittest.TestLoader().loadTestsFromTestCase(FieldMany2OneTestCase)
diff -r f58bbfe0aefb -r e71764e0ec1b trytond/tests/test_field_one2many.py
--- a/trytond/tests/test_field_one2many.py Tue Apr 02 19:01:33 2019 +0200
+++ b/trytond/tests/test_field_one2many.py Thu Apr 04 19:06:09 2019 +0200
@@ -455,6 +455,43 @@
self.assertListEqual(one2manys, [one2many])
self.assertListEqual(one2manys_filtered, [])
+ @with_transaction()
+ def test_context_attribute(self):
+ "Test context on one2many attribute"
+ pool = Pool()
+ Many2One = pool.get('test.one2many_context')
+
+ record, = Many2One.create([{
+ 'targets': [('create', [{}])],
+ }])
+
+ self.assertEqual(record.targets[0].context, 'foo')
+
+ @with_transaction()
+ def test_context_read(self):
+ "Test context on one2many read"
+ pool = Pool()
+ Many2One = pool.get('test.one2many_context')
+
+ record, = Many2One.create([{
+ 'targets': [('create', [{}])],
+ }])
+ data, = Many2One.read([record.id], ['targets.context'])
+
+ self.assertEqual(data['targets.'][0]['context'], 'foo')
+
+ @with_transaction()
+ def test_context_set(self):
+ "Test context on one2many set"
+ pool = Pool()
+ Many2One = pool.get('test.one2many_context')
+ Target = pool.get('test.one2many_context.target')
+
+ target, = Target.create([{}])
+ record = Many2One(targets=[target.id])
+
+ self.assertEqual(record.targets[0].context, 'foo')
+
class FieldOne2ManyReferenceTestCase(unittest.TestCase, CommonTestCaseMixin):
"Test Field One2Many Reference"
diff -r f58bbfe0aefb -r e71764e0ec1b trytond/tests/test_field_reference.py
--- a/trytond/tests/test_field_reference.py Tue Apr 02 19:01:33 2019 +0200
+++ b/trytond/tests/test_field_reference.py Thu Apr 04 19:06:09 2019 +0200
@@ -398,6 +398,47 @@
self.assertEqual(reference.reference, None)
+ @with_transaction()
+ def test_context_attribute(self):
+ "Test context on reference attribute"
+ pool = Pool()
+ Reference = pool.get('test.reference_context')
+ Target = pool.get('test.reference_context.target')
+
+ target, = Target.create([{}])
+ record, = Reference.create([{
+ 'target': str(target),
+ }])
+
+ self.assertEqual(record.target.context, 'foo')
+
+ @with_transaction()
+ def test_context_read(self):
+ "Test context on reference read"
+ pool = Pool()
+ Reference = pool.get('test.reference_context')
+ Target = pool.get('test.reference_context.target')
+
+ target, = Target.create([{}])
+ record, = Reference.create([{
+ 'target': str(target),
+ }])
+ data, = Reference.read([record.id], ['target.context'])
+
+ self.assertEqual(data['target.']['context'], 'foo')
+
+ @with_transaction()
+ def test_context_set(self):
+ "Test context on reference set"
+ pool = Pool()
+ Reference = pool.get('test.reference_context')
+ Target = pool.get('test.reference_context.target')
+
+ target, = Target.create([{}])
+ record = Reference(target=str(target))
+
+ self.assertEqual(record.target.context, 'foo')
+
def suite():
return unittest.TestLoader().loadTestsFromTestCase(FieldReferenceTestCase)