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
=== modified file 'openerp/addons/base/res/res_company.py'
--- openerp/addons/base/res/res_company.py 2012-09-13 12:20:11 +0000
+++ openerp/addons/base/res/res_company.py 2012-09-24 10:54:32 +0000
@@ -195,11 +195,11 @@
195 ]195 ]
196196
197 ids = proxy.search(cr, uid, args, context=context)197 ids = proxy.search(cr, uid, args, context=context)
198 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)198 user = self.pool.get('res.users').read(cr, uid, [uid], ['company_id'], context=context)[0]
199 for rule in proxy.browse(cr, uid, ids, context):199 for rule in proxy.browse(cr, uid, ids, context):
200 if eval(rule.expression, {'context': context, 'user': user}):200 if eval(rule.expression, {'context': context, 'user': user}):
201 return rule.company_dest_id.id201 return rule.company_dest_id.id
202 return user.company_id.id202 return user['company_id'][0]
203203
204 @tools.ormcache()204 @tools.ormcache()
205 def _get_company_children(self, cr, uid=None, company=None):205 def _get_company_children(self, cr, uid=None, company=None):
206206
=== modified file 'openerp/addons/base/res/res_partner.py'
--- openerp/addons/base/res/res_partner.py 2012-09-13 18:27:36 +0000
+++ openerp/addons/base/res/res_partner.py 2012-09-24 10:54:32 +0000
@@ -22,6 +22,7 @@
22import math22import math
23import openerp23import openerp
24from osv import osv, fields24from osv import osv, fields
25from openerp import SUPERUSER_ID
25import re26import re
26import tools27import tools
27from tools.translate import _28from tools.translate import _
@@ -33,7 +34,7 @@
33class format_address(object):34class format_address(object):
34 def fields_view_get_address(self, cr, uid, arch, context={}):35 def fields_view_get_address(self, cr, uid, arch, context={}):
35 user_obj = self.pool.get('res.users')36 user_obj = self.pool.get('res.users')
36 fmt = user_obj.browse(cr, uid, uid,context).company_id.country_id37 fmt = user_obj.browse(cr, SUPERUSER_ID, uid, context).company_id.country_id
37 fmt = fmt and fmt.address_format38 fmt = fmt and fmt.address_format
38 layouts = {39 layouts = {
39 '%(city)s %(state_code)s\n%(zip)s': """40 '%(city)s %(state_code)s\n%(zip)s': """
@@ -392,7 +393,7 @@
392 - otherwise: default, everything is set as the name """393 - otherwise: default, everything is set as the name """
393 match = re.search(r'([^\s,<@]+@[^>\s,]+)', text)394 match = re.search(r'([^\s,<@]+@[^>\s,]+)', text)
394 if match:395 if match:
395 email = match.group(1) 396 email = match.group(1)
396 name = text[:text.index(email)].replace('"','').replace('<','').strip()397 name = text[:text.index(email)].replace('"','').replace('<','').strip()
397 else:398 else:
398 name, email = text, ''399 name, email = text, ''
@@ -440,7 +441,7 @@
440 def find_or_create(self, cr, uid, email, context=None):441 def find_or_create(self, cr, uid, email, context=None):
441 """ Find a partner with the given ``email`` or use :py:method:`~.name_create`442 """ Find a partner with the given ``email`` or use :py:method:`~.name_create`
442 to create one443 to create one
443 444
444 :param str email: email-like string, which should contain at least one email,445 :param str email: email-like string, which should contain at least one email,
445 e.g. ``"Raoul Grosbedon <r.g@grosbedon.fr>"``"""446 e.g. ``"Raoul Grosbedon <r.g@grosbedon.fr>"``"""
446 assert email, 'an email is required for find_or_create to work'447 assert email, 'an email is required for find_or_create to work'
447448
=== modified file 'openerp/addons/base/res/res_users.py'
--- openerp/addons/base/res/res_users.py 2012-09-12 04:35:51 +0000
+++ openerp/addons/base/res/res_users.py 2012-09-24 10:54:32 +0000
@@ -52,7 +52,7 @@
52 else:52 else:
53 res[g.id] = g.name53 res[g.id] = g.name
54 return res54 return res
55 55
56 def _search_group(self, cr, uid, obj, name, args, context=None):56 def _search_group(self, cr, uid, obj, name, args, context=None):
57 operand = args[0][2]57 operand = args[0][2]
58 operator = args[0][1]58 operator = args[0][1]
@@ -64,7 +64,7 @@
64 group_name = values[1]64 group_name = values[1]
65 where = ['|',('category_id.name', operator, application_name)] + where65 where = ['|',('category_id.name', operator, application_name)] + where
66 return where66 return where
67 67
68 _columns = {68 _columns = {
69 'name': fields.char('Name', size=64, required=True, translate=True),69 'name': fields.char('Name', size=64, required=True, translate=True),
70 'users': fields.many2many('res.users', 'res_groups_users_rel', 'gid', 'uid', 'Users'),70 'users': fields.many2many('res.users', 'res_groups_users_rel', 'gid', 'uid', 'Users'),
@@ -138,7 +138,7 @@
138138
139 def _get_password(self, cr, uid, ids, arg, karg, context=None):139 def _get_password(self, cr, uid, ids, arg, karg, context=None):
140 return dict.fromkeys(ids, '')140 return dict.fromkeys(ids, '')
141 141
142 _columns = {142 _columns = {
143 'id': fields.integer('ID'),143 'id': fields.integer('ID'),
144 'login_date': fields.date('Latest connection', select=1),144 'login_date': fields.date('Latest connection', select=1),
@@ -193,21 +193,6 @@
193 partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context=context)]193 partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context=context)]
194 return self.pool.get('res.partner').onchange_address(cr, uid, partner_ids, use_parent_address, parent_id, context=context)194 return self.pool.get('res.partner').onchange_address(cr, uid, partner_ids, use_parent_address, parent_id, context=context)
195195
196 def read(self,cr, uid, ids, fields=None, context=None, load='_classic_read'):
197 def override_password(o):
198 if 'password' in o and ( 'id' not in o or o['id'] != uid ):
199 o['password'] = '********'
200 return o
201 result = super(res_users, self).read(cr, uid, ids, fields, context, load)
202 canwrite = self.pool.get('ir.model.access').check(cr, uid, 'res.users', 'write', False)
203 if not canwrite:
204 if isinstance(ids, (int, long)):
205 result = override_password(result)
206 else:
207 result = map(override_password, result)
208 return result
209
210
211 def _check_company(self, cr, uid, ids, context=None):196 def _check_company(self, cr, uid, ids, context=None):
212 return all(((this.company_id in this.company_ids) or not this.company_ids) for this in self.browse(cr, uid, ids, context))197 return all(((this.company_id in this.company_ids) or not this.company_ids) for this in self.browse(cr, uid, ids, context))
213198
@@ -276,8 +261,34 @@
276 return self.pool.get('res.partner').fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)261 return self.pool.get('res.partner').fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
277 return super(res_users, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)262 return super(res_users, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
278263
279 # User can write to a few of her own fields (but not her groups for example)264 # User can write on a few of his own fields (but not his groups for example)
280 SELF_WRITEABLE_FIELDS = ['password', 'signature', 'action_id', 'company_id', 'email', 'name', 'image', 'image_medium', 'image_small']265 SELF_WRITEABLE_FIELDS = ['password', 'signature', 'action_id', 'company_id', 'email', 'name', 'image', 'image_medium', 'image_small', 'lang', 'tz']
266 # User can read a few of his own fields
267 SELF_READABLE_FIELDS = ['signature', 'company_id', 'login', 'email', 'name', 'image', 'image_medium', 'image_small', 'lang', 'tz', 'groups_id', 'partner_id']
268
269 def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
270 def override_password(o):
271 if 'password' in o and ('id' not in o or o['id'] != uid):
272 o['password'] = '********'
273 return o
274
275 if (isinstance(ids, (list, tuple)) and ids == [uid]) or ids == uid:
276 for key in fields:
277 if not (key in self.SELF_READABLE_FIELDS or key.startswith('context_') or key in ['__last_update']):
278 break
279 else:
280 # safe fields only, so we read as super-user to bypass access rights
281 uid = SUPERUSER_ID
282
283 result = super(res_users, self).read(cr, uid, ids, fields=fields, context=context, load=load)
284 canwrite = self.pool.get('ir.model.access').check(cr, uid, 'res.users', 'write', False)
285 if not canwrite:
286 if isinstance(ids, (int, long)):
287 result = override_password(result)
288 else:
289 result = map(override_password, result)
290
291 return result
281292
282 def write(self, cr, uid, ids, values, context=None):293 def write(self, cr, uid, ids, values, context=None):
283 if not hasattr(ids, '__iter__'):294 if not hasattr(ids, '__iter__'):
@@ -495,14 +506,14 @@
495 """506 """
496 assert group_ext_id and '.' in group_ext_id, "External ID must be fully qualified"507 assert group_ext_id and '.' in group_ext_id, "External ID must be fully qualified"
497 module, ext_id = group_ext_id.split('.')508 module, ext_id = group_ext_id.split('.')
498 cr.execute("""SELECT 1 FROM res_groups_users_rel WHERE uid=%s AND gid IN 509 cr.execute("""SELECT 1 FROM res_groups_users_rel WHERE uid=%s AND gid IN
499 (SELECT res_id FROM ir_model_data WHERE module=%s AND name=%s)""",510 (SELECT res_id FROM ir_model_data WHERE module=%s AND name=%s)""",
500 (uid, module, ext_id))511 (uid, module, ext_id))
501 return bool(cr.fetchone())512 return bool(cr.fetchone())
502513
503514
504#515#
505# Extension of res.groups and res.users with a relation for "implied" or 516# Extension of res.groups and res.users with a relation for "implied" or
506# "inherited" groups. Once a user belongs to a group, it automatically belongs517# "inherited" groups. Once a user belongs to a group, it automatically belongs
507# to the implied groups (transitively).518# to the implied groups (transitively).
508#519#
509520
=== modified file 'openerp/osv/expression.py'
--- openerp/osv/expression.py 2012-08-02 15:25:53 +0000
+++ openerp/osv/expression.py 2012-09-24 10:54:32 +0000
@@ -126,8 +126,7 @@
126126
127from openerp.tools import flatten, reverse_enumerate127from openerp.tools import flatten, reverse_enumerate
128import fields128import fields
129import openerp.modules129import openerp
130from openerp.osv.orm import MAGIC_COLUMNS
131130
132#.apidoc title: Domain Expressions131#.apidoc title: Domain Expressions
133132
@@ -341,6 +340,46 @@
341 (select_field, from_table, select_field))340 (select_field, from_table, select_field))
342 return [r[0] for r in cr.fetchall()]341 return [r[0] for r in cr.fetchall()]
343342
343def resolve_child_of(cr, uid, left, ids, left_model, parent=None, prefix='', context=None):
344 """Returns a domain implementing the child_of operator for [(left,child_of,ids)],
345 either as a range using the parent_left/right tree lookup fields (when available),
346 or as an expanded [(left,in,child_ids)]"""
347 if left_model._parent_store and (not left_model.pool._init):
348 # TODO: Improve where joins are implemented for many with '.', replace by:
349 # doms += ['&',(prefix+'.parent_left','<',o.parent_right),(prefix+'.parent_left','>=',o.parent_left)]
350 doms = []
351 for o in left_model.browse(cr, uid, ids, context=context):
352 if doms:
353 doms.insert(0, OR_OPERATOR)
354 doms += [AND_OPERATOR, ('parent_left', '<', o.parent_right), ('parent_left', '>=', o.parent_left)]
355 if prefix:
356 return [(left, 'in', left_model.search(cr, uid, doms, context=context))]
357 return doms
358 else:
359 def recursive_children(ids, model, parent_field):
360 if not ids:
361 return []
362 ids2 = model.search(cr, uid, [(parent_field, 'in', ids)], context=context)
363 return ids + recursive_children(ids2, model, parent_field)
364 return [(left, 'in', recursive_children(ids, left_model, parent or left_model._parent_name))]
365
366
367def relationship_value_to_ids(cr, uid, value, model, context=None):
368 """ Normalize a search value for a relationship field (i.e. one or more strings,
369 one or more IDs, possibly with an extra False) to a corresponding list of IDs"""
370 names = []
371 if isinstance(value, basestring):
372 names = [value]
373 if value and isinstance(value, (tuple, list)) and isinstance(value[0], basestring):
374 names = value
375 if names:
376 return flatten([[x[0] for x in model.name_search(cr, uid, n, [], 'ilike', context=context, limit=None)] \
377 for n in names])
378 elif isinstance(value, (int, long)):
379 return [value]
380 return list(value)
381
382
344class expression(object):383class expression(object):
345 """384 """
346 parse a domain expression385 parse a domain expression
@@ -369,43 +408,6 @@
369 self.__main_table = table408 self.__main_table = table
370 self.__all_tables.add(table)409 self.__all_tables.add(table)
371410
372 def child_of_domain(left, ids, left_model, parent=None, prefix=''):
373 """Returns a domain implementing the child_of operator for [(left,child_of,ids)],
374 either as a range using the parent_left/right tree lookup fields (when available),
375 or as an expanded [(left,in,child_ids)]"""
376 if left_model._parent_store and (not left_model.pool._init):
377 # TODO: Improve where joins are implemented for many with '.', replace by:
378 # doms += ['&',(prefix+'.parent_left','<',o.parent_right),(prefix+'.parent_left','>=',o.parent_left)]
379 doms = []
380 for o in left_model.browse(cr, uid, ids, context=context):
381 if doms:
382 doms.insert(0, OR_OPERATOR)
383 doms += [AND_OPERATOR, ('parent_left', '<', o.parent_right), ('parent_left', '>=', o.parent_left)]
384 if prefix:
385 return [(left, 'in', left_model.search(cr, uid, doms, context=context))]
386 return doms
387 else:
388 def recursive_children(ids, model, parent_field):
389 if not ids:
390 return []
391 ids2 = model.search(cr, uid, [(parent_field, 'in', ids)], context=context)
392 return ids + recursive_children(ids2, model, parent_field)
393 return [(left, 'in', recursive_children(ids, left_model, parent or left_model._parent_name))]
394
395 def to_ids(value, field_obj):
396 """Normalize a single id or name, or a list of those, into a list of ids"""
397 names = []
398 if isinstance(value, basestring):
399 names = [value]
400 if value and isinstance(value, (tuple, list)) and isinstance(value[0], basestring):
401 names = value
402 if names:
403 return flatten([[x[0] for x in field_obj.name_search(cr, uid, n, [], 'ilike', context=context, limit=None)] \
404 for n in names])
405 elif isinstance(value, (int, long)):
406 return [value]
407 return list(value)
408
409 i = -1411 i = -1
410 while i + 1<len(self.__exp):412 while i + 1<len(self.__exp):
411 i += 1413 i += 1
@@ -445,14 +447,14 @@
445447
446 if not field:448 if not field:
447 if left == 'id' and operator == 'child_of':449 if left == 'id' and operator == 'child_of':
448 ids2 = to_ids(right, table)450 ids2 = relationship_value_to_ids(cr, uid, right, table, context)
449 dom = child_of_domain(left, ids2, working_table)451 dom = resolve_child_of(cr, uid, left, ids2, working_table, context=context)
450 self.__exp = self.__exp[:i] + dom + self.__exp[i+1:]452 self.__exp = self.__exp[:i] + dom + self.__exp[i+1:]
451 else:453 else:
452 # field could not be found in model columns, it's probably invalid, unless454 # field could not be found in model columns, it's probably invalid, unless
453 # it's one of the _log_access special fields455 # it's one of the _log_access special fields
454 # TODO: make these fields explicitly available in self.columns instead!456 # TODO: make these fields explicitly available in self.columns instead!
455 if field_path[0] not in MAGIC_COLUMNS:457 if field_path[0] not in openerp.osv.orm.MAGIC_COLUMNS:
456 raise ValueError("Invalid field %r in domain expression %r" % (left, exp))458 raise ValueError("Invalid field %r in domain expression %r" % (left, exp))
457 continue459 continue
458460
@@ -499,11 +501,11 @@
499 elif field._type == 'one2many':501 elif field._type == 'one2many':
500 # Applying recursivity on field(one2many)502 # Applying recursivity on field(one2many)
501 if operator == 'child_of':503 if operator == 'child_of':
502 ids2 = to_ids(right, field_obj)504 ids2 = relationship_value_to_ids(cr, uid, right, field_obj, context)
503 if field._obj != working_table._name:505 if field._obj != working_table._name:
504 dom = child_of_domain(left, ids2, field_obj, prefix=field._obj)506 dom = resolve_child_of(cr, uid, left, ids2, field_obj, prefix=field._obj, context=context)
505 else:507 else:
506 dom = child_of_domain('id', ids2, working_table, parent=left)508 dom = resolve_child_of(cr, uid, 'id', ids2, working_table, parent=left, context=context)
507 self.__exp = self.__exp[:i] + dom + self.__exp[i+1:]509 self.__exp = self.__exp[:i] + dom + self.__exp[i+1:]
508510
509 else:511 else:
@@ -535,53 +537,16 @@
535 self.__exp[i] = ('id', o2m_op, select_distinct_from_where_not_null(cr, field._fields_id, field_obj._table))537 self.__exp[i] = ('id', o2m_op, select_distinct_from_where_not_null(cr, field._fields_id, field_obj._table))
536538
537 elif field._type == 'many2many':539 elif field._type == 'many2many':
538 rel_table, rel_id1, rel_id2 = field._sql_names(working_table)540 dom = field.search(cr, uid, working_table, left, [self.__exp[i]], context=context)
539 #FIXME541 self.__exp = self.__exp[:i] + dom + self.__exp[i+1:]
540 if operator == 'child_of':542
541 def _rec_convert(ids):
542 if field_obj == table:
543 return ids
544 return select_from_where(cr, rel_id1, rel_table, rel_id2, ids, operator)
545
546 ids2 = to_ids(right, field_obj)
547 dom = child_of_domain('id', ids2, field_obj)
548 ids2 = field_obj.search(cr, uid, dom, context=context)
549 self.__exp[i] = ('id', 'in', _rec_convert(ids2))
550 else:
551 call_null_m2m = True
552 if right is not False:
553 if isinstance(right, basestring):
554 res_ids = [x[0] for x in field_obj.name_search(cr, uid, right, [], operator, context=context)]
555 if res_ids:
556 operator = 'in'
557 else:
558 if not isinstance(right, list):
559 res_ids = [right]
560 else:
561 res_ids = right
562 if not res_ids:
563 if operator in ['like','ilike','in','=']:
564 #no result found with given search criteria
565 call_null_m2m = False
566 self.__exp[i] = FALSE_LEAF
567 else:
568 operator = 'in' # operator changed because ids are directly related to main object
569 else:
570 call_null_m2m = False
571 m2m_op = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in'
572 self.__exp[i] = ('id', m2m_op, select_from_where(cr, rel_id1, rel_table, rel_id2, res_ids, operator) or [0])
573
574 if call_null_m2m:
575 m2m_op = 'in' if operator in NEGATIVE_TERM_OPERATORS else 'not in'
576 self.__exp[i] = ('id', m2m_op, select_distinct_from_where_not_null(cr, rel_id1, rel_table))
577
578 elif field._type == 'many2one':543 elif field._type == 'many2one':
579 if operator == 'child_of':544 if operator == 'child_of':
580 ids2 = to_ids(right, field_obj)545 ids2 = relationship_value_to_ids(cr, uid, right, field_obj, context)
581 if field._obj != working_table._name:546 if field._obj != working_table._name:
582 dom = child_of_domain(left, ids2, field_obj, prefix=field._obj)547 dom = resolve_child_of(cr, uid, left, ids2, field_obj, prefix=field._obj, context=context)
583 else:548 else:
584 dom = child_of_domain('id', ids2, working_table, parent=left)549 dom = resolve_child_of(cr, uid, 'id', ids2, working_table, parent=left, context=context)
585 self.__exp = self.__exp[:i] + dom + self.__exp[i+1:]550 self.__exp = self.__exp[:i] + dom + self.__exp[i+1:]
586 else:551 else:
587 def _get_expression(field_obj, cr, uid, left, right, operator, context=None):552 def _get_expression(field_obj, cr, uid, left, right, operator, context=None):
@@ -668,7 +633,7 @@
668 assert operator in (TERM_OPERATORS + ('inselect',)), \633 assert operator in (TERM_OPERATORS + ('inselect',)), \
669 "Invalid operator %r in domain term %r" % (operator, leaf)634 "Invalid operator %r in domain term %r" % (operator, leaf)
670 assert leaf in (TRUE_LEAF, FALSE_LEAF) or left in table._all_columns \635 assert leaf in (TRUE_LEAF, FALSE_LEAF) or left in table._all_columns \
671 or left in MAGIC_COLUMNS, "Invalid field %r in domain term %r" % (left, leaf)636 or left in openerp.osv.orm.MAGIC_COLUMNS, "Invalid field %r in domain term %r" % (left, leaf)
672637
673 if leaf == TRUE_LEAF:638 if leaf == TRUE_LEAF:
674 query = 'TRUE'639 query = 'TRUE'
@@ -759,7 +724,7 @@
759 query = '(unaccent(%s."%s") %s unaccent(%s))' % (table._table, left, sql_operator, format)724 query = '(unaccent(%s."%s") %s unaccent(%s))' % (table._table, left, sql_operator, format)
760 else:725 else:
761 query = '(%s."%s" %s %s)' % (table._table, left, sql_operator, format)726 query = '(%s."%s" %s %s)' % (table._table, left, sql_operator, format)
762 elif left in MAGIC_COLUMNS:727 elif left in openerp.osv.orm.MAGIC_COLUMNS:
763 query = "(%s.\"%s\" %s %%s)" % (table._table, left, sql_operator)728 query = "(%s.\"%s\" %s %%s)" % (table._table, left, sql_operator)
764 params = right729 params = right
765 else: # Must not happen730 else: # Must not happen
766731
=== modified file 'openerp/osv/fields.py'
--- openerp/osv/fields.py 2012-09-10 14:46:09 +0000
+++ openerp/osv/fields.py 2012-09-24 10:54:32 +0000
@@ -35,6 +35,7 @@
35import base6435import base64
36import datetime as DT36import datetime as DT
37import logging37import logging
38import simplejson
38import pytz39import pytz
39import re40import re
40import xmlrpclib41import xmlrpclib
@@ -42,11 +43,12 @@
4243
43import openerp44import openerp
44import openerp.tools as tools45import openerp.tools as tools
46from openerp import SUPERUSER_ID
47from openerp.osv.expression import relationship_value_to_ids, resolve_child_of, \
48 FALSE_LEAF, NEGATIVE_TERM_OPERATORS
45from openerp.tools.translate import _49from openerp.tools.translate import _
46from openerp.tools import float_round, float_repr50from openerp.tools import float_round, float_repr
47import simplejson
48from openerp.tools.html_sanitize import html_sanitize51from openerp.tools.html_sanitize import html_sanitize
49from openerp import SUPERUSER_ID
5052
51_logger = logging.getLogger(__name__)53_logger = logging.getLogger(__name__)
5254
@@ -515,6 +517,12 @@
515 for id in ids:517 for id in ids:
516 res[id] = []518 res[id] = []
517519
520 # static domains are lists, and are evaluated both here and on client-side, while string
521 # domains are dynamic and evaluated on client-side only (thus ignored here)
522 # FIXME: make this distinction explicit in API!
523 domain = self._domain(obj) if callable(self._domain) else self._domain
524 if not isinstance(domain, list): domain = []
525
518 domain = self._domain(obj) if callable(self._domain) else self._domain526 domain = self._domain(obj) if callable(self._domain) else self._domain
519 ids2 = obj.pool.get(self._obj).search(cr, user, domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)527 ids2 = obj.pool.get(self._obj).search(cr, user, domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
520 for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):528 for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
@@ -676,6 +684,19 @@
676 % values684 % values
677 return query, where_params685 return query, where_params
678686
687 def _search_query(self, model, params, operator='in'):
688 """ Extracted from ``search`` to facilitate fine-tuning of the generated
689 query. """
690 rel_table, rel_id1, rel_id2 = self._sql_names(model)
691 query = """SELECT DISTINCT %(rel_table)s.%(rel_id1)s
692 FROM %(rel_table)s """ % locals()
693 if params.get('ids'):
694 if operator not in ('>','<','>=','<='):
695 operator = 'in'
696 query += " WHERE %(rel_table)s.%(rel_id2)s %(operator)s %%(ids)s""" % locals()
697 return query, params
698
699
679 def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):700 def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):
680 if not context:701 if not context:
681 context = {}702 context = {}
@@ -694,9 +715,10 @@
694 rel, id1, id2 = self._sql_names(model)715 rel, id1, id2 = self._sql_names(model)
695716
696 # static domains are lists, and are evaluated both here and on client-side, while string717 # static domains are lists, and are evaluated both here and on client-side, while string
697 # domains supposed by dynamic and evaluated on client-side only (thus ignored here)718 # domains are dynamic and evaluated on client-side only (thus ignored here)
698 # FIXME: make this distinction explicit in API!719 # FIXME: make this distinction explicit in API!
699 domain = isinstance(self._domain, list) and self._domain or []720 domain = self._domain(obj) if callable(self._domain) else self._domain
721 if not isinstance(domain, list): domain = []
700722
701 wquery = obj._where_calc(cr, user, domain, context=context)723 wquery = obj._where_calc(cr, user, domain, context=context)
702 obj._apply_ir_rules(cr, user, wquery, 'read', context=context)724 obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
@@ -767,11 +789,58 @@
767 for act_nbr in act[2]:789 for act_nbr in act[2]:
768 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))790 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))
769791
770 #792
771 # TODO: use a name_search793 def _search(self, cr, uid, model, target_ids, operator='in'):
772 #794 """ Return IDs of current model that are related to the given target_ids
773 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):795 through the m2m relationship. If target_ids is None, returns
774 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)796 all IDs that are currently involved in a m2m relationship. """
797 if operator in ('>','<','>=','<='):
798 target_ids = target_ids and target_ids[0]
799 else:
800 target_ids = target_ids and tuple(target_ids)
801 params = {'ids': target_ids}
802 query, params = self._search_query(model, params, operator=operator)
803 cr.execute(query, params)
804 return [r[0] for r in cr.fetchall()]
805
806
807 def search(self, cr, uid, model, name, domain, context=None):
808 """ Implements domain resolution, transforming an input single-item domain
809 on this m2m column into an equivalent domain that does not involve
810 the m2m column anymore. Usually this resulting domain should only
811 contain one item filtering on the ``id`` column.
812
813 :param obj: the model to which the m2m field belongs.
814 :param name: the name of the m2m field being searched.
815 :param domain: the one-item search domain applying to this m2m field.
816 :return: a new domain (possibly with multiple items) corresponding to the
817 requested results, and not referencing the m2m field anymore.
818 """
819
820 target_model = model.pool.get(self._obj)
821 _, operator, right = domain[0] # 1-item domain
822 target_ids = relationship_value_to_ids(cr, uid, right, target_model, context)
823 m2m_op = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in'
824
825 if operator == 'child_of':
826 child_of_domain = resolve_child_of(cr, uid, 'id', target_ids, target_model, context=context)
827 target_ids = target_model.search(cr, uid, child_of_domain, context=context)
828 related_ids = self._search(cr, uid, model, target_ids)
829 return [('id', m2m_op, related_ids)]
830
831 if right is not False:
832 if target_ids:
833 return [('id', m2m_op, self._search(cr, uid, model, target_ids, operator) or [0])]
834 elif operator in ['like','ilike','in','=']:
835 return [FALSE_LEAF]
836 else:
837 operator = 'in'
838
839 # We have no matching result, so if a negative operator was used we should
840 # return all records that have at least one m2m relationship, otherwise we
841 # should return all records that don't have any.
842 m2m_op = 'in' if operator in NEGATIVE_TERM_OPERATORS else 'not in'
843 return [('id', m2m_op, self._search(cr, uid, model, None))]
775844
776 @classmethod845 @classmethod
777 def _as_display_name(cls, field, cr, uid, obj, value, context=None):846 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
778847
=== modified file 'openerp/osv/orm.py'
--- openerp/osv/orm.py 2012-09-20 14:55:19 +0000
+++ openerp/osv/orm.py 2012-09-24 10:54:32 +0000
@@ -63,7 +63,6 @@
63from openerp.tools.translate import _63from openerp.tools.translate import _
64from openerp import SUPERUSER_ID64from openerp import SUPERUSER_ID
65from query import Query65from query import Query
66from openerp import SUPERUSER_ID
6766
68_logger = logging.getLogger(__name__)67_logger = logging.getLogger(__name__)
69_schema = logging.getLogger(__name__ + '.schema')68_schema = logging.getLogger(__name__ + '.schema')
7069
=== modified file 'openerp/osv/osv.py'
--- openerp/osv/osv.py 2012-09-18 13:04:36 +0000
+++ openerp/osv/osv.py 2012-09-24 10:54:32 +0000
@@ -120,7 +120,9 @@
120 raise except_osv('Database not ready', 'Currently, this database is not fully loaded and can not be used.')120 raise except_osv('Database not ready', 'Currently, this database is not fully loaded and can not be used.')
121 return f(self, dbname, *args, **kwargs)121 return f(self, dbname, *args, **kwargs)
122 except orm.except_orm, inst:122 except orm.except_orm, inst:
123 raise except_osv(inst.name, inst.value)123 raise
124 # TDE: commented to have more valuable stack traces
125 # raise except_osv(inst.name, inst.value)
124 except except_osv:126 except except_osv:
125 raise127 raise
126 except IntegrityError, inst:128 except IntegrityError, inst: