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 | 195 | ] | 195 | ] |
6 | 196 | 196 | ||
7 | 197 | ids = proxy.search(cr, uid, args, context=context) | 197 | ids = proxy.search(cr, uid, args, context=context) |
9 | 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] |
10 | 199 | for rule in proxy.browse(cr, uid, ids, context): | 199 | for rule in proxy.browse(cr, uid, ids, context): |
11 | 200 | if eval(rule.expression, {'context': context, 'user': user}): | 200 | if eval(rule.expression, {'context': context, 'user': user}): |
12 | 201 | return rule.company_dest_id.id | 201 | return rule.company_dest_id.id |
14 | 202 | return user.company_id.id | 202 | return user['company_id'][0] |
15 | 203 | 203 | ||
16 | 204 | @tools.ormcache() | 204 | @tools.ormcache() |
17 | 205 | def _get_company_children(self, cr, uid=None, company=None): | 205 | def _get_company_children(self, cr, uid=None, company=None): |
18 | 206 | 206 | ||
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 | 22 | import math | 22 | import math |
24 | 23 | import openerp | 23 | import openerp |
25 | 24 | from osv import osv, fields | 24 | from osv import osv, fields |
26 | 25 | from openerp import SUPERUSER_ID | ||
27 | 25 | import re | 26 | import re |
28 | 26 | import tools | 27 | import tools |
29 | 27 | from tools.translate import _ | 28 | from tools.translate import _ |
30 | @@ -33,7 +34,7 @@ | |||
31 | 33 | class format_address(object): | 34 | class format_address(object): |
32 | 34 | def fields_view_get_address(self, cr, uid, arch, context={}): | 35 | def fields_view_get_address(self, cr, uid, arch, context={}): |
33 | 35 | user_obj = self.pool.get('res.users') | 36 | user_obj = self.pool.get('res.users') |
35 | 36 | fmt = user_obj.browse(cr, uid, uid,context).company_id.country_id | 37 | fmt = user_obj.browse(cr, SUPERUSER_ID, uid, context).company_id.country_id |
36 | 37 | fmt = fmt and fmt.address_format | 38 | fmt = fmt and fmt.address_format |
37 | 38 | layouts = { | 39 | layouts = { |
38 | 39 | '%(city)s %(state_code)s\n%(zip)s': """ | 40 | '%(city)s %(state_code)s\n%(zip)s': """ |
39 | @@ -392,7 +393,7 @@ | |||
40 | 392 | - otherwise: default, everything is set as the name """ | 393 | - otherwise: default, everything is set as the name """ |
41 | 393 | match = re.search(r'([^\s,<@]+@[^>\s,]+)', text) | 394 | match = re.search(r'([^\s,<@]+@[^>\s,]+)', text) |
42 | 394 | if match: | 395 | if match: |
44 | 395 | email = match.group(1) | 396 | email = match.group(1) |
45 | 396 | name = text[:text.index(email)].replace('"','').replace('<','').strip() | 397 | name = text[:text.index(email)].replace('"','').replace('<','').strip() |
46 | 397 | else: | 398 | else: |
47 | 398 | name, email = text, '' | 399 | name, email = text, '' |
48 | @@ -440,7 +441,7 @@ | |||
49 | 440 | def find_or_create(self, cr, uid, email, context=None): | 441 | def find_or_create(self, cr, uid, email, context=None): |
50 | 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` |
51 | 442 | to create one | 443 | to create one |
53 | 443 | 444 | ||
54 | 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, |
55 | 445 | e.g. ``"Raoul Grosbedon <r.g@grosbedon.fr>"``""" | 446 | e.g. ``"Raoul Grosbedon <r.g@grosbedon.fr>"``""" |
56 | 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' |
57 | 447 | 448 | ||
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 | 52 | else: | 52 | else: |
63 | 53 | res[g.id] = g.name | 53 | res[g.id] = g.name |
64 | 54 | return res | 54 | return res |
66 | 55 | 55 | ||
67 | 56 | def _search_group(self, cr, uid, obj, name, args, context=None): | 56 | def _search_group(self, cr, uid, obj, name, args, context=None): |
68 | 57 | operand = args[0][2] | 57 | operand = args[0][2] |
69 | 58 | operator = args[0][1] | 58 | operator = args[0][1] |
70 | @@ -64,7 +64,7 @@ | |||
71 | 64 | group_name = values[1] | 64 | group_name = values[1] |
72 | 65 | where = ['|',('category_id.name', operator, application_name)] + where | 65 | where = ['|',('category_id.name', operator, application_name)] + where |
73 | 66 | return where | 66 | return where |
75 | 67 | 67 | ||
76 | 68 | _columns = { | 68 | _columns = { |
77 | 69 | 'name': fields.char('Name', size=64, required=True, translate=True), | 69 | 'name': fields.char('Name', size=64, required=True, translate=True), |
78 | 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'), |
79 | @@ -138,7 +138,7 @@ | |||
80 | 138 | 138 | ||
81 | 139 | def _get_password(self, cr, uid, ids, arg, karg, context=None): | 139 | def _get_password(self, cr, uid, ids, arg, karg, context=None): |
82 | 140 | return dict.fromkeys(ids, '') | 140 | return dict.fromkeys(ids, '') |
84 | 141 | 141 | ||
85 | 142 | _columns = { | 142 | _columns = { |
86 | 143 | 'id': fields.integer('ID'), | 143 | 'id': fields.integer('ID'), |
87 | 144 | 'login_date': fields.date('Latest connection', select=1), | 144 | 'login_date': fields.date('Latest connection', select=1), |
88 | @@ -193,21 +193,6 @@ | |||
89 | 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)] |
90 | 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) |
91 | 195 | 195 | ||
92 | 196 | def read(self,cr, uid, ids, fields=None, context=None, load='_classic_read'): | ||
93 | 197 | def override_password(o): | ||
94 | 198 | if 'password' in o and ( 'id' not in o or o['id'] != uid ): | ||
95 | 199 | o['password'] = '********' | ||
96 | 200 | return o | ||
97 | 201 | result = super(res_users, self).read(cr, uid, ids, fields, context, load) | ||
98 | 202 | canwrite = self.pool.get('ir.model.access').check(cr, uid, 'res.users', 'write', False) | ||
99 | 203 | if not canwrite: | ||
100 | 204 | if isinstance(ids, (int, long)): | ||
101 | 205 | result = override_password(result) | ||
102 | 206 | else: | ||
103 | 207 | result = map(override_password, result) | ||
104 | 208 | return result | ||
105 | 209 | |||
106 | 210 | |||
107 | 211 | def _check_company(self, cr, uid, ids, context=None): | 196 | def _check_company(self, cr, uid, ids, context=None): |
108 | 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)) |
109 | 213 | 198 | ||
110 | @@ -276,8 +261,34 @@ | |||
111 | 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) |
112 | 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) |
113 | 278 | 263 | ||
116 | 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) |
117 | 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'] |
118 | 266 | # User can read a few of his own fields | ||
119 | 267 | SELF_READABLE_FIELDS = ['signature', 'company_id', 'login', 'email', 'name', 'image', 'image_medium', 'image_small', 'lang', 'tz', 'groups_id', 'partner_id'] | ||
120 | 268 | |||
121 | 269 | def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'): | ||
122 | 270 | def override_password(o): | ||
123 | 271 | if 'password' in o and ('id' not in o or o['id'] != uid): | ||
124 | 272 | o['password'] = '********' | ||
125 | 273 | return o | ||
126 | 274 | |||
127 | 275 | if (isinstance(ids, (list, tuple)) and ids == [uid]) or ids == uid: | ||
128 | 276 | for key in fields: | ||
129 | 277 | if not (key in self.SELF_READABLE_FIELDS or key.startswith('context_') or key in ['__last_update']): | ||
130 | 278 | break | ||
131 | 279 | else: | ||
132 | 280 | # safe fields only, so we read as super-user to bypass access rights | ||
133 | 281 | uid = SUPERUSER_ID | ||
134 | 282 | |||
135 | 283 | result = super(res_users, self).read(cr, uid, ids, fields=fields, context=context, load=load) | ||
136 | 284 | canwrite = self.pool.get('ir.model.access').check(cr, uid, 'res.users', 'write', False) | ||
137 | 285 | if not canwrite: | ||
138 | 286 | if isinstance(ids, (int, long)): | ||
139 | 287 | result = override_password(result) | ||
140 | 288 | else: | ||
141 | 289 | result = map(override_password, result) | ||
142 | 290 | |||
143 | 291 | return result | ||
144 | 281 | 292 | ||
145 | 282 | def write(self, cr, uid, ids, values, context=None): | 293 | def write(self, cr, uid, ids, values, context=None): |
146 | 283 | if not hasattr(ids, '__iter__'): | 294 | if not hasattr(ids, '__iter__'): |
147 | @@ -495,14 +506,14 @@ | |||
148 | 495 | """ | 506 | """ |
149 | 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" |
150 | 497 | module, ext_id = group_ext_id.split('.') | 508 | module, ext_id = group_ext_id.split('.') |
152 | 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 |
153 | 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)""", |
154 | 500 | (uid, module, ext_id)) | 511 | (uid, module, ext_id)) |
155 | 501 | return bool(cr.fetchone()) | 512 | return bool(cr.fetchone()) |
156 | 502 | 513 | ||
157 | 503 | 514 | ||
158 | 504 | # | 515 | # |
160 | 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 |
161 | 506 | # "inherited" groups. Once a user belongs to a group, it automatically belongs | 517 | # "inherited" groups. Once a user belongs to a group, it automatically belongs |
162 | 507 | # to the implied groups (transitively). | 518 | # to the implied groups (transitively). |
163 | 508 | # | 519 | # |
164 | 509 | 520 | ||
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 | 126 | 126 | ||
170 | 127 | from openerp.tools import flatten, reverse_enumerate | 127 | from openerp.tools import flatten, reverse_enumerate |
171 | 128 | import fields | 128 | import fields |
174 | 129 | import openerp.modules | 129 | import openerp |
173 | 130 | from openerp.osv.orm import MAGIC_COLUMNS | ||
175 | 131 | 130 | ||
176 | 132 | #.apidoc title: Domain Expressions | 131 | #.apidoc title: Domain Expressions |
177 | 133 | 132 | ||
178 | @@ -341,6 +340,46 @@ | |||
179 | 341 | (select_field, from_table, select_field)) | 340 | (select_field, from_table, select_field)) |
180 | 342 | return [r[0] for r in cr.fetchall()] | 341 | return [r[0] for r in cr.fetchall()] |
181 | 343 | 342 | ||
182 | 343 | def resolve_child_of(cr, uid, left, ids, left_model, parent=None, prefix='', context=None): | ||
183 | 344 | """Returns a domain implementing the child_of operator for [(left,child_of,ids)], | ||
184 | 345 | either as a range using the parent_left/right tree lookup fields (when available), | ||
185 | 346 | or as an expanded [(left,in,child_ids)]""" | ||
186 | 347 | if left_model._parent_store and (not left_model.pool._init): | ||
187 | 348 | # TODO: Improve where joins are implemented for many with '.', replace by: | ||
188 | 349 | # doms += ['&',(prefix+'.parent_left','<',o.parent_right),(prefix+'.parent_left','>=',o.parent_left)] | ||
189 | 350 | doms = [] | ||
190 | 351 | for o in left_model.browse(cr, uid, ids, context=context): | ||
191 | 352 | if doms: | ||
192 | 353 | doms.insert(0, OR_OPERATOR) | ||
193 | 354 | doms += [AND_OPERATOR, ('parent_left', '<', o.parent_right), ('parent_left', '>=', o.parent_left)] | ||
194 | 355 | if prefix: | ||
195 | 356 | return [(left, 'in', left_model.search(cr, uid, doms, context=context))] | ||
196 | 357 | return doms | ||
197 | 358 | else: | ||
198 | 359 | def recursive_children(ids, model, parent_field): | ||
199 | 360 | if not ids: | ||
200 | 361 | return [] | ||
201 | 362 | ids2 = model.search(cr, uid, [(parent_field, 'in', ids)], context=context) | ||
202 | 363 | return ids + recursive_children(ids2, model, parent_field) | ||
203 | 364 | return [(left, 'in', recursive_children(ids, left_model, parent or left_model._parent_name))] | ||
204 | 365 | |||
205 | 366 | |||
206 | 367 | def relationship_value_to_ids(cr, uid, value, model, context=None): | ||
207 | 368 | """ Normalize a search value for a relationship field (i.e. one or more strings, | ||
208 | 369 | one or more IDs, possibly with an extra False) to a corresponding list of IDs""" | ||
209 | 370 | names = [] | ||
210 | 371 | if isinstance(value, basestring): | ||
211 | 372 | names = [value] | ||
212 | 373 | if value and isinstance(value, (tuple, list)) and isinstance(value[0], basestring): | ||
213 | 374 | names = value | ||
214 | 375 | if names: | ||
215 | 376 | return flatten([[x[0] for x in model.name_search(cr, uid, n, [], 'ilike', context=context, limit=None)] \ | ||
216 | 377 | for n in names]) | ||
217 | 378 | elif isinstance(value, (int, long)): | ||
218 | 379 | return [value] | ||
219 | 380 | return list(value) | ||
220 | 381 | |||
221 | 382 | |||
222 | 344 | class expression(object): | 383 | class expression(object): |
223 | 345 | """ | 384 | """ |
224 | 346 | parse a domain expression | 385 | parse a domain expression |
225 | @@ -369,43 +408,6 @@ | |||
226 | 369 | self.__main_table = table | 408 | self.__main_table = table |
227 | 370 | self.__all_tables.add(table) | 409 | self.__all_tables.add(table) |
228 | 371 | 410 | ||
229 | 372 | def child_of_domain(left, ids, left_model, parent=None, prefix=''): | ||
230 | 373 | """Returns a domain implementing the child_of operator for [(left,child_of,ids)], | ||
231 | 374 | either as a range using the parent_left/right tree lookup fields (when available), | ||
232 | 375 | or as an expanded [(left,in,child_ids)]""" | ||
233 | 376 | if left_model._parent_store and (not left_model.pool._init): | ||
234 | 377 | # TODO: Improve where joins are implemented for many with '.', replace by: | ||
235 | 378 | # doms += ['&',(prefix+'.parent_left','<',o.parent_right),(prefix+'.parent_left','>=',o.parent_left)] | ||
236 | 379 | doms = [] | ||
237 | 380 | for o in left_model.browse(cr, uid, ids, context=context): | ||
238 | 381 | if doms: | ||
239 | 382 | doms.insert(0, OR_OPERATOR) | ||
240 | 383 | doms += [AND_OPERATOR, ('parent_left', '<', o.parent_right), ('parent_left', '>=', o.parent_left)] | ||
241 | 384 | if prefix: | ||
242 | 385 | return [(left, 'in', left_model.search(cr, uid, doms, context=context))] | ||
243 | 386 | return doms | ||
244 | 387 | else: | ||
245 | 388 | def recursive_children(ids, model, parent_field): | ||
246 | 389 | if not ids: | ||
247 | 390 | return [] | ||
248 | 391 | ids2 = model.search(cr, uid, [(parent_field, 'in', ids)], context=context) | ||
249 | 392 | return ids + recursive_children(ids2, model, parent_field) | ||
250 | 393 | return [(left, 'in', recursive_children(ids, left_model, parent or left_model._parent_name))] | ||
251 | 394 | |||
252 | 395 | def to_ids(value, field_obj): | ||
253 | 396 | """Normalize a single id or name, or a list of those, into a list of ids""" | ||
254 | 397 | names = [] | ||
255 | 398 | if isinstance(value, basestring): | ||
256 | 399 | names = [value] | ||
257 | 400 | if value and isinstance(value, (tuple, list)) and isinstance(value[0], basestring): | ||
258 | 401 | names = value | ||
259 | 402 | if names: | ||
260 | 403 | return flatten([[x[0] for x in field_obj.name_search(cr, uid, n, [], 'ilike', context=context, limit=None)] \ | ||
261 | 404 | for n in names]) | ||
262 | 405 | elif isinstance(value, (int, long)): | ||
263 | 406 | return [value] | ||
264 | 407 | return list(value) | ||
265 | 408 | |||
266 | 409 | i = -1 | 411 | i = -1 |
267 | 410 | while i + 1<len(self.__exp): | 412 | while i + 1<len(self.__exp): |
268 | 411 | i += 1 | 413 | i += 1 |
269 | @@ -445,14 +447,14 @@ | |||
270 | 445 | 447 | ||
271 | 446 | if not field: | 448 | if not field: |
272 | 447 | if left == 'id' and operator == 'child_of': | 449 | if left == 'id' and operator == 'child_of': |
275 | 448 | ids2 = to_ids(right, table) | 450 | ids2 = relationship_value_to_ids(cr, uid, right, table, context) |
276 | 449 | dom = child_of_domain(left, ids2, working_table) | 451 | dom = resolve_child_of(cr, uid, left, ids2, working_table, context=context) |
277 | 450 | self.__exp = self.__exp[:i] + dom + self.__exp[i+1:] | 452 | self.__exp = self.__exp[:i] + dom + self.__exp[i+1:] |
278 | 451 | else: | 453 | else: |
279 | 452 | # field could not be found in model columns, it's probably invalid, unless | 454 | # field could not be found in model columns, it's probably invalid, unless |
280 | 453 | # it's one of the _log_access special fields | 455 | # it's one of the _log_access special fields |
281 | 454 | # TODO: make these fields explicitly available in self.columns instead! | 456 | # TODO: make these fields explicitly available in self.columns instead! |
283 | 455 | if field_path[0] not in MAGIC_COLUMNS: | 457 | if field_path[0] not in openerp.osv.orm.MAGIC_COLUMNS: |
284 | 456 | raise ValueError("Invalid field %r in domain expression %r" % (left, exp)) | 458 | raise ValueError("Invalid field %r in domain expression %r" % (left, exp)) |
285 | 457 | continue | 459 | continue |
286 | 458 | 460 | ||
287 | @@ -499,11 +501,11 @@ | |||
288 | 499 | elif field._type == 'one2many': | 501 | elif field._type == 'one2many': |
289 | 500 | # Applying recursivity on field(one2many) | 502 | # Applying recursivity on field(one2many) |
290 | 501 | if operator == 'child_of': | 503 | if operator == 'child_of': |
292 | 502 | ids2 = to_ids(right, field_obj) | 504 | ids2 = relationship_value_to_ids(cr, uid, right, field_obj, context) |
293 | 503 | if field._obj != working_table._name: | 505 | if field._obj != working_table._name: |
295 | 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) |
296 | 505 | else: | 507 | else: |
298 | 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) |
299 | 507 | self.__exp = self.__exp[:i] + dom + self.__exp[i+1:] | 509 | self.__exp = self.__exp[:i] + dom + self.__exp[i+1:] |
300 | 508 | 510 | ||
301 | 509 | else: | 511 | else: |
302 | @@ -535,53 +537,16 @@ | |||
303 | 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)) |
304 | 536 | 538 | ||
305 | 537 | elif field._type == 'many2many': | 539 | elif field._type == 'many2many': |
346 | 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) |
347 | 539 | #FIXME | 541 | self.__exp = self.__exp[:i] + dom + self.__exp[i+1:] |
348 | 540 | if operator == 'child_of': | 542 | |
309 | 541 | def _rec_convert(ids): | ||
310 | 542 | if field_obj == table: | ||
311 | 543 | return ids | ||
312 | 544 | return select_from_where(cr, rel_id1, rel_table, rel_id2, ids, operator) | ||
313 | 545 | |||
314 | 546 | ids2 = to_ids(right, field_obj) | ||
315 | 547 | dom = child_of_domain('id', ids2, field_obj) | ||
316 | 548 | ids2 = field_obj.search(cr, uid, dom, context=context) | ||
317 | 549 | self.__exp[i] = ('id', 'in', _rec_convert(ids2)) | ||
318 | 550 | else: | ||
319 | 551 | call_null_m2m = True | ||
320 | 552 | if right is not False: | ||
321 | 553 | if isinstance(right, basestring): | ||
322 | 554 | res_ids = [x[0] for x in field_obj.name_search(cr, uid, right, [], operator, context=context)] | ||
323 | 555 | if res_ids: | ||
324 | 556 | operator = 'in' | ||
325 | 557 | else: | ||
326 | 558 | if not isinstance(right, list): | ||
327 | 559 | res_ids = [right] | ||
328 | 560 | else: | ||
329 | 561 | res_ids = right | ||
330 | 562 | if not res_ids: | ||
331 | 563 | if operator in ['like','ilike','in','=']: | ||
332 | 564 | #no result found with given search criteria | ||
333 | 565 | call_null_m2m = False | ||
334 | 566 | self.__exp[i] = FALSE_LEAF | ||
335 | 567 | else: | ||
336 | 568 | operator = 'in' # operator changed because ids are directly related to main object | ||
337 | 569 | else: | ||
338 | 570 | call_null_m2m = False | ||
339 | 571 | m2m_op = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in' | ||
340 | 572 | self.__exp[i] = ('id', m2m_op, select_from_where(cr, rel_id1, rel_table, rel_id2, res_ids, operator) or [0]) | ||
341 | 573 | |||
342 | 574 | if call_null_m2m: | ||
343 | 575 | m2m_op = 'in' if operator in NEGATIVE_TERM_OPERATORS else 'not in' | ||
344 | 576 | self.__exp[i] = ('id', m2m_op, select_distinct_from_where_not_null(cr, rel_id1, rel_table)) | ||
345 | 577 | |||
349 | 578 | elif field._type == 'many2one': | 543 | elif field._type == 'many2one': |
350 | 579 | if operator == 'child_of': | 544 | if operator == 'child_of': |
352 | 580 | ids2 = to_ids(right, field_obj) | 545 | ids2 = relationship_value_to_ids(cr, uid, right, field_obj, context) |
353 | 581 | if field._obj != working_table._name: | 546 | if field._obj != working_table._name: |
355 | 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) |
356 | 583 | else: | 548 | else: |
358 | 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) |
359 | 585 | self.__exp = self.__exp[:i] + dom + self.__exp[i+1:] | 550 | self.__exp = self.__exp[:i] + dom + self.__exp[i+1:] |
360 | 586 | else: | 551 | else: |
361 | 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): |
362 | @@ -668,7 +633,7 @@ | |||
363 | 668 | assert operator in (TERM_OPERATORS + ('inselect',)), \ | 633 | assert operator in (TERM_OPERATORS + ('inselect',)), \ |
364 | 669 | "Invalid operator %r in domain term %r" % (operator, leaf) | 634 | "Invalid operator %r in domain term %r" % (operator, leaf) |
365 | 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 \ |
367 | 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) |
368 | 672 | 637 | ||
369 | 673 | if leaf == TRUE_LEAF: | 638 | if leaf == TRUE_LEAF: |
370 | 674 | query = 'TRUE' | 639 | query = 'TRUE' |
371 | @@ -759,7 +724,7 @@ | |||
372 | 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) |
373 | 760 | else: | 725 | else: |
374 | 761 | query = '(%s."%s" %s %s)' % (table._table, left, sql_operator, format) | 726 | query = '(%s."%s" %s %s)' % (table._table, left, sql_operator, format) |
376 | 762 | elif left in MAGIC_COLUMNS: | 727 | elif left in openerp.osv.orm.MAGIC_COLUMNS: |
377 | 763 | query = "(%s.\"%s\" %s %%s)" % (table._table, left, sql_operator) | 728 | query = "(%s.\"%s\" %s %%s)" % (table._table, left, sql_operator) |
378 | 764 | params = right | 729 | params = right |
379 | 765 | else: # Must not happen | 730 | else: # Must not happen |
380 | 766 | 731 | ||
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 | 35 | import base64 | 35 | import base64 |
386 | 36 | import datetime as DT | 36 | import datetime as DT |
387 | 37 | import logging | 37 | import logging |
388 | 38 | import simplejson | ||
389 | 38 | import pytz | 39 | import pytz |
390 | 39 | import re | 40 | import re |
391 | 40 | import xmlrpclib | 41 | import xmlrpclib |
392 | @@ -42,11 +43,12 @@ | |||
393 | 42 | 43 | ||
394 | 43 | import openerp | 44 | import openerp |
395 | 44 | import openerp.tools as tools | 45 | import openerp.tools as tools |
396 | 46 | from openerp import SUPERUSER_ID | ||
397 | 47 | from openerp.osv.expression import relationship_value_to_ids, resolve_child_of, \ | ||
398 | 48 | FALSE_LEAF, NEGATIVE_TERM_OPERATORS | ||
399 | 45 | from openerp.tools.translate import _ | 49 | from openerp.tools.translate import _ |
400 | 46 | from openerp.tools import float_round, float_repr | 50 | from openerp.tools import float_round, float_repr |
401 | 47 | import simplejson | ||
402 | 48 | from openerp.tools.html_sanitize import html_sanitize | 51 | from openerp.tools.html_sanitize import html_sanitize |
403 | 49 | from openerp import SUPERUSER_ID | ||
404 | 50 | 52 | ||
405 | 51 | _logger = logging.getLogger(__name__) | 53 | _logger = logging.getLogger(__name__) |
406 | 52 | 54 | ||
407 | @@ -515,6 +517,12 @@ | |||
408 | 515 | for id in ids: | 517 | for id in ids: |
409 | 516 | res[id] = [] | 518 | res[id] = [] |
410 | 517 | 519 | ||
411 | 520 | # static domains are lists, and are evaluated both here and on client-side, while string | ||
412 | 521 | # domains are dynamic and evaluated on client-side only (thus ignored here) | ||
413 | 522 | # FIXME: make this distinction explicit in API! | ||
414 | 523 | domain = self._domain(obj) if callable(self._domain) else self._domain | ||
415 | 524 | if not isinstance(domain, list): domain = [] | ||
416 | 525 | |||
417 | 518 | domain = self._domain(obj) if callable(self._domain) else self._domain | 526 | domain = self._domain(obj) if callable(self._domain) else self._domain |
418 | 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) |
419 | 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'): |
420 | @@ -676,6 +684,19 @@ | |||
421 | 676 | % values | 684 | % values |
422 | 677 | return query, where_params | 685 | return query, where_params |
423 | 678 | 686 | ||
424 | 687 | def _search_query(self, model, params, operator='in'): | ||
425 | 688 | """ Extracted from ``search`` to facilitate fine-tuning of the generated | ||
426 | 689 | query. """ | ||
427 | 690 | rel_table, rel_id1, rel_id2 = self._sql_names(model) | ||
428 | 691 | query = """SELECT DISTINCT %(rel_table)s.%(rel_id1)s | ||
429 | 692 | FROM %(rel_table)s """ % locals() | ||
430 | 693 | if params.get('ids'): | ||
431 | 694 | if operator not in ('>','<','>=','<='): | ||
432 | 695 | operator = 'in' | ||
433 | 696 | query += " WHERE %(rel_table)s.%(rel_id2)s %(operator)s %%(ids)s""" % locals() | ||
434 | 697 | return query, params | ||
435 | 698 | |||
436 | 699 | |||
437 | 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): |
438 | 680 | if not context: | 701 | if not context: |
439 | 681 | context = {} | 702 | context = {} |
440 | @@ -694,9 +715,10 @@ | |||
441 | 694 | rel, id1, id2 = self._sql_names(model) | 715 | rel, id1, id2 = self._sql_names(model) |
442 | 695 | 716 | ||
443 | 696 | # static domains are lists, and are evaluated both here and on client-side, while string | 717 | # static domains are lists, and are evaluated both here and on client-side, while string |
445 | 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) |
446 | 698 | # FIXME: make this distinction explicit in API! | 719 | # FIXME: make this distinction explicit in API! |
448 | 699 | domain = isinstance(self._domain, list) and self._domain or [] | 720 | domain = self._domain(obj) if callable(self._domain) else self._domain |
449 | 721 | if not isinstance(domain, list): domain = [] | ||
450 | 700 | 722 | ||
451 | 701 | wquery = obj._where_calc(cr, user, domain, context=context) | 723 | wquery = obj._where_calc(cr, user, domain, context=context) |
452 | 702 | obj._apply_ir_rules(cr, user, wquery, 'read', context=context) | 724 | obj._apply_ir_rules(cr, user, wquery, 'read', context=context) |
453 | @@ -767,11 +789,58 @@ | |||
454 | 767 | for act_nbr in act[2]: | 789 | for act_nbr in act[2]: |
455 | 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)) |
456 | 769 | 791 | ||
462 | 770 | # | 792 | |
463 | 771 | # TODO: use a name_search | 793 | def _search(self, cr, uid, model, target_ids, operator='in'): |
464 | 772 | # | 794 | """ Return IDs of current model that are related to the given target_ids |
465 | 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 |
466 | 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. """ |
467 | 797 | if operator in ('>','<','>=','<='): | ||
468 | 798 | target_ids = target_ids and target_ids[0] | ||
469 | 799 | else: | ||
470 | 800 | target_ids = target_ids and tuple(target_ids) | ||
471 | 801 | params = {'ids': target_ids} | ||
472 | 802 | query, params = self._search_query(model, params, operator=operator) | ||
473 | 803 | cr.execute(query, params) | ||
474 | 804 | return [r[0] for r in cr.fetchall()] | ||
475 | 805 | |||
476 | 806 | |||
477 | 807 | def search(self, cr, uid, model, name, domain, context=None): | ||
478 | 808 | """ Implements domain resolution, transforming an input single-item domain | ||
479 | 809 | on this m2m column into an equivalent domain that does not involve | ||
480 | 810 | the m2m column anymore. Usually this resulting domain should only | ||
481 | 811 | contain one item filtering on the ``id`` column. | ||
482 | 812 | |||
483 | 813 | :param obj: the model to which the m2m field belongs. | ||
484 | 814 | :param name: the name of the m2m field being searched. | ||
485 | 815 | :param domain: the one-item search domain applying to this m2m field. | ||
486 | 816 | :return: a new domain (possibly with multiple items) corresponding to the | ||
487 | 817 | requested results, and not referencing the m2m field anymore. | ||
488 | 818 | """ | ||
489 | 819 | |||
490 | 820 | target_model = model.pool.get(self._obj) | ||
491 | 821 | _, operator, right = domain[0] # 1-item domain | ||
492 | 822 | target_ids = relationship_value_to_ids(cr, uid, right, target_model, context) | ||
493 | 823 | m2m_op = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in' | ||
494 | 824 | |||
495 | 825 | if operator == 'child_of': | ||
496 | 826 | child_of_domain = resolve_child_of(cr, uid, 'id', target_ids, target_model, context=context) | ||
497 | 827 | target_ids = target_model.search(cr, uid, child_of_domain, context=context) | ||
498 | 828 | related_ids = self._search(cr, uid, model, target_ids) | ||
499 | 829 | return [('id', m2m_op, related_ids)] | ||
500 | 830 | |||
501 | 831 | if right is not False: | ||
502 | 832 | if target_ids: | ||
503 | 833 | return [('id', m2m_op, self._search(cr, uid, model, target_ids, operator) or [0])] | ||
504 | 834 | elif operator in ['like','ilike','in','=']: | ||
505 | 835 | return [FALSE_LEAF] | ||
506 | 836 | else: | ||
507 | 837 | operator = 'in' | ||
508 | 838 | |||
509 | 839 | # We have no matching result, so if a negative operator was used we should | ||
510 | 840 | # return all records that have at least one m2m relationship, otherwise we | ||
511 | 841 | # should return all records that don't have any. | ||
512 | 842 | m2m_op = 'in' if operator in NEGATIVE_TERM_OPERATORS else 'not in' | ||
513 | 843 | return [('id', m2m_op, self._search(cr, uid, model, None))] | ||
514 | 775 | 844 | ||
515 | 776 | @classmethod | 845 | @classmethod |
516 | 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): |
517 | 778 | 847 | ||
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 | 63 | from openerp.tools.translate import _ | 63 | from openerp.tools.translate import _ |
523 | 64 | from openerp import SUPERUSER_ID | 64 | from openerp import SUPERUSER_ID |
524 | 65 | from query import Query | 65 | from query import Query |
525 | 66 | from openerp import SUPERUSER_ID | ||
526 | 67 | 66 | ||
527 | 68 | _logger = logging.getLogger(__name__) | 67 | _logger = logging.getLogger(__name__) |
528 | 69 | _schema = logging.getLogger(__name__ + '.schema') | 68 | _schema = logging.getLogger(__name__ + '.schema') |
529 | 70 | 69 | ||
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 | 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.') |
535 | 121 | return f(self, dbname, *args, **kwargs) | 121 | return f(self, dbname, *args, **kwargs) |
536 | 122 | except orm.except_orm, inst: | 122 | except orm.except_orm, inst: |
538 | 123 | raise except_osv(inst.name, inst.value) | 123 | raise |
539 | 124 | # TDE: commented to have more valuable stack traces | ||
540 | 125 | # raise except_osv(inst.name, inst.value) | ||
541 | 124 | except except_osv: | 126 | except except_osv: |
542 | 125 | raise | 127 | raise |
543 | 126 | except IntegrityError, inst: | 128 | except IntegrityError, inst: |