Merge lp:~openerp-dev/openobject-server/trunk-message-sub-type-apa into lp:openobject-server
- trunk-message-sub-type-apa
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
OpenERP Core Team | Pending | ||
Review via email: mp+125686@code.launchpad.net |
Commit message
Description of the change
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: |