Merge lp:~openerp-dev/openobject-server/trunk-message-sub-type-apa into lp:openobject-server

Proposed by Christophe Matthieu (OpenERP)
Status: Work in progress
Proposed branch: lp:~openerp-dev/openobject-server/trunk-message-sub-type-apa
Merge into: lp:openobject-server
Diff against target: 543 lines (+175/-128)
7 files modified
openerp/addons/base/res/res_company.py (+2/-2)
openerp/addons/base/res/res_partner.py (+4/-3)
openerp/addons/base/res/res_users.py (+33/-22)
openerp/osv/expression.py (+55/-90)
openerp/osv/fields.py (+78/-9)
openerp/osv/orm.py (+0/-1)
openerp/osv/osv.py (+3/-1)
To merge this branch: bzr merge lp:~openerp-dev/openobject-server/trunk-message-sub-type-apa
Reviewer Review Type Date Requested Status
OpenERP Core Team Pending
Review via email: mp+125686@code.launchpad.net
To post a comment you must log in.
4448. By Christophe Matthieu (OpenERP)

[MERGE] from trunk

Unmerged revisions

4448. By Christophe Matthieu (OpenERP)

[MERGE] from trunk

4447. By Christophe Matthieu (OpenERP)

[MERGE] from trunk

4446. By Christophe Matthieu (OpenERP)

[MERGE] merge from trunk-fix-mail-mail-rules-tde

4445. By Olivier Dony (Odoo)

[IMP] fields.m2m/expression: Refactoring of m2m domain resolution to delegate to field

