[MERGE] feature branch with improvements for sharing resources
Contains a few fixes for ir.rule management, tools for combining domains, related tests, and a few minor fixes. bzr revid: odo@openerp.com-20110624133529-z4y01gy2fm0x0oj1
This commit is contained in:
@@ -1667,24 +1667,29 @@
|
||||
<field name="perm_create"/>
|
||||
<field name="perm_unlink"/>
|
||||
</group>
|
||||
<separator colspan="4" string="Domain Setup"/>
|
||||
<separator colspan="4" string="Rule definition (domain filter)"/>
|
||||
<group colspan="4">
|
||||
<field name="domain_force" colspan="4"/>
|
||||
<field name="domain_force" colspan="4" nolabel="1"/>
|
||||
</group>
|
||||
<separator colspan="4" string="Groups (no group = global)"/>
|
||||
<group colspan="4" expand="1">
|
||||
<field name="global"/>
|
||||
<field name="groups" nolabel="1" colspan="4"/>
|
||||
</group>
|
||||
<separator colspan="4" string="Combination of rules"/>
|
||||
<label align="0.0" colspan="4"
|
||||
<label colspan="4" string=""/>
|
||||
<separator colspan="4" string="Interaction between rules"/>
|
||||
<label colspan="4"
|
||||
string="Global rules (non group-specific) are restrictions, and cannot be bypassed. Group-local rules grant additional permissions, but are constrained within the bounds of global ones. The first group rules restrict further than global rules, but any additional group rule will add more permissions"/>
|
||||
<label colspan="4"
|
||||
string="Detailed algorithm:"/>
|
||||
<label colspan="4"
|
||||
string="1. Global rules are combined together with a logical AND operator, and with the result of the following steps"/>
|
||||
<label align="0.0" colspan="4"
|
||||
string="2. Group-specific rules are combined together with a logical AND operator"/>
|
||||
<label align="0.0" colspan="4"
|
||||
<label colspan="4"
|
||||
string="2. Group-specific rules are combined together with a logical OR operator"/>
|
||||
<label colspan="4"
|
||||
string="3. If user belongs to several groups, the results from step 2 are combined with logical OR operator"/>
|
||||
<label align="0.0" colspan="4"
|
||||
string="Example: GLOBAL_RULE_1 AND GLOBAL_RULE_2 AND ( (GROUP_A_RULE_1 AND GROUP_A_RULE_2) OR (GROUP_B_RULE_1 AND GROUP_B_RULE_2) )"/>
|
||||
<label colspan="4"
|
||||
string="Example: GLOBAL_RULE_1 AND GLOBAL_RULE_2 AND ( (GROUP_A_RULE_1 OR GROUP_A_RULE_2) OR (GROUP_B_RULE_1 OR GROUP_B_RULE_2) )"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
from operator import itemgetter
|
||||
|
||||
from osv import fields,osv
|
||||
import netsvc
|
||||
@@ -450,6 +449,23 @@ class ir_model_access(osv.osv):
|
||||
# pass no groups -> no access
|
||||
return False
|
||||
|
||||
def group_names_with_access(self, cr, model_name, access_mode):
|
||||
"""Returns the names of visible groups which have been granted ``access_mode`` on
|
||||
the model ``model_name``.
|
||||
:rtype: list
|
||||
"""
|
||||
assert access_mode in ['read','write','create','unlink'], 'Invalid access mode: %s' % access_mode
|
||||
cr.execute('''SELECT
|
||||
g.name
|
||||
FROM
|
||||
ir_model_access a
|
||||
JOIN ir_model m ON (a.model_id=m.id)
|
||||
JOIN res_groups g ON (a.group_id=g.id)
|
||||
WHERE
|
||||
m.model=%s AND
|
||||
a.perm_''' + access_mode, (model_name,))
|
||||
return [x[0] for x in cr.fetchall()]
|
||||
|
||||
def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
|
||||
if uid==1:
|
||||
# User root have all accesses
|
||||
@@ -493,16 +509,7 @@ class ir_model_access(osv.osv):
|
||||
r = cr.fetchone()[0]
|
||||
|
||||
if not r and raise_exception:
|
||||
cr.execute('''select
|
||||
g.name
|
||||
from
|
||||
ir_model_access a
|
||||
left join ir_model m on (a.model_id=m.id)
|
||||
left join res_groups g on (a.group_id=g.id)
|
||||
where
|
||||
m.model=%s and
|
||||
a.group_id is not null and perm_''' + mode, (model_name, ))
|
||||
groups = ', '.join(map(lambda x: x[0], cr.fetchall())) or '/'
|
||||
groups = ', '.join(self.group_names_with_access(cr, model_name, mode)) or '/'
|
||||
msgs = {
|
||||
'read': _("You can not read this document (%s) ! Be sure your user belongs to one of these groups: %s."),
|
||||
'write': _("You can not write in this document (%s) ! Be sure your user belongs to one of these groups: %s."),
|
||||
|
||||
@@ -19,25 +19,41 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import fields,osv
|
||||
from osv import fields, osv, expression
|
||||
import time
|
||||
from operator import itemgetter
|
||||
from functools import partial
|
||||
import tools
|
||||
from tools.safe_eval import safe_eval as eval
|
||||
from tools.misc import unquote as unquote
|
||||
|
||||
SUPERUSER_UID = 1
|
||||
|
||||
class ir_rule(osv.osv):
|
||||
_name = 'ir.rule'
|
||||
_order = 'name'
|
||||
_MODES = ['read', 'write', 'create', 'unlink']
|
||||
|
||||
def _eval_context_for_combinations(self):
|
||||
"""Returns a dictionary to use as evaluation context for
|
||||
ir.rule domains, when the goal is to obtain python lists
|
||||
that are easier to parse and combine, but not to
|
||||
actually execute them."""
|
||||
return {'user': unquote('user'),
|
||||
'time': unquote('time')}
|
||||
|
||||
def _eval_context(self, cr, uid):
|
||||
"""Returns a dictionary to use as evaluation context for
|
||||
ir.rule domains."""
|
||||
return {'user': self.pool.get('res.users').browse(cr, 1, uid),
|
||||
'time':time}
|
||||
|
||||
def _domain_force_get(self, cr, uid, ids, field_name, arg, context=None):
|
||||
res = {}
|
||||
eval_context = self._eval_context(cr, uid)
|
||||
for rule in self.browse(cr, uid, ids, context):
|
||||
if rule.domain_force:
|
||||
eval_user_data = {'user': self.pool.get('res.users').browse(cr, 1, uid),
|
||||
'time':time}
|
||||
res[rule.id] = eval(rule.domain_force, eval_user_data)
|
||||
res[rule.id] = expression.normalize(eval(rule.domain_force, eval_context))
|
||||
else:
|
||||
res[rule.id] = []
|
||||
return res
|
||||
@@ -83,25 +99,12 @@ class ir_rule(osv.osv):
|
||||
(_check_model_obj, 'Rules are not supported for osv_memory objects !', ['model_id'])
|
||||
]
|
||||
|
||||
def domain_create(self, cr, uid, rule_ids):
|
||||
count = 0
|
||||
dom = []
|
||||
for rule in self.browse(cr, uid, rule_ids):
|
||||
if rule.domain:
|
||||
dom += rule.domain
|
||||
count += 1
|
||||
if count:
|
||||
return ['&'] * (count-1) + dom
|
||||
return []
|
||||
|
||||
@tools.cache()
|
||||
def _compute_domain(self, cr, uid, model_name, mode="read"):
|
||||
if mode not in self._MODES:
|
||||
raise ValueError('Invalid mode: %r' % (mode,))
|
||||
group_rule = {}
|
||||
global_rules = []
|
||||
|
||||
if uid == 1:
|
||||
if uid == SUPERUSER_UID:
|
||||
return None
|
||||
cr.execute("""SELECT r.id
|
||||
FROM ir_rule r
|
||||
@@ -111,26 +114,28 @@ class ir_rule(osv.osv):
|
||||
AND (r.id IN (SELECT rule_group_id FROM rule_group_rel g_rel
|
||||
JOIN res_groups_users_rel u_rel ON (g_rel.group_id = u_rel.gid)
|
||||
WHERE u_rel.uid = %s) OR r.global)""", (model_name, uid))
|
||||
ids = map(lambda x: x[0], cr.fetchall())
|
||||
if ids:
|
||||
for rule in self.browse(cr, uid, ids):
|
||||
rule_ids = [x[0] for x in cr.fetchall()]
|
||||
if rule_ids:
|
||||
# browse user as super-admin root to avoid access errors!
|
||||
user = self.pool.get('res.users').browse(cr, SUPERUSER_UID, uid)
|
||||
global_domains = [] # list of domains
|
||||
group_domains = {} # map: group -> list of domains
|
||||
for rule in self.browse(cr, SUPERUSER_UID, rule_ids):
|
||||
# read 'domain' as UID to have the correct eval context for the rule.
|
||||
rule_domain = self.read(cr, uid, rule.id, ['domain'])['domain']
|
||||
dom = expression.normalize(rule_domain)
|
||||
for group in rule.groups:
|
||||
group_rule.setdefault(group.id, []).append(rule.id)
|
||||
if group in user.groups_id:
|
||||
group_domains.setdefault(group, []).append(dom)
|
||||
if not rule.groups:
|
||||
global_rules.append(rule.id)
|
||||
global_domain = self.domain_create(cr, uid, global_rules)
|
||||
count = 0
|
||||
group_domains = []
|
||||
for value in group_rule.values():
|
||||
group_domain = self.domain_create(cr, uid, value)
|
||||
if group_domain:
|
||||
group_domains += group_domain
|
||||
count += 1
|
||||
if count and global_domain:
|
||||
return ['&'] + global_domain + ['|'] * (count-1) + group_domains
|
||||
if count:
|
||||
return ['|'] * (count-1) + group_domains
|
||||
return global_domain
|
||||
global_domains.append(dom)
|
||||
# combine global domains and group domains
|
||||
if group_domains:
|
||||
group_domain = expression.OR(map(expression.OR, group_domains.values()))
|
||||
else:
|
||||
group_domain = []
|
||||
domain = expression.AND(global_domains + [group_domain])
|
||||
return domain
|
||||
return []
|
||||
|
||||
def clear_cache(self, cr, uid):
|
||||
|
||||
@@ -24,7 +24,6 @@ from lxml import etree
|
||||
from tools import graph
|
||||
from tools.safe_eval import safe_eval as eval
|
||||
import tools
|
||||
import netsvc
|
||||
import os
|
||||
import logging
|
||||
|
||||
|
||||
@@ -581,9 +581,9 @@ class groups2(osv.osv): ##FIXME: Is there a reason to inherit this object ?
|
||||
|
||||
if group_users:
|
||||
user_names = [user.name for user in self.pool.get('res.users').browse(cr, uid, group_users, context=context)]
|
||||
user_names = list(set(user_names))
|
||||
if len(user_names) >= 5:
|
||||
user_names = user_names[:5]
|
||||
user_names += '...'
|
||||
user_names = user_names[:5] + ['...']
|
||||
raise osv.except_osv(_('Warning !'),
|
||||
_('Group(s) cannot be deleted, because some user(s) still belong to them: %s !') % \
|
||||
', '.join(user_names))
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
"access_res_partner_bank_group_user","res_partner_bank group_user","model_res_partner_bank","group_user",1,0,0,0
|
||||
"access_res_partner_bank_group_partner_manager","res_partner_bank group_partner_manager","model_res_partner_bank","group_partner_manager",1,1,1,1
|
||||
"access_res_partner_bank_type_group_partner_manager","res_partner_bank_type group_partner_manager","model_res_partner_bank_type","group_partner_manager",1,1,1,1
|
||||
"access_res_partner_bank_type_group_user","res_partner_bank_type group_user","model_res_partner_bank_type","group_user",1,0,0,0
|
||||
"access_res_partner_bank_type_group_user","res_partner_bank_type group_user","model_res_partner_bank_type",,1,0,0,0
|
||||
"access_res_partner_bank_type_field_group_partner_manager","res_partner_bank_type_field group_partner_manager","model_res_partner_bank_type_field","group_partner_manager",1,1,1,1
|
||||
"access_res_partner_bank_type_field_group_user","res_partner_bank_type_field group_user","model_res_partner_bank_type_field",,1,0,0,0
|
||||
"access_res_partner_canal_group_user","res_partner_canal group_user","model_res_partner_canal","group_user",1,0,0,0
|
||||
|
||||
|
@@ -132,6 +132,48 @@
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, ref('base.user_demo'), [])
|
||||
assert ids, "Demo user should see some partner."
|
||||
-
|
||||
Modify the ir_rule for employee to have a rule that fordids
|
||||
seeing any record.
|
||||
We use a domain with implicit AND operator for later tests
|
||||
on normalization.
|
||||
-
|
||||
!record {model: ir.rule, id: test_rule2}:
|
||||
domain_force: "[('id','=',False),('name','=',False)]"
|
||||
-
|
||||
Check that demo user still sees partners, because group-rules are OR'ed.
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, ref('base.user_demo'), [])
|
||||
assert ids, "Demo user should see some partner."
|
||||
-
|
||||
Create a new group with demo user in it, and a complex rule.
|
||||
Demo should still see partners.
|
||||
-
|
||||
!record {model: res.groups, id: test_group}:
|
||||
name: Test Group
|
||||
users:
|
||||
- base.user_demo
|
||||
-
|
||||
Add the rule to the new group, with a domain containing an implicit AND operator,
|
||||
which is more tricky because it will have to be normalized before combining it.
|
||||
-
|
||||
!record {model: ir.rule, id: test_rule3}:
|
||||
model_id: base.model_res_partner
|
||||
domain_force: "[('name', '!=', False),('id', '!=', False)]"
|
||||
name: test_rule4
|
||||
groups:
|
||||
- test_group
|
||||
perm_unlink: 1
|
||||
perm_write: 1
|
||||
perm_read: 1
|
||||
perm_create: 1
|
||||
-
|
||||
Read the partners again as demo user, which should give results.
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, ref('base.user_demo'), [])
|
||||
assert ids, "Demo user should see partners even with the combined rules."
|
||||
-
|
||||
Delete global domains (to combine only group domains).
|
||||
-
|
||||
@@ -140,7 +182,7 @@
|
||||
assert ids, "Demo user should see some partner."
|
||||
self.unlink(cr, uid, ids)
|
||||
-
|
||||
Read as demo user the partners (three 1=1 domains, no global domain).
|
||||
Read as demo user the partners (several group domains, no global domain).
|
||||
-
|
||||
!python {model: res.partner }: |
|
||||
ids = self.search(cr, ref('base.user_demo'), [])
|
||||
|
||||
@@ -177,4 +177,16 @@
|
||||
res_ids = self.search(cr, uid, [('company_id.partner_id', 'not in', [])])
|
||||
res_ids.sort()
|
||||
assert res_ids == all_ids, "Searching against empty set failed, returns %r" % res_ids
|
||||
-
|
||||
Verify that normalize_domain() works.
|
||||
-
|
||||
!python {model: res.partner}: |
|
||||
from osv import expression
|
||||
norm_domain = domain = ['&',(1,'=',1),('a','=','b')]
|
||||
assert norm_domain == expression.normalize(domain), "Normalized domains should be left untouched"
|
||||
domain = [('x','in',['y','z']),('a.v','=','e'),'|','|',('a','=','b'),'!',('c','>','d'),('e','!=','f'),('g','=','h')]
|
||||
norm_domain = ['&','&','&'] + domain
|
||||
assert norm_domain == expression.normalize(domain), "Non-normalized domains should be properly normalized"
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -188,6 +188,16 @@
|
||||
</rng:element>
|
||||
</rng:define>
|
||||
|
||||
<rng:define name="url">
|
||||
<rng:element name="url">
|
||||
<rng:attribute name="id" />
|
||||
<rng:attribute name="name" />
|
||||
<rng:attribute name="url" />
|
||||
<rng:optional><rng:attribute name="target" /></rng:optional>
|
||||
<rng:empty />
|
||||
</rng:element>
|
||||
</rng:define>
|
||||
|
||||
<rng:define name="act_window">
|
||||
<rng:element name="act_window">
|
||||
<rng:attribute name="id" />
|
||||
@@ -222,6 +232,7 @@
|
||||
<rng:ref name="delete" />
|
||||
<rng:ref name="wizard" />
|
||||
<rng:ref name="act_window" />
|
||||
<rng:ref name="url" />
|
||||
<rng:ref name="assert" />
|
||||
<rng:ref name="report" />
|
||||
<rng:ref name="workflow" />
|
||||
|
||||
@@ -35,7 +35,6 @@ import socket
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import warnings
|
||||
import types
|
||||
from pprint import pformat
|
||||
|
||||
@@ -186,7 +185,6 @@ class ColoredFormatter(DBFormatter):
|
||||
return DBFormatter.format(self, record)
|
||||
|
||||
def init_logger():
|
||||
import os
|
||||
from tools.translate import resetlocale
|
||||
resetlocale()
|
||||
|
||||
|
||||
@@ -23,6 +23,72 @@
|
||||
from openerp.tools import flatten, reverse_enumerate
|
||||
import fields
|
||||
|
||||
NOT_OPERATOR = '!'
|
||||
OR_OPERATOR = '|'
|
||||
AND_OPERATOR = '&'
|
||||
|
||||
TRUE_DOMAIN = [(1,'=',1)]
|
||||
FALSE_DOMAIN = [(0,'=',1)]
|
||||
|
||||
def normalize(domain):
|
||||
"""Returns a normalized version of ``domain_expr``, where all implicit '&' operators
|
||||
have been made explicit. One property of normalized domain expressions is that they
|
||||
can be easily combined together as if they were single domain components.
|
||||
"""
|
||||
assert isinstance(domain, (list, tuple)), "Domains to normalize must have a 'domain' form: a list or tuple of domain components"
|
||||
if not domain:
|
||||
return TRUE_DOMAIN
|
||||
result = []
|
||||
expected = 1 # expected number of expressions
|
||||
op_arity = {NOT_OPERATOR: 1, AND_OPERATOR: 2, OR_OPERATOR: 2}
|
||||
for token in domain:
|
||||
if expected == 0: # more than expected, like in [A, B]
|
||||
result[0:0] = ['&'] # put an extra '&' in front
|
||||
expected = 1
|
||||
result.append(token)
|
||||
if isinstance(token, (list,tuple)): # domain term
|
||||
expected -= 1
|
||||
else:
|
||||
expected += op_arity.get(token, 0) - 1
|
||||
assert expected == 0
|
||||
return result
|
||||
|
||||
def combine(operator, unit, zero, domains):
|
||||
"""Returns a new domain expression where all domain components from ``domains``
|
||||
have been added together using the binary operator ``operator``.
|
||||
|
||||
:param unit: the identity element of the domains "set" with regard to the operation
|
||||
performed by ``operator``, i.e the domain component ``i`` which, when
|
||||
combined with any domain ``x`` via ``operator``, yields ``x``.
|
||||
E.g. [(1,'=',1)] is the typical unit for AND_OPERATOR: adding it
|
||||
to any domain component gives the same domain.
|
||||
:param zero: the absorbing element of the domains "set" with regard to the operation
|
||||
performed by ``operator``, i.e the domain component ``z`` which, when
|
||||
combined with any domain ``x`` via ``operator``, yields ``z``.
|
||||
E.g. [(1,'=',1)] is the typical zero for OR_OPERATOR: as soon as
|
||||
you see it in a domain component the resulting domain is the zero.
|
||||
"""
|
||||
result = []
|
||||
count = 0
|
||||
for domain in domains:
|
||||
if domain == unit:
|
||||
continue
|
||||
if domain == zero:
|
||||
return zero
|
||||
if domain:
|
||||
result += domain
|
||||
count += 1
|
||||
result = [operator] * (count - 1) + result
|
||||
return result
|
||||
|
||||
def AND(domains):
|
||||
""" AND([D1,D2,...]) returns a domain representing D1 and D2 and ... """
|
||||
return combine(AND_OPERATOR, TRUE_DOMAIN, FALSE_DOMAIN, domains)
|
||||
|
||||
def OR(domains):
|
||||
""" OR([D1,D2,...]) returns a domain representing D1 or D2 or ... """
|
||||
return combine(OR_OPERATOR, FALSE_DOMAIN, TRUE_DOMAIN, domains)
|
||||
|
||||
|
||||
class expression(object):
|
||||
"""
|
||||
@@ -32,10 +98,12 @@ class expression(object):
|
||||
For more info: http://christophe-simonis-at-tiny.blogspot.com/2008/08/new-new-domain-notation.html
|
||||
"""
|
||||
|
||||
def _is_operator(self, element):
|
||||
return isinstance(element, (str, unicode)) and element in ['&', '|', '!']
|
||||
@classmethod
|
||||
def _is_operator(cls, element):
|
||||
return isinstance(element, (str, unicode)) and element in [AND_OPERATOR, OR_OPERATOR, NOT_OPERATOR]
|
||||
|
||||
def _is_leaf(self, element, internal=False):
|
||||
@classmethod
|
||||
def _is_leaf(cls, element, internal=False):
|
||||
OPS = ('=', '!=', '<>', '<=', '<', '>', '>=', '=?', '=like', '=ilike', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of')
|
||||
INTERNAL_OPS = OPS + ('inselect',)
|
||||
return (isinstance(element, tuple) or isinstance(element, list)) \
|
||||
@@ -92,8 +160,8 @@ class expression(object):
|
||||
doms = []
|
||||
for o in table.browse(cr, uid, ids, context=context):
|
||||
if doms:
|
||||
doms.insert(0, '|')
|
||||
doms += ['&', ('parent_left', '<', o.parent_right), ('parent_left', '>=', o.parent_left)]
|
||||
doms.insert(0, OR_OPERATOR)
|
||||
doms += [AND_OPERATOR, ('parent_left', '<', o.parent_right), ('parent_left', '>=', o.parent_left)]
|
||||
if prefix:
|
||||
return [(left, 'in', table.search(cr, uid, doms, context=context))]
|
||||
return doms
|
||||
@@ -187,7 +255,7 @@ class expression(object):
|
||||
else:
|
||||
# we assume that the expression is valid
|
||||
# we create a dummy leaf for forcing the parsing of the resulting expression
|
||||
self.__exp[i] = '&'
|
||||
self.__exp[i] = AND_OPERATOR
|
||||
self.__exp.insert(i + 1, self.__DUMMY_LEAF)
|
||||
for j, se in enumerate(subexp):
|
||||
self.__exp.insert(i + 2 + j, se)
|
||||
@@ -389,7 +457,6 @@ class expression(object):
|
||||
]
|
||||
|
||||
self.__exp[i] = ('id', 'inselect', (query1, query2))
|
||||
|
||||
return self
|
||||
|
||||
def __leaf_to_sql(self, leaf, table):
|
||||
@@ -493,10 +560,10 @@ class expression(object):
|
||||
params.insert(0, p)
|
||||
stack.append(q)
|
||||
else:
|
||||
if e == '!':
|
||||
if e == NOT_OPERATOR:
|
||||
stack.append('(NOT (%s))' % (stack.pop(),))
|
||||
else:
|
||||
ops = {'&': ' AND ', '|': ' OR '}
|
||||
ops = {AND_OPERATOR: ' AND ', OR_OPERATOR: ' OR '}
|
||||
q1 = stack.pop()
|
||||
q2 = stack.pop()
|
||||
stack.append('(%s %s %s)' % (q1, ops[e], q2,))
|
||||
|
||||
+12
-3
@@ -936,6 +936,9 @@ class related(function):
|
||||
if self._type=='many2one':
|
||||
ids = filter(None, res.values())
|
||||
if ids:
|
||||
# name_get as root, as seeing the name of a related
|
||||
# object depends on access right of source document,
|
||||
# not target, so user may not have access.
|
||||
ng = dict(obj.pool.get(self._obj).name_get(cr, 1, ids, context=context))
|
||||
for r in res:
|
||||
if res[r]:
|
||||
@@ -1079,7 +1082,10 @@ class property(function):
|
||||
value = properties.get_by_record(cr, uid, prop, context=context)
|
||||
res[prop.res_id.id][prop.fields_id.name] = value or False
|
||||
if value and (prop.type == 'many2one'):
|
||||
record_exists = obj.pool.get(value._name).exists(cr, uid, value.id)
|
||||
# check existence as root, as seeing the name of a related
|
||||
# object depends on access right of source document,
|
||||
# not target, so user may not have access.
|
||||
record_exists = obj.pool.get(value._name).exists(cr, 1, value.id)
|
||||
if record_exists:
|
||||
replaces.setdefault(value._name, {})
|
||||
replaces[value._name][value.id] = True
|
||||
@@ -1087,8 +1093,11 @@ class property(function):
|
||||
res[prop.res_id.id][prop.fields_id.name] = False
|
||||
|
||||
for rep in replaces:
|
||||
nids = obj.pool.get(rep).search(cr, uid, [('id','in',replaces[rep].keys())], context=context)
|
||||
replaces[rep] = dict(obj.pool.get(rep).name_get(cr, uid, nids, context=context))
|
||||
# search+name_get as root, as seeing the name of a related
|
||||
# object depends on access right of source document,
|
||||
# not target, so user may not have access.
|
||||
nids = obj.pool.get(rep).search(cr, 1, [('id','in',replaces[rep].keys())], context=context)
|
||||
replaces[rep] = dict(obj.pool.get(rep).name_get(cr, 1, nids, context=context))
|
||||
|
||||
for prop in prop_name:
|
||||
for id in ids:
|
||||
|
||||
+6
-3
@@ -972,7 +972,7 @@ class orm_template(object):
|
||||
if field[len(prefix)]=='id':
|
||||
try:
|
||||
data_res_id = _get_id(model_name, line[i], current_module, 'id')
|
||||
except ValueError, e:
|
||||
except ValueError:
|
||||
pass
|
||||
xml_id = line[i]
|
||||
continue
|
||||
@@ -2069,8 +2069,9 @@ class orm_template(object):
|
||||
class orm_memory(orm_template):
|
||||
|
||||
_protected = ['read', 'write', 'create', 'default_get', 'perm_read', 'unlink', 'fields_get', 'fields_view_get', 'search', 'name_get', 'distinct_field_get', 'name_search', 'copy', 'import_data', 'search_count', 'exists']
|
||||
_max_count = config.get('osv_memory_count_limit')
|
||||
_max_hours = config.get('osv_memory_age_limit')
|
||||
_inherit_fields = {}
|
||||
_max_count = None
|
||||
_max_hours = None
|
||||
_check_time = 20
|
||||
|
||||
@classmethod
|
||||
@@ -2082,6 +2083,8 @@ class orm_memory(orm_template):
|
||||
self.datas = {}
|
||||
self.next_id = 0
|
||||
self.check_id = 0
|
||||
self._max_count = config.get('osv_memory_count_limit')
|
||||
self._max_hours = config.get('osv_memory_age_limit')
|
||||
cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
|
||||
|
||||
def _check_access(self, uid, object_id, mode):
|
||||
|
||||
@@ -45,7 +45,6 @@ import openerp.loglevels as loglevels
|
||||
import openerp.pooler as pooler
|
||||
from config import config
|
||||
from translate import _
|
||||
from yaml_import import convert_yaml_import
|
||||
|
||||
# List of etree._Element subclasses that we choose to ignore when parsing XML.
|
||||
from misc import SKIPPED_ELEMENT_TYPES
|
||||
@@ -402,7 +401,7 @@ form: module.record_id""" % (xml_id,)
|
||||
self._remove_ir_values(cr, string, value, model)
|
||||
|
||||
def _tag_url(self, cr, rec, data_node=None):
|
||||
url = rec.get("string",'').encode('utf8')
|
||||
url = rec.get("url",'').encode('utf8')
|
||||
target = rec.get("target",'').encode('utf8')
|
||||
name = rec.get("name",'').encode('utf8')
|
||||
xml_id = rec.get('id','').encode('utf8')
|
||||
@@ -654,6 +653,12 @@ form: module.record_id""" % (xml_id,)
|
||||
resw = cr.fetchone()
|
||||
if (not values.get('name', False)) and resw:
|
||||
values['name'] = resw[0]
|
||||
elif a_type=='url':
|
||||
a_id = self.id_get(cr, a_action)
|
||||
cr.execute('select name from ir_act_url where id=%s', (int(a_id),))
|
||||
resw = cr.fetchone()
|
||||
if (not values.get('name')) and resw:
|
||||
values['name'] = resw[0]
|
||||
if rec.get('sequence'):
|
||||
values['sequence'] = int(rec.get('sequence'))
|
||||
if rec.get('icon'):
|
||||
|
||||
@@ -177,7 +177,7 @@ class YamlInterpreter(object):
|
||||
try:
|
||||
_, id = self.pool.get('ir.model.data').get_object_reference(self.cr, self.uid, module, checked_xml_id)
|
||||
self.id_map[xml_id] = id
|
||||
except ValueError, e:
|
||||
except ValueError:
|
||||
raise ValueError("""%s not found when processing %s.
|
||||
This Yaml file appears to depend on missing data. This often happens for
|
||||
tests that belong to a module's test suite and depend on each other.""" % (checked_xml_id, self.filename))
|
||||
@@ -488,7 +488,6 @@ class YamlInterpreter(object):
|
||||
if self.isnoupdate(function) and self.mode != 'init':
|
||||
return
|
||||
model = self.get_model(function.model)
|
||||
context = self.get_context(function, self.eval_context)
|
||||
if function.eval:
|
||||
args = self.process_eval(function.eval)
|
||||
else:
|
||||
|
||||
@@ -32,7 +32,7 @@ class Record(YamlTag):
|
||||
return '!record {model: %s, id: %s}:' % (str(self.model,), str(self.id,))
|
||||
|
||||
class Python(YamlTag):
|
||||
def __init__(self, model, severity=logging.WARNING, name="", **kwargs):
|
||||
def __init__(self, model, severity=logging.ERROR, name="", **kwargs):
|
||||
self.model= model
|
||||
self.severity = severity
|
||||
self.name = name
|
||||
|
||||
Reference in New Issue
Block a user