Files
tryton/trytond/model/tree.py
T
Cédric Krier 5ca66d5ccf Add tree mixin
- Implement generic get_rec_name and search_rec_name
- Include recursive check

issue7383
review48281002
2018-05-12 00:45:55 +02:00

117 lines
4.9 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)
field.domain = [
field.domain,
(name, 'not like', '%' + separator + '%'),
]
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