- Support for dynamic/lambda domains in m2m.get()
- Moved m2m domain item resolution into fields.m2m.search(), similarly to how it worked for function fields
- Tried to preserve the existing behaviors, even the strange stuff like ('m2m','>=',[1,2,3]) etc.
- minor cleanup (dup'ed import)
- had to defer references to MAGIC_COLUMN to avoid dependency loop issue during import of orm/fields/expression

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openerp/addons/base/res/res_company.py'
2--- openerp/addons/base/res/res_company.py 2012-09-13 12:20:11 +0000
3+++ openerp/addons/base/res/res_company.py 2012-09-24 10:54:32 +0000
4@@ -195,11 +195,11 @@
5 ]
6
7 ids = proxy.search(cr, uid, args, context=context)
8- user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
9+ user = self.pool.get('res.users').read(cr, uid, [uid], ['company_id'], context=context)[0]
10 for rule in proxy.browse(cr, uid, ids, context):
11 if eval(rule.expression, {'context': context, 'user': user}):
12 return rule.company_dest_id.id
13- return user.company_id.id
14+ return user['company_id'][0]
15
16 @tools.ormcache()
17 def _get_company_children(self, cr, uid=None, company=None):
18
19=== modified file 'openerp/addons/base/res/res_partner.py'
20--- openerp/addons/base/res/res_partner.py 2012-09-13 18:27:36 +0000
21+++ openerp/addons/base/res/res_partner.py 2012-09-24 10:54:32 +0000
22@@ -22,6 +22,7 @@
23 import math
24 import openerp
25 from osv import osv, fields
26+from openerp import SUPERUSER_ID
27 import re
28 import tools
29 from tools.translate import _
30@@ -33,7 +34,7 @@
31 class format_address(object):
32 def fields_view_get_address(self, cr, uid, arch, context={}):
33 user_obj = self.pool.get('res.users')
34- fmt = user_obj.browse(cr, uid, uid,context).company_id.country_id
35+ fmt = user_obj.browse(cr, SUPERUSER_ID, uid, context).company_id.country_id
36 fmt = fmt and fmt.address_format
37 layouts = {
38 '%(city)s %(state_code)s\n%(zip)s': """
39@@ -392,7 +393,7 @@
40 - otherwise: default, everything is set as the name """
41 match = re.search(r'([^\s,<@]+@[^>\s,]+)', text)
42 if match:
43- email = match.group(1)
44+ email = match.group(1)
45 name = text[:text.index(email)].replace('"','').replace('<','').strip()
46 else:
47 name, email = text, ''
48@@ -440,7 +441,7 @@
49 def find_or_create(self, cr, uid, email, context=None):
50 """ Find a partner with the given ``email`` or use :py:method:`~.name_create`
51 to create one
52-
53+
54 :param str email: email-like string, which should contain at least one email,
55 e.g. ``"Raoul Grosbedon <r.g@grosbedon.fr>"``"""
56 assert email, 'an email is required for find_or_create to work'
57
58=== modified file 'openerp/addons/base/res/res_users.py'
59--- openerp/addons/base/res/res_users.py 2012-09-12 04:35:51 +0000
60+++ openerp/addons/base/res/res_users.py 2012-09-24 10:54:32 +0000
61@@ -52,7 +52,7 @@
62 else:
63 res[g.id] = g.name
64 return res
65-
66+
67 def _search_group(self, cr, uid, obj, name, args, context=None):
68 operand = args[0][2]
69 operator = args[0][1]
70@@ -64,7 +64,7 @@
71 group_name = values[1]
72 where = ['|',('category_id.name', operator, application_name)] + where
73 return where
74-
75+
76 _columns = {
77 'name': fields.char('Name', size=64, required=True, translate=True),
78 'users': fields.many2many('res.users', 'res_groups_users_rel', 'gid', 'uid', 'Users'),
79@@ -138,7 +138,7 @@
80
81 def _get_password(self, cr, uid, ids, arg, karg, context=None):
82 return dict.fromkeys(ids, '')
83-
84+
85 _columns = {
86 'id': fields.integer('ID'),
87 'login_date': fields.date('Latest connection', select=1),
88@@ -193,21 +193,6 @@
89 partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context=context)]
90 return self.pool.get('res.partner').onchange_address(cr, uid, partner_ids, use_parent_address, parent_id, context=context)
91
92- def read(self,cr, uid, ids, fields=None, context=None, load='_classic_read'):
93- def override_password(o):
94- if 'password' in o and ( 'id' not in o or o['id'] != uid ):
95- o['password'] = '********'
96- return o
97- result = super(res_users, self).read(cr, uid, ids, fields, context, load)
98- canwrite = self.pool.get('ir.model.access').check(cr, uid, 'res.users', 'write', False)
99- if not canwrite:
100- if isinstance(ids, (int, long)):
101- result = override_password(result)
102- else:
103- result = map(override_password, result)
104- return result
105-
106-
107 def _check_company(self, cr, uid, ids, context=None):
108 return all(((this.company_id in this.company_ids) or not this.company_ids) for this in self.browse(cr, uid, ids, context))
109
110@@ -276,8 +261,34 @@
111 return self.pool.get('res.partner').fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
112 return super(res_users, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
113
114- # User can write to a few of her own fields (but not her groups for example)
115- SELF_WRITEABLE_FIELDS = ['password', 'signature', 'action_id', 'company_id', 'email', 'name', 'image', 'image_medium', 'image_small']
116+ # User can write on a few of his own fields (but not his groups for example)
117+ SELF_WRITEABLE_FIELDS = ['password', 'signature', 'action_id', 'company_id', 'email', 'name', 'image', 'image_medium', 'image_small', 'lang', 'tz']
118+ # User can read a few of his own fields
119+ SELF_READABLE_FIELDS = ['signature', 'company_id', 'login', 'email', 'name', 'image', 'image_medium', 'image_small', 'lang', 'tz', 'groups_id', 'partner_id']
120+
121+ def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
122+ def override_password(o):
123+ if 'password' in o and ('id' not in o or o['id'] != uid):
124+ o['password'] = '********'
125+ return o
126+
127+ if (isinstance(ids, (list, tuple)) and ids == [uid]) or ids == uid:
128+ for key in fields:
129+ if not (key in self.SELF_READABLE_FIELDS or key.startswith('context_') or key in ['__last_update']):
130+ break
131+ else:
132+ # safe fields only, so we read as super-user to bypass access rights
133+ uid = SUPERUSER_ID
134+
135+ result = super(res_users, self).read(cr, uid, ids, fields=fields, context=context, load=load)
136+ canwrite = self.pool.get('ir.model.access').check(cr, uid, 'res.users', 'write', False)
137+ if not canwrite:
138+ if isinstance(ids, (int, long)):
139+ result = override_password(result)
140+ else:
141+ result = map(override_password, result)
142+
143+ return result
144
145 def write(self, cr, uid, ids, values, context=None):
146 if not hasattr(ids, '__iter__'):
147@@ -495,14 +506,14 @@
148 """
149 assert group_ext_id and '.' in group_ext_id, "External ID must be fully qualified"
150 module, ext_id = group_ext_id.split('.')
151- cr.execute("""SELECT 1 FROM res_groups_users_rel WHERE uid=%s AND gid IN
152+ cr.execute("""SELECT 1 FROM res_groups_users_rel WHERE uid=%s AND gid IN
153 (SELECT res_id FROM ir_model_data WHERE module=%s AND name=%s)""",
154 (uid, module, ext_id))
155 return bool(cr.fetchone())
156
157
158 #
159-# Extension of res.groups and res.users with a relation for "implied" or
160+# Extension of res.groups and res.users with a relation for "implied" or
161 # "inherited" groups. Once a user belongs to a group, it automatically belongs
162 # to the implied groups (transitively).
163 #
164
165=== modified file 'openerp/osv/expression.py'
166--- openerp/osv/expression.py 2012-08-02 15:25:53 +0000
167+++ openerp/osv/expression.py 2012-09-24 10:54:32 +0000
168@@ -126,8 +126,7 @@
169
170 from openerp.tools import flatten, reverse_enumerate
171 import fields
172-import openerp.modules
173-from openerp.osv.orm import MAGIC_COLUMNS
174+import openerp
175
176 #.apidoc title: Domain Expressions
177
178@@ -341,6 +340,46 @@
179 (select_field, from_table, select_field))
180 return [r[0] for r in cr.fetchall()]
181
182+def resolve_child_of(cr, uid, left, ids, left_model, parent=None, prefix='', context=None):
183+ """Returns a domain implementing the child_of operator for [(left,child_of,ids)],
184+ either as a range using the parent_left/right tree lookup fields (when available),
185+ or as an expanded [(left,in,child_ids)]"""
186+ if left_model._parent_store and (not left_model.pool._init):
187+ # TODO: Improve where joins are implemented for many with '.', replace by:
188+ # doms += ['&',(prefix+'.parent_left','<',o.parent_right),(prefix+'.parent_left','>=',o.parent_left)]
189+ doms = []
190+ for o in left_model.browse(cr, uid, ids, context=context):
191+ if doms:
192+ doms.insert(0, OR_OPERATOR)
193+ doms += [AND_OPERATOR, ('parent_left', '<', o.parent_right), ('parent_left', '>=', o.parent_left)]
194+ if prefix:
195+ return [(left, 'in', left_model.search(cr, uid, doms, context=context))]
196+ return doms
197+ else:
198+ def recursive_children(ids, model, parent_field):
199+ if not ids:
200+ return []
201+ ids2 = model.search(cr, uid, [(parent_field, 'in', ids)], context=context)
202+ return ids + recursive_children(ids2, model, parent_field)
203+ return [(left, 'in', recursive_children(ids, left_model, parent or left_model._parent_name))]
204+
205+
206+def relationship_value_to_ids(cr, uid, value, model, context=None):
207+ """ Normalize a search value for a relationship field (i.e. one or more strings,
208+ one or more IDs, possibly with an extra False) to a corresponding list of IDs"""
209+ names = []
210+ if isinstance(value, basestring):
211+ names = [value]
212+ if value and isinstance(value, (tuple, list)) and isinstance(value[0], basestring):
213+ names = value
214+ if names:
215+ return flatten([[x[0] for x in model.name_search(cr, uid, n, [], 'ilike', context=context, limit=None)] \
216+ for n in names])
217+ elif isinstance(value, (int, long)):
218+ return [value]
219+ return list(value)
220+
221+
222 class expression(object):
223 """
224 parse a domain expression
225@@ -369,43 +408,6 @@
226 self.__main_table = table
227 self.__all_tables.add(table)
228
229- def child_of_domain(left, ids, left_model, parent=None, prefix=''):
230- """Returns a domain implementing the child_of operator for [(left,child_of,ids)],
231- either as a range using the parent_left/right tree lookup fields (when available),
232- or as an expanded [(left,in,child_ids)]"""
233- if left_model._parent_store and (not left_model.pool._init):
234- # TODO: Improve where joins are implemented for many with '.', replace by:
235- # doms += ['&',(prefix+'.parent_left','<',o.parent_right),(prefix+'.parent_left','>=',o.parent_left)]
236- doms = []
237- for o in left_model.browse(cr, uid, ids, context=context):
238- if doms:
239- doms.insert(0, OR_OPERATOR)
240- doms += [AND_OPERATOR, ('parent_left', '<', o.parent_right), ('parent_left', '>=', o.parent_left)]
241- if prefix:
242- return [(left, 'in', left_model.search(cr, uid, doms, context=context))]
243- return doms
244- else:
245- def recursive_children(ids, model, parent_field):
246- if not ids:
247- return []
248- ids2 = model.search(cr, uid, [(parent_field, 'in', ids)], context=context)
249- return ids + recursive_children(ids2, model, parent_field)
250- return [(left, 'in', recursive_children(ids, left_model, parent or left_model._parent_name))]
251-
252- def to_ids(value, field_obj):
253- """Normalize a single id or name, or a list of those, into a list of ids"""
254- names = []
255- if isinstance(value, basestring):
256- names = [value]
257- if value and isinstance(value, (tuple, list)) and isinstance(value[0], basestring):
258- names = value
259- if names:
260- return flatten([[x[0] for x in field_obj.name_search(cr, uid, n, [], 'ilike', context=context, limit=None)] \
261- for n in names])
262- elif isinstance(value, (int, long)):
263- return [value]
264- return list(value)
265-
266 i = -1
267 while i + 1<len(self.__exp):
268 i += 1
269@@ -445,14 +447,14 @@
270
271 if not field:
272 if left == 'id' and operator == 'child_of':
273- ids2 = to_ids(right, table)
274- dom = child_of_domain(left, ids2, working_table)
275+ ids2 = relationship_value_to_ids(cr, uid, right, table, context)
276+ dom = resolve_child_of(cr, uid, left, ids2, working_table, context=context)
277 self.__exp = self.__exp[:i] + dom + self.__exp[i+1:]
278 else:
279 # field could not be found in model columns, it's probably invalid, unless
280 # it's one of the _log_access special fields
281 # TODO: make these fields explicitly available in self.columns instead!
282- if field_path[0] not in MAGIC_COLUMNS:
283+ if field_path[0] not in openerp.osv.orm.MAGIC_COLUMNS:
284 raise ValueError("Invalid field %r in domain expression %r" % (left, exp))
285 continue
286
287@@ -499,11 +501,11 @@
288 elif field._type == 'one2many':
289 # Applying recursivity on field(one2many)
290 if operator == 'child_of':
291- ids2 = to_ids(right, field_obj)
292+ ids2 = relationship_value_to_ids(cr, uid, right, field_obj, context)
293 if field._obj != working_table._name:
294- dom = child_of_domain(left, ids2, field_obj, prefix=field._obj)
295+ dom = resolve_child_of(cr, uid, left, ids2, field_obj, prefix=field._obj, context=context)
296 else:
297- dom = child_of_domain('id', ids2, working_table, parent=left)
298+ dom = resolve_child_of(cr, uid, 'id', ids2, working_table, parent=left, context=context)
299 self.__exp = self.__exp[:i] + dom + self.__exp[i+1:]
300
301 else:
302@@ -535,53 +537,16 @@
303 self.__exp[i] = ('id', o2m_op, select_distinct_from_where_not_null(cr, field._fields_id, field_obj._table))
304
305 elif field._type == 'many2many':
306- rel_table, rel_id1, rel_id2 = field._sql_names(working_table)
307- #FIXME
308- if operator == 'child_of':
309- def _rec_convert(ids):
310- if field_obj == table:
311- return ids
312- return select_from_where(cr, rel_id1, rel_table, rel_id2, ids, operator)
313-
314- ids2 = to_ids(right, field_obj)
315- dom = child_of_domain('id', ids2, field_obj)
316- ids2 = field_obj.search(cr, uid, dom, context=context)
317- self.__exp[i] = ('id', 'in', _rec_convert(ids2))
318- else:
319- call_null_m2m = True
320- if right is not False:
321- if isinstance(right, basestring):
322- res_ids = [x[0] for x in field_obj.name_search(cr, uid, right, [], operator, context=context)]
323- if res_ids:
324- operator = 'in'
325- else:
326- if not isinstance(right, list):
327- res_ids = [right]
328- else:
329- res_ids = right
330- if not res_ids:
331- if operator in ['like','ilike','in','=']:
332- #no result found with given search criteria
333- call_null_m2m = False
334- self.__exp[i] = FALSE_LEAF
335- else:
336- operator = 'in' # operator changed because ids are directly related to main object
337- else:
338- call_null_m2m = False
339- m2m_op = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in'
340- self.__exp[i] = ('id', m2m_op, select_from_where(cr, rel_id1, rel_table, rel_id2, res_ids, operator) or [0])
341-
342- if call_null_m2m:
343- m2m_op = 'in' if operator in NEGATIVE_TERM_OPERATORS else 'not in'
344- self.__exp[i] = ('id', m2m_op, select_distinct_from_where_not_null(cr, rel_id1, rel_table))
345-
346+ dom = field.search(cr, uid, working_table, left, [self.__exp[i]], context=context)
347+ self.__exp = self.__exp[:i] + dom + self.__exp[i+1:]
348+
349 elif field._type == 'many2one':
350 if operator == 'child_of':
351- ids2 = to_ids(right, field_obj)
352+ ids2 = relationship_value_to_ids(cr, uid, right, field_obj, context)
353 if field._obj != working_table._name:
354- dom = child_of_domain(left, ids2, field_obj, prefix=field._obj)
355+ dom = resolve_child_of(cr, uid, left, ids2, field_obj, prefix=field._obj, context=context)
356 else:
357- dom = child_of_domain('id', ids2, working_table, parent=left)
358+ dom = resolve_child_of(cr, uid, 'id', ids2, working_table, parent=left, context=context)
359 self.__exp = self.__exp[:i] + dom + self.__exp[i+1:]
360 else:
361 def _get_expression(field_obj, cr, uid, left, right, operator, context=None):
362@@ -668,7 +633,7 @@
363 assert operator in (TERM_OPERATORS + ('inselect',)), \
364 "Invalid operator %r in domain term %r" % (operator, leaf)
365 assert leaf in (TRUE_LEAF, FALSE_LEAF) or left in table._all_columns \
366- or left in MAGIC_COLUMNS, "Invalid field %r in domain term %r" % (left, leaf)
367+ or left in openerp.osv.orm.MAGIC_COLUMNS, "Invalid field %r in domain term %r" % (left, leaf)
368
369 if leaf == TRUE_LEAF:
370 query = 'TRUE'
371@@ -759,7 +724,7 @@
372 query = '(unaccent(%s."%s") %s unaccent(%s))' % (table._table, left, sql_operator, format)
373 else:
374 query = '(%s."%s" %s %s)' % (table._table, left, sql_operator, format)
375- elif left in MAGIC_COLUMNS:
376+ elif left in openerp.osv.orm.MAGIC_COLUMNS:
377 query = "(%s.\"%s\" %s %%s)" % (table._table, left, sql_operator)
378 params = right
379 else: # Must not happen
380
381=== modified file 'openerp/osv/fields.py'
382--- openerp/osv/fields.py 2012-09-10 14:46:09 +0000
383+++ openerp/osv/fields.py 2012-09-24 10:54:32 +0000
384@@ -35,6 +35,7 @@
385 import base64
386 import datetime as DT
387 import logging
388+import simplejson
389 import pytz
390 import re
391 import xmlrpclib
392@@ -42,11 +43,12 @@
393
394 import openerp
395 import openerp.tools as tools
396+from openerp import SUPERUSER_ID
397+from openerp.osv.expression import relationship_value_to_ids, resolve_child_of, \
398+ FALSE_LEAF, NEGATIVE_TERM_OPERATORS
399 from openerp.tools.translate import _
400 from openerp.tools import float_round, float_repr
401-import simplejson
402 from openerp.tools.html_sanitize import html_sanitize
403-from openerp import SUPERUSER_ID
404
405 _logger = logging.getLogger(__name__)
406
407@@ -515,6 +517,12 @@
408 for id in ids:
409 res[id] = []
410
411+ # static domains are lists, and are evaluated both here and on client-side, while string
412+ # domains are dynamic and evaluated on client-side only (thus ignored here)
413+ # FIXME: make this distinction explicit in API!
414+ domain = self._domain(obj) if callable(self._domain) else self._domain
415+ if not isinstance(domain, list): domain = []
416+
417 domain = self._domain(obj) if callable(self._domain) else self._domain
418 ids2 = obj.pool.get(self._obj).search(cr, user, domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
419 for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
420@@ -676,6 +684,19 @@
421 % values
422 return query, where_params
423
424+ def _search_query(self, model, params, operator='in'):
425+ """ Extracted from ``search`` to facilitate fine-tuning of the generated
426+ query. """
427+ rel_table, rel_id1, rel_id2 = self._sql_names(model)
428+ query = """SELECT DISTINCT %(rel_table)s.%(rel_id1)s
429+ FROM %(rel_table)s """ % locals()
430+ if params.get('ids'):
431+ if operator not in ('>','<','>=','<='):
432+ operator = 'in'
433+ query += " WHERE %(rel_table)s.%(rel_id2)s %(operator)s %%(ids)s""" % locals()
434+ return query, params
435+
436+
437 def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):
438 if not context:
439 context = {}
440@@ -694,9 +715,10 @@
441 rel, id1, id2 = self._sql_names(model)
442
443 # static domains are lists, and are evaluated both here and on client-side, while string
444- # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
445+ # domains are dynamic and evaluated on client-side only (thus ignored here)
446 # FIXME: make this distinction explicit in API!
447- domain = isinstance(self._domain, list) and self._domain or []
448+ domain = self._domain(obj) if callable(self._domain) else self._domain
449+ if not isinstance(domain, list): domain = []
450
451 wquery = obj._where_calc(cr, user, domain, context=context)
452 obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
453@@ -767,11 +789,58 @@
454 for act_nbr in act[2]:
455 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))
456
457- #
458- # TODO: use a name_search
459- #
460- def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
461- return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
462+
463+ def _search(self, cr, uid, model, target_ids, operator='in'):
464+ """ Return IDs of current model that are related to the given target_ids
465+ through the m2m relationship. If target_ids is None, returns
466+ all IDs that are currently involved in a m2m relationship. """
467+ if operator in ('>','<','>=','<='):
468+ target_ids = target_ids and target_ids[0]
469+ else:
470+ target_ids = target_ids and tuple(target_ids)
471+ params = {'ids': target_ids}
472+ query, params = self._search_query(model, params, operator=operator)
473+ cr.execute(query, params)
474+ return [r[0] for r in cr.fetchall()]
475+
476+
477+ def search(self, cr, uid, model, name, domain, context=None):
478+ """ Implements domain resolution, transforming an input single-item domain
479+ on this m2m column into an equivalent domain that does not involve
480+ the m2m column anymore. Usually this resulting domain should only
481+ contain one item filtering on the ``id`` column.
482+
483+ :param obj: the model to which the m2m field belongs.
484+ :param name: the name of the m2m field being searched.
485+ :param domain: the one-item search domain applying to this m2m field.
486+ :return: a new domain (possibly with multiple items) corresponding to the
487+ requested results, and not referencing the m2m field anymore.
488+ """
489+
490+ target_model = model.pool.get(self._obj)
491+ _, operator, right = domain[0] # 1-item domain
492+ target_ids = relationship_value_to_ids(cr, uid, right, target_model, context)
493+ m2m_op = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in'
494+
495+ if operator == 'child_of':
496+ child_of_domain = resolve_child_of(cr, uid, 'id', target_ids, target_model, context=context)
497+ target_ids = target_model.search(cr, uid, child_of_domain, context=context)
498+ related_ids = self._search(cr, uid, model, target_ids)
499+ return [('id', m2m_op, related_ids)]
500+
501+ if right is not False:
502+ if target_ids:
503+ return [('id', m2m_op, self._search(cr, uid, model, target_ids, operator) or [0])]
504+ elif operator in ['like','ilike','in','=']:
505+ return [FALSE_LEAF]
506+ else:
507+ operator = 'in'
508+
509+ # We have no matching result, so if a negative operator was used we should
510+ # return all records that have at least one m2m relationship, otherwise we
511+ # should return all records that don't have any.
512+ m2m_op = 'in' if operator in NEGATIVE_TERM_OPERATORS else 'not in'
513+ return [('id', m2m_op, self._search(cr, uid, model, None))]
514
515 @classmethod
516 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
517
518=== modified file 'openerp/osv/orm.py'
519--- openerp/osv/orm.py 2012-09-20 14:55:19 +0000
520+++ openerp/osv/orm.py 2012-09-24 10:54:32 +0000
521@@ -63,7 +63,6 @@
522 from openerp.tools.translate import _
523 from openerp import SUPERUSER_ID
524 from query import Query
525-from openerp import SUPERUSER_ID
526
527 _logger = logging.getLogger(__name__)
528 _schema = logging.getLogger(__name__ + '.schema')
529
530=== modified file 'openerp/osv/osv.py'
531--- openerp/osv/osv.py 2012-09-18 13:04:36 +0000
532+++ openerp/osv/osv.py 2012-09-24 10:54:32 +0000
533@@ -120,7 +120,9 @@
534 raise except_osv('Database not ready', 'Currently, this database is not fully loaded and can not be used.')
535 return f(self, dbname, *args, **kwargs)
536 except orm.except_orm, inst:
537- raise except_osv(inst.name, inst.value)
538+ raise
539+ # TDE: commented to have more valuable stack traces
540+ # raise except_osv(inst.name, inst.value)
541 except except_osv:
542 raise
543 except IntegrityError, inst: