Files
tryton/trytond/model/tree.py
T
Cédric Krier 90502790f9 Add tree name clause only if not yet present
We can not rely on the deepcopy of the field because the Mixin maybe added
after the class that define the field. In this case we modify the reference
field for the deepcopy.

issue7434
review47421002
2018-05-17 18:26:54 +02:00

122 lines
5.3 KiB
Python

# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from itertools import chain
def tree(parent='parent', name='name', separator=None):
class TreeMixin(object):
if separator:
@classmethod
def __setup__(cls):
super(TreeMixin, cls).__setup__()
field = getattr(cls, name)
clause = (name, 'not like', '%' + separator + '%')
# If TreeMixin is after the class where name is defined in
# __mro__, it modifies the base field copied so it must ensure
# to add only once the domain
if clause not in field.domain:
domain = [clause]
if field.domain:
domain.append(field.domain)
field.domain = domain
def get_rec_name(self, _):
record, names = self, []
while record:
names.append(getattr(record, name))
record = getattr(record, parent)
return separator.join(reversed(names))
@classmethod
def search_rec_name(cls, _, clause):
if isinstance(clause[2], basestring):
values = list(reversed(clause[2].split(separator)))
else:
values = [[]]
for value in clause[2]:
if value is None:
values[0].append(value)
continue
for i, v in range(reversed(value.split(separator))):
while len(values) <= i:
values.append([])
values[i].append(v)
domain = []
field = name
for value in values:
domain.append((field, clause[1], value.strip()))
field = parent + '.' + field
if ((clause[1].endswith('like')
and not clause[2].replace(
'%%', '__').startswith('%'))
or not clause[1].endswith('like')):
if clause[1].startswith('not') or clause[1] == '!=':
operator = '!='
domain.insert(0, 'OR')
else:
operator = '='
top_parent = '.'.join((parent,) * len(values))
domain.append((top_parent, operator, None))
if (clause[1].endswith('like')
and clause[2].replace('%%', '__').endswith('%')):
ids = map(int, cls.search(domain, order=[]))
domain = [(parent, 'child_of', ids)]
return domain
@classmethod
def validate(cls, records):
super(TreeMixin, cls).validate(records)
cls.check_recursion(records)
@classmethod
def check_recursion(cls, records):
'''
Function that checks if there is no recursion in the tree
composed with parent as parent field name.
'''
if hasattr(super(TreeMixin, cls), 'check_recursion'):
super(TreeMixin, cls).check_recursion(records)
parent_type = cls._fields[parent]._type
if parent_type not in ('many2one', 'many2many', 'one2one'):
raise ValueError(
'Unsupported field type "%s" for field "%s" on "%s"'
% (parent_type, parent, cls.__name__))
visited = set()
for record in records:
walked = set()
walker = getattr(record, parent)
while walker:
if parent_type == 'many2many':
for walk in walker:
walked.add(walk.id)
if walk.id == record.id:
parent_name = ', '.join(getattr(r, name)
for r in getattr(record, parent))
cls.raise_user_error('recursion_error', {
'rec_name': getattr(record, name),
'parent_rec_name': parent_name,
})
walker = list(chain(*(
getattr(walk, parent)
for walk in walker
if walk.id not in visited)))
else:
walked.add(walker.id)
if walker.id == record.id:
parent_name = getattr(
getattr(record, parent), name)
cls.raise_user_error('recursion_error', {
'rec_name': getattr(record, name),
'parent_rec_name': parent_name
})
walker = (getattr(walker, parent) not in visited
and getattr(walker, parent))
visited.update(walked)
return TreeMixin