Files
tryton/trytond/ir/rule.py
T
2014-01-31 00:02:03 +01:00

282 lines
10 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.
import time
import datetime
from ..model import ModelView, ModelSQL, fields
from ..tools import safe_eval
from ..transaction import Transaction
from ..cache import Cache
from ..pool import Pool
from .. import backend
__all__ = [
'RuleGroup', 'Rule',
]
class RuleGroup(ModelSQL, ModelView):
"Rule group"
__name__ = 'ir.rule.group'
name = fields.Char('Name', select=True)
model = fields.Many2One('ir.model', 'Model', select=True,
required=True)
global_p = fields.Boolean('Global', select=True,
help="Make the rule global \nso every users must follow this rule")
default_p = fields.Boolean('Default', select=True,
help="Add this rule to all users by default")
rules = fields.One2Many('ir.rule', 'rule_group', 'Tests',
help="The rule is satisfied if at least one test is True")
groups = fields.Many2Many('ir.rule.group-res.group',
'rule_group', 'group', 'Groups')
users = fields.Many2Many('ir.rule.group-res.user',
'rule_group', 'user', 'Users')
perm_read = fields.Boolean('Read Access')
perm_write = fields.Boolean('Write Access')
perm_create = fields.Boolean('Create Access')
perm_delete = fields.Boolean('Delete Access')
@classmethod
def __setup__(cls):
super(RuleGroup, cls).__setup__()
cls._order.insert(0, ('model', 'ASC'))
cls._order.insert(1, ('global_p', 'ASC'))
cls._order.insert(2, ('default_p', 'ASC'))
cls._sql_constraints += [
('global_default_exclusive', 'CHECK(NOT(global_p AND default_p))',
'Global and Default are mutually exclusive!'),
]
@staticmethod
def default_global_p():
return True
@staticmethod
def default_default_p():
return False
@staticmethod
def default_perm_read():
return True
@staticmethod
def default_perm_write():
return True
@staticmethod
def default_perm_create():
return True
@staticmethod
def default_perm_delete():
return True
@classmethod
def delete(cls, groups):
super(RuleGroup, cls).delete(groups)
# Restart the cache on the domain_get method of ir.rule
Pool().get('ir.rule')._domain_get_cache.clear()
@classmethod
def create(cls, vlist):
res = super(RuleGroup, cls).create(vlist)
# Restart the cache on the domain_get method of ir.rule
Pool().get('ir.rule')._domain_get_cache.clear()
return res
@classmethod
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()
class Rule(ModelSQL, ModelView):
"Rule"
__name__ = 'ir.rule'
_rec_name = 'field'
rule_group = fields.Many2One('ir.rule.group', 'Group', select=True,
required=True, ondelete="CASCADE")
domain = fields.Char('Domain', required=True,
help='Domain is evaluated with "user" as the current user')
_domain_get_cache = Cache('ir_rule.domain_get')
@classmethod
def __setup__(cls):
super(Rule, cls).__setup__()
cls._error_messages.update({
'invalid_domain': 'Invalid domain in rule "%s".',
})
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
super(Rule, cls).__register__(module_name)
table = TableHandler(Transaction().cursor, cls, module_name)
# Migration from 2.6: replace field, operator and operand by domain
table.not_null_action('field', action='remove')
table.not_null_action('operator', action='remove')
table.not_null_action('operand', action='remove')
@classmethod
def validate(cls, rules):
super(Rule, cls).validate(rules)
cls.check_domain(rules)
@classmethod
def check_domain(cls, rules):
ctx = cls._get_context()
for rule in rules:
try:
value = safe_eval(rule.domain, ctx)
except Exception:
cls.raise_user_error('invalid_domain', (rule.rec_name,))
if not isinstance(value, list):
cls.raise_user_error('invalid_domain', (rule.rec_name,))
else:
try:
fields.domain_validate(value)
except Exception:
cls.raise_user_error('invalid_domain', (rule.rec_name,))
@staticmethod
def _get_context():
User = Pool().get('res.user')
user_id = Transaction().user
with Transaction().set_user(0, set_context=True):
user = User(user_id)
return {
'user': user,
'current_date': datetime.datetime.today(),
'time': time,
'context': Transaction().context,
}
@classmethod
def domain_get(cls, model_name, mode='read'):
assert mode in ['read', 'write', 'create', 'delete'], \
'Invalid domain mode for security'
# root user above constraint
if Transaction().user == 0:
if not Transaction().context.get('user'):
return
with Transaction().set_user(Transaction().context['user']):
return cls.domain_get(model_name, mode=mode)
key = (model_name, mode)
domain = cls._domain_get_cache.get(key, False)
if domain is not False:
return domain
pool = Pool()
RuleGroup = pool.get('ir.rule.group')
Model = pool.get('ir.model')
RuleGroup_User = pool.get('ir.rule.group-res.user')
RuleGroup_Group = pool.get('ir.rule.group-res.group')
User_Group = pool.get('res.user-res.group')
cursor = Transaction().cursor
rule_table = cls.__table__()
rule_group = RuleGroup.__table__()
rule_group_user = RuleGroup_User.__table__()
rule_group_group = RuleGroup_Group.__table__()
user_group = User_Group.__table__()
model = Model.__table__()
user_id = Transaction().user
cursor.execute(*rule_table.join(rule_group,
condition=rule_group.id == rule_table.rule_group
).join(model,
condition=rule_group.model == model.id
).select(rule_table.id,
where=(model.model == model_name)
& getattr(rule_group, 'perm_%s' % mode)
& (rule_group.id.in_(
rule_group_user.select(rule_group_user.rule_group,
where=rule_group_user.user == user_id)
| rule_group_group.join(
user_group,
condition=(rule_group_group.group
== user_group.group)
).select(rule_group_group.rule_group,
where=user_group.user == user_id)
)
| rule_group.default_p
| rule_group.global_p
)))
ids = [x[0] for x in cursor.fetchall()]
if not ids:
cls._domain_get_cache.set(key, None)
return
obj = pool.get(model_name)
clause = {}
clause_global = {}
ctx = cls._get_context()
# Use root user without context to prevent recursion
with Transaction().set_user(0), \
Transaction().set_context(user=0):
for rule in cls.browse(ids):
assert rule.domain, ('Rule domain empty,'
'check if migration was done')
dom = safe_eval(rule.domain, ctx)
if rule.rule_group.global_p:
clause_global.setdefault(rule.rule_group.id, ['OR'])
clause_global[rule.rule_group.id].append(dom)
else:
clause.setdefault(rule.rule_group.id, ['OR'])
clause[rule.rule_group.id].append(dom)
# Test if there is no rule_group that have no rule
cursor.execute(*rule_group.join(model,
condition=rule_group.model == model.id
).select(rule_group.id,
where=(model.model == model_name)
& ~rule_group.id.in_(rule_table.select(rule_table.rule_group))
& rule_group.id.in_(rule_group_user.select(
rule_group_user.rule_group,
where=rule_group_user.user == user_id)
| rule_group_group.join(user_group,
condition=rule_group_group.group == user_group.group
).select(rule_group_group.rule_group,
where=user_group.user == user_id))))
fetchone = cursor.fetchone()
if fetchone:
group_id = fetchone[0]
clause[group_id] = []
clause = clause.values()
clause.insert(0, 'OR')
clause_global = clause_global.values()
if clause_global:
clause_global.insert(0, 'AND')
clause = ['AND', clause_global, clause]
# Use root to prevent infinite recursion
with Transaction().set_user(0), \
Transaction().set_context(active_test=False, user=0):
query = obj.search(clause, order=[], query=True)
cls._domain_get_cache.set(key, query)
return query
@classmethod
def delete(cls, rules):
super(Rule, cls).delete(rules)
# Restart the cache on the domain_get method of ir.rule
cls._domain_get_cache.clear()
@classmethod
def create(cls, vlist):
res = super(Rule, cls).create(vlist)
# Restart the cache on the domain_get method of ir.rule
cls._domain_get_cache.clear()
return res
@classmethod
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()