Merge lp:~openerp-dev/openerp-web/7.0-searchview-invisibles-xmo into lp:openerp-web/7.0
- 7.0-searchview-invisibles-xmo
- Merge into 7.0
Proposed by
Xavier (Open ERP)
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 3803 | ||||
Proposed branch: | lp:~openerp-dev/openerp-web/7.0-searchview-invisibles-xmo | ||||
Merge into: | lp:openerp-web/7.0 | ||||
Diff against target: |
643 lines (+284/-83) 4 files modified
addons/web/doc/search_view.rst (+11/-0) addons/web/static/src/js/search.js (+107/-64) addons/web/static/src/xml/base.xml (+1/-10) addons/web/static/test/search.js (+165/-9) |
||||
To merge this branch: | bzr merge lp:~openerp-dev/openerp-web/7.0-searchview-invisibles-xmo | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Yannick Vaucher @ Camptocamp (community) | test | Approve | |
OpenERP Core Team | Pending | ||
Review via email: mp+148386@code.launchpad.net |
Commit message
Description of the change
Re-introduces handling of invisible fields and groups in the search view, forgotten during the reimplementation of the unified search view for 7.0
This handling is important as it's how @groups is handled (invisible fields and filters can't be removed altogether as e.g. defaults still need to work correctly).
To post a comment you must log in.
Revision history for this message
Yannick Vaucher @ Camptocamp (yvaucher-c2c) wrote : | # |
review:
Approve
(test)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'addons/web/doc/search_view.rst' |
2 | --- addons/web/doc/search_view.rst 2012-11-14 23:00:42 +0000 |
3 | +++ addons/web/doc/search_view.rst 2013-02-14 08:53:20 +0000 |
4 | @@ -107,6 +107,12 @@ |
5 | items, it *should* prefix those with a section title using its own |
6 | name. This has no technical consequence but is clearer for users. |
7 | |
8 | +.. note:: |
9 | + |
10 | + If a field is :js:func:`invisible |
11 | + <openerp.web.search.Input.visible>`, its completion function will |
12 | + *not* be called. |
13 | + |
14 | Providing drawer/supplementary UI |
15 | +++++++++++++++++++++++++++++++++ |
16 | |
17 | @@ -145,6 +151,11 @@ |
18 | dynamically collects, lays out and renders filters? => |
19 | exercises drawer thingies |
20 | |
21 | +.. note:: |
22 | + |
23 | + An :js:func:`invisible <openerp.web.search.Input.visible>` input |
24 | + will not be inserted into the drawer. |
25 | + |
26 | Converting from facet objects |
27 | +++++++++++++++++++++++++++++ |
28 | |
29 | |
30 | === modified file 'addons/web/static/src/js/search.js' |
31 | --- addons/web/static/src/js/search.js 2013-01-31 13:51:25 +0000 |
32 | +++ addons/web/static/src/js/search.js 2013-02-14 08:53:20 +0000 |
33 | @@ -359,7 +359,7 @@ |
34 | this.has_defaults = !_.isEmpty(this.defaults); |
35 | |
36 | this.inputs = []; |
37 | - this.controls = {}; |
38 | + this.controls = []; |
39 | |
40 | this.headless = this.options.hidden && !this.has_defaults; |
41 | |
42 | @@ -499,6 +499,7 @@ |
43 | */ |
44 | complete_global_search: function (req, resp) { |
45 | $.when.apply(null, _(this.inputs).chain() |
46 | + .filter(function (input) { return input.visible(); }) |
47 | .invoke('complete', req.term) |
48 | .value()).then(function () { |
49 | resp(_(_(arguments).compact()).flatten(true)); |
50 | @@ -587,18 +588,18 @@ |
51 | * |
52 | * @param {Array} items a list of nodes to convert to widgets |
53 | * @param {Object} fields a mapping of field names to (ORM) field attributes |
54 | - * @param {String} [group_name] name of the group to put the new controls in |
55 | + * @param {Object} [group] group to put the new controls in |
56 | */ |
57 | - make_widgets: function (items, fields, group_name) { |
58 | - group_name = group_name || null; |
59 | - if (!(group_name in this.controls)) { |
60 | - this.controls[group_name] = []; |
61 | + make_widgets: function (items, fields, group) { |
62 | + if (!group) { |
63 | + group = new instance.web.search.Group( |
64 | + this, 'q', {attrs: {string: _t("Filters")}}); |
65 | } |
66 | - var self = this, group = this.controls[group_name]; |
67 | + var self = this; |
68 | var filters = []; |
69 | _.each(items, function (item) { |
70 | if (filters.length && item.tag !== 'filter') { |
71 | - group.push(new instance.web.search.FilterGroup(filters, this)); |
72 | + group.push(new instance.web.search.FilterGroup(filters, group)); |
73 | filters = []; |
74 | } |
75 | |
76 | @@ -606,15 +607,18 @@ |
77 | case 'separator': case 'newline': |
78 | break; |
79 | case 'filter': |
80 | - filters.push(new instance.web.search.Filter(item, this)); |
81 | + filters.push(new instance.web.search.Filter(item, group)); |
82 | break; |
83 | case 'group': |
84 | - self.make_widgets(item.children, fields, item.attrs.string); |
85 | + self.make_widgets(item.children, fields, |
86 | + new instance.web.search.Group(group, 'w', item)); |
87 | break; |
88 | case 'field': |
89 | - group.push(this.make_field(item, fields[item['attrs'].name])); |
90 | + var field = this.make_field( |
91 | + item, fields[item['attrs'].name], group); |
92 | + group.push(field); |
93 | // filters |
94 | - self.make_widgets(item.children, fields, group_name); |
95 | + self.make_widgets(item.children, fields, group); |
96 | break; |
97 | } |
98 | }, this); |
99 | @@ -629,12 +633,13 @@ |
100 | * |
101 | * @param {Object} item fields_view_get node for the field |
102 | * @param {Object} field fields_get result for the field |
103 | + * @param {Object} [parent] |
104 | * @returns instance.web.search.Field |
105 | */ |
106 | - make_field: function (item, field) { |
107 | + make_field: function (item, field, parent) { |
108 | var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]); |
109 | if(obj) { |
110 | - return new (obj) (item, field, this); |
111 | + return new (obj) (item, field, parent || this); |
112 | } else { |
113 | console.group('Unknown field type ' + field.type); |
114 | console.error('View node', item); |
115 | @@ -869,13 +874,18 @@ |
116 | * @constructs instance.web.search.Widget |
117 | * @extends instance.web.Widget |
118 | * |
119 | - * @param view the ancestor view of this widget |
120 | + * @param parent parent of this widget |
121 | */ |
122 | - init: function (view) { |
123 | - this._super(view); |
124 | - this.view = view; |
125 | + init: function (parent) { |
126 | + this._super(parent); |
127 | + var ancestor = parent; |
128 | + do { |
129 | + this.view = ancestor; |
130 | + } while (!(ancestor instanceof instance.web.SearchView) |
131 | + && (ancestor = (ancestor.getParent && ancestor.getParent()))); |
132 | } |
133 | }); |
134 | + |
135 | instance.web.search.add_expand_listener = function($root) { |
136 | $root.find('a.searchview_group_string').click(function (e) { |
137 | $root.toggleClass('folded expanded'); |
138 | @@ -884,13 +894,24 @@ |
139 | }); |
140 | }; |
141 | instance.web.search.Group = instance.web.search.Widget.extend({ |
142 | - template: 'SearchView.group', |
143 | - init: function (view_section, view, fields) { |
144 | - this._super(view); |
145 | - this.attrs = view_section.attrs; |
146 | - this.lines = view.make_widgets( |
147 | - view_section.children, fields); |
148 | - } |
149 | + init: function (parent, icon, node) { |
150 | + this._super(parent); |
151 | + var attrs = node.attrs; |
152 | + this.modifiers = attrs.modifiers = |
153 | + attrs.modifiers ? JSON.parse(attrs.modifiers) : {}; |
154 | + this.attrs = attrs; |
155 | + this.icon = icon; |
156 | + this.name = attrs.string; |
157 | + this.children = []; |
158 | + |
159 | + this.view.controls.push(this); |
160 | + }, |
161 | + push: function (input) { |
162 | + this.children.push(input); |
163 | + }, |
164 | + visible: function () { |
165 | + return !this.modifiers.invisible; |
166 | + }, |
167 | }); |
168 | |
169 | instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instance.web.search.Input# */{ |
170 | @@ -899,12 +920,12 @@ |
171 | * @constructs instance.web.search.Input |
172 | * @extends instance.web.search.Widget |
173 | * |
174 | - * @param view |
175 | + * @param parent |
176 | */ |
177 | - init: function (view) { |
178 | - this._super(view); |
179 | + init: function (parent) { |
180 | + this._super(parent); |
181 | + this.load_attrs({}); |
182 | this.view.inputs.push(this); |
183 | - this.style = undefined; |
184 | }, |
185 | /** |
186 | * Fetch auto-completion values for the widget. |
187 | @@ -952,15 +973,30 @@ |
188 | "get_domain not implemented for widget " + this.attrs.type); |
189 | }, |
190 | load_attrs: function (attrs) { |
191 | - if (attrs.modifiers) { |
192 | - attrs.modifiers = JSON.parse(attrs.modifiers); |
193 | - attrs.invisible = attrs.modifiers.invisible || false; |
194 | - if (attrs.invisible) { |
195 | - this.style = 'display: none;' |
196 | + attrs.modifiers = attrs.modifiers ? JSON.parse(attrs.modifiers) : {}; |
197 | + this.attrs = attrs; |
198 | + }, |
199 | + /** |
200 | + * Returns whether the input is "visible". The default behavior is to |
201 | + * query the ``modifiers.invisible`` flag on the input's description or |
202 | + * view node. |
203 | + * |
204 | + * @returns {Boolean} |
205 | + */ |
206 | + visible: function () { |
207 | + if (this.attrs.modifiers.invisible) { |
208 | + return false; |
209 | + } |
210 | + var parent = this; |
211 | + while ((parent = parent.getParent()) && |
212 | + ( (parent instanceof instance.web.search.Group) |
213 | + || (parent instanceof instance.web.search.Input))) { |
214 | + if (!parent.visible()) { |
215 | + return false; |
216 | } |
217 | } |
218 | - this.attrs = attrs; |
219 | - } |
220 | + return true; |
221 | + }, |
222 | }); |
223 | instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends instance.web.search.FilterGroup# */{ |
224 | template: 'SearchView.filters', |
225 | @@ -974,17 +1010,17 @@ |
226 | * @extends instance.web.search.Input |
227 | * |
228 | * @param {Array<instance.web.search.Filter>} filters elements of the group |
229 | - * @param {instance.web.SearchView} view view in which the filters are contained |
230 | + * @param {instance.web.SearchView} parent parent in which the filters are contained |
231 | */ |
232 | - init: function (filters, view) { |
233 | + init: function (filters, parent) { |
234 | // If all filters are group_by and we're not initializing a GroupbyGroup, |
235 | // create a GroupbyGroup instead of the current FilterGroup |
236 | if (!(this instanceof instance.web.search.GroupbyGroup) && |
237 | _(filters).all(function (f) { |
238 | return f.attrs.context && f.attrs.context.group_by; })) { |
239 | - return new instance.web.search.GroupbyGroup(filters, view); |
240 | + return new instance.web.search.GroupbyGroup(filters, parent); |
241 | } |
242 | - this._super(view); |
243 | + this._super(parent); |
244 | this.filters = filters; |
245 | this.view.query.on('add remove change reset', this.proxy('search_change')); |
246 | }, |
247 | @@ -1103,6 +1139,7 @@ |
248 | var self = this; |
249 | item = item.toLowerCase(); |
250 | var facet_values = _(this.filters).chain() |
251 | + .filter(function (filter) { return filter.visible(); }) |
252 | .filter(function (filter) { |
253 | var at = { |
254 | string: filter.attrs.string || '', |
255 | @@ -1129,8 +1166,8 @@ |
256 | instance.web.search.GroupbyGroup = instance.web.search.FilterGroup.extend({ |
257 | icon: 'w', |
258 | completion_label: _lt("Group by: %s"), |
259 | - init: function (filters, view) { |
260 | - this._super(filters, view); |
261 | + init: function (filters, parent) { |
262 | + this._super(filters, parent); |
263 | // Not flanders: facet unicity is handled through the |
264 | // (category, field) pair of facet attributes. This is all well and |
265 | // good for regular filter groups where a group matches a facet, but for |
266 | @@ -1138,8 +1175,8 @@ |
267 | // view which proxies to the first GroupbyGroup, so it can be used |
268 | // for every GroupbyGroup and still provides the various methods needed |
269 | // by the search view. Use weirdo name to avoid risks of conflicts |
270 | - if (!this.getParent()._s_groupby) { |
271 | - this.getParent()._s_groupby = { |
272 | + if (!this.view._s_groupby) { |
273 | + this.view._s_groupby = { |
274 | help: "See GroupbyGroup#init", |
275 | get_context: this.proxy('get_context'), |
276 | get_domain: this.proxy('get_domain'), |
277 | @@ -1148,7 +1185,7 @@ |
278 | } |
279 | }, |
280 | match_facet: function (facet) { |
281 | - return facet.get('field') === this.getParent()._s_groupby; |
282 | + return facet.get('field') === this.view._s_groupby; |
283 | }, |
284 | make_facet: function (values) { |
285 | return { |
286 | @@ -1173,10 +1210,10 @@ |
287 | * @extends instance.web.search.Input |
288 | * |
289 | * @param node |
290 | - * @param view |
291 | + * @param parent |
292 | */ |
293 | - init: function (node, view) { |
294 | - this._super(view); |
295 | + init: function (node, parent) { |
296 | + this._super(parent); |
297 | this.load_attrs(node.attrs); |
298 | }, |
299 | facet_for: function () { return $.when(null); }, |
300 | @@ -1192,10 +1229,10 @@ |
301 | * |
302 | * @param view_section |
303 | * @param field |
304 | - * @param view |
305 | + * @param parent |
306 | */ |
307 | - init: function (view_section, field, view) { |
308 | - this._super(view); |
309 | + init: function (view_section, field, parent) { |
310 | + this._super(parent); |
311 | this.load_attrs(_.extend({}, field, view_section.attrs)); |
312 | }, |
313 | facet_for: function (value) { |
314 | @@ -1235,7 +1272,7 @@ |
315 | * |
316 | * @param {String} name the field's name |
317 | * @param {String} operator the field's operator (either attribute-specified or default operator for the field |
318 | - * @param {Number|String} value parsed value for the field |
319 | + * @param {Number|String} facet parsed value for the field |
320 | * @returns {Array<Array>} domain to include in the resulting search |
321 | */ |
322 | make_domain: function (name, operator, facet) { |
323 | @@ -1467,8 +1504,8 @@ |
324 | }); |
325 | instance.web.search.ManyToOneField = instance.web.search.CharField.extend({ |
326 | default_operator: {}, |
327 | - init: function (view_section, field, view) { |
328 | - this._super(view_section, field, view); |
329 | + init: function (view_section, field, parent) { |
330 | + this._super(view_section, field, parent); |
331 | this.model = new instance.web.Model(this.attrs.relation); |
332 | }, |
333 | complete: function (needle) { |
334 | @@ -1703,22 +1740,28 @@ |
335 | var running_count = 0; |
336 | // get total filters count |
337 | var is_group = function (i) { return i instanceof instance.web.search.FilterGroup; }; |
338 | - var filters_count = _(this.view.controls).chain() |
339 | + var visible_filters = _(this.view.controls).chain().reject(function (group) { |
340 | + return _(_(group.children).filter(is_group)).isEmpty() |
341 | + || group.modifiers.invisible; |
342 | + }); |
343 | + var filters_count = visible_filters |
344 | + .pluck('children') |
345 | .flatten() |
346 | .filter(is_group) |
347 | .map(function (i) { return i.filters.length; }) |
348 | .sum() |
349 | .value(); |
350 | |
351 | - var col1 = [], col2 = _(this.view.controls).map(function (inputs, group) { |
352 | - var filters = _(inputs).filter(is_group); |
353 | - return { |
354 | - name: group === 'null' ? "<span class='oe_i'>q</span> " + _t("Filters") : "<span class='oe_i'>w</span> " + group, |
355 | - filters: filters, |
356 | - length: _(filters).chain().map(function (i) { |
357 | - return i.filters.length; }).sum().value() |
358 | - }; |
359 | - }); |
360 | + var col1 = [], col2 = visible_filters.map(function (group) { |
361 | + var filters = _(group.children).filter(is_group); |
362 | + return { |
363 | + name: _.str.sprintf("<span class='oe_i'>%s</span> %s", |
364 | + group.icon, group.name), |
365 | + filters: filters, |
366 | + length: _(filters).chain().map(function (i) { |
367 | + return i.filters.length; }).sum().value() |
368 | + }; |
369 | + }).value(); |
370 | |
371 | while (col2.length) { |
372 | // col1 + group should be smaller than col2 + group |
373 | |
374 | === modified file 'addons/web/static/src/xml/base.xml' |
375 | --- addons/web/static/src/xml/base.xml 2013-02-13 15:21:58 +0000 |
376 | +++ addons/web/static/src/xml/base.xml 2013-02-14 08:53:20 +0000 |
377 | @@ -1496,7 +1496,7 @@ |
378 | <t t-esc="attrs.string"/> |
379 | </button> |
380 | <ul t-name="SearchView.filters"> |
381 | - <li t-foreach="widget.filters" t-as="filter" |
382 | + <li t-foreach="widget.filters" t-as="filter" t-if="filter.visible()" |
383 | t-att-title="filter.attrs.string ? filter.attrs.help : undefined"> |
384 | <t t-esc="filter.attrs.string or filter.attrs.help or filter.attrs.name or 'Ω'"/> |
385 | </li> |
386 | @@ -1584,15 +1584,6 @@ |
387 | </div> |
388 | </div> |
389 | </t> |
390 | -<t t-name="SearchView.group"> |
391 | - <t t-call="SearchView.util.expand"> |
392 | - <t t-set="expand" t-value="attrs.expand"/> |
393 | - <t t-set="label" t-value="attrs.string"/> |
394 | - <t t-set="content"> |
395 | - <t t-call="SearchView.render_lines"/> |
396 | - </t> |
397 | - </t> |
398 | -</t> |
399 | <div t-name="SearchView.Filters" class="oe_searchview_filters oe_searchview_section"> |
400 | |
401 | </div> |
402 | |
403 | === modified file 'addons/web/static/test/search.js' |
404 | --- addons/web/static/test/search.js 2013-01-31 11:26:17 +0000 |
405 | +++ addons/web/static/test/search.js 2013-02-14 08:53:20 +0000 |
406 | @@ -1,4 +1,4 @@ |
407 | -openerp.testing.section('query', { |
408 | +openerp.testing.section('search.query', { |
409 | dependencies: ['web.search'] |
410 | }, function (test) { |
411 | test('Adding a facet to the query creates a facet and a value', function (instance) { |
412 | @@ -180,7 +180,7 @@ |
413 | }); |
414 | return view; |
415 | }; |
416 | -openerp.testing.section('defaults', { |
417 | +openerp.testing.section('search.defaults', { |
418 | dependencies: ['web.search'], |
419 | rpc: 'mock', |
420 | templates: true, |
421 | @@ -331,7 +331,7 @@ |
422 | }); |
423 | }); |
424 | }); |
425 | -openerp.testing.section('completions', { |
426 | +openerp.testing.section('search.completions', { |
427 | dependencies: ['web.search'], |
428 | rpc: 'mock', |
429 | templates: true |
430 | @@ -564,7 +564,7 @@ |
431 | }); |
432 | }); |
433 | }); |
434 | -openerp.testing.section('search-serialization', { |
435 | +openerp.testing.section('search.serialization', { |
436 | dependencies: ['web.search'], |
437 | rpc: 'mock', |
438 | templates: true |
439 | @@ -871,7 +871,7 @@ |
440 | return $.when(t1, t2); |
441 | }); |
442 | }); |
443 | -openerp.testing.section('removal', { |
444 | +openerp.testing.section('search.removal', { |
445 | dependencies: ['web.search'], |
446 | rpc: 'mock', |
447 | templates: true |
448 | @@ -894,7 +894,7 @@ |
449 | }); |
450 | }); |
451 | }); |
452 | -openerp.testing.section('drawer', { |
453 | +openerp.testing.section('search.drawer', { |
454 | dependencies: ['web.search'], |
455 | rpc: 'mock', |
456 | templates: true |
457 | @@ -910,7 +910,7 @@ |
458 | }); |
459 | }); |
460 | }); |
461 | -openerp.testing.section('filters', { |
462 | +openerp.testing.section('search.filters', { |
463 | dependencies: ['web.search'], |
464 | rpc: 'mock', |
465 | templates: true, |
466 | @@ -995,7 +995,7 @@ |
467 | }); |
468 | }); |
469 | }); |
470 | -openerp.testing.section('saved_filters', { |
471 | +openerp.testing.section('search.filters.saved', { |
472 | dependencies: ['web.search'], |
473 | rpc: 'mock', |
474 | templates: true |
475 | @@ -1077,7 +1077,7 @@ |
476 | }); |
477 | }); |
478 | }); |
479 | -openerp.testing.section('advanced', { |
480 | +openerp.testing.section('search.advanced', { |
481 | dependencies: ['web.search'], |
482 | rpc: 'mock', |
483 | templates: true |
484 | @@ -1158,3 +1158,159 @@ |
485 | }); |
486 | // TODO: UI tests? |
487 | }); |
488 | +openerp.testing.section('search.invisible', { |
489 | + dependencies: ['web.search'], |
490 | + rpc: 'mock', |
491 | + templates: true, |
492 | +}, function (test) { |
493 | + var registerTestField = function (instance, methods) { |
494 | + instance.web.search.fields.add('test', 'instance.testing.TestWidget'); |
495 | + instance.testing = { |
496 | + TestWidget: instance.web.search.Field.extend(methods), |
497 | + }; |
498 | + }; |
499 | + var makeView = function (instance, mock, fields, arch, defaults) { |
500 | + mock('ir.filters:get_filters', function () { return []; }); |
501 | + mock('test.model:fields_get', function () { return fields; }); |
502 | + mock('test.model:fields_view_get', function () { |
503 | + return { type: 'search', fields: fields, arch: arch }; |
504 | + }); |
505 | + var ds = new instance.web.DataSet(null, 'test.model'); |
506 | + return new instance.web.SearchView(null, ds, false, defaults); |
507 | + }; |
508 | + // Invisible fields should not auto-complete |
509 | + test('invisible-field-no-autocomplete', {asserts: 1}, function (instance, $fix, mock) { |
510 | + registerTestField(instance, { |
511 | + complete: function () { |
512 | + return $.when([{label: this.attrs.string}]); |
513 | + }, |
514 | + }); |
515 | + var view = makeView(instance, mock, { |
516 | + field0: {type: 'test', string: 'Field 0'}, |
517 | + field1: {type: 'test', string: 'Field 1'}, |
518 | + }, ['<search>', |
519 | + '<field name="field0"/>', |
520 | + '<field name="field1" modifiers="{"invisible": true}"/>', |
521 | + '</search>'].join()); |
522 | + return view.appendTo($fix) |
523 | + .then(function () { |
524 | + var done = $.Deferred(); |
525 | + view.complete_global_search({term: 'test'}, function (comps) { |
526 | + done.resolve(comps); |
527 | + }); |
528 | + return done; |
529 | + }).then(function (completions) { |
530 | + deepEqual(completions, [{label: 'Field 0'}], |
531 | + "should only complete the visible field"); |
532 | + }); |
533 | + }); |
534 | + // Invisible filters should not appear in the drawer |
535 | + test('invisible-filter-no-drawer', {asserts: 4}, function (instance, $fix, mock) { |
536 | + var view = makeView(instance, mock, {}, [ |
537 | + '<search>', |
538 | + '<filter string="filter 0"/>', |
539 | + '<filter string="filter 1" modifiers="{"invisible": true}"/>', |
540 | + '</search>'].join()); |
541 | + return view.appendTo($fix) |
542 | + .then(function () { |
543 | + var $fs = $fix.find('.oe_searchview_filters ul'); |
544 | + strictEqual($fs.children().length, |
545 | + 1, |
546 | + "should only display one filter"); |
547 | + strictEqual(_.str.trim($fs.children().text()), |
548 | + "filter 0", |
549 | + "should only display filter 0"); |
550 | + var done = $.Deferred(); |
551 | + view.complete_global_search({term: 'filter'}, function (comps) { |
552 | + done.resolve(); |
553 | + strictEqual(comps.length, 1, "should only complete visible filter"); |
554 | + strictEqual(comps[0].label, "Filter on: filter 0", |
555 | + "should complete filter 0"); |
556 | + }); |
557 | + return done; |
558 | + }); |
559 | + }); |
560 | + // Invisible filter groups should not appear in the drawer |
561 | + // Group invisibility should be inherited by children |
562 | + test('group-invisibility', {asserts: 6}, function (instance, $fix, mock) { |
563 | + registerTestField(instance, { |
564 | + complete: function () { |
565 | + return $.when([{label: this.attrs.string}]); |
566 | + }, |
567 | + }); |
568 | + var view = makeView(instance, mock, { |
569 | + field0: {type: 'test', string: 'Field 0'}, |
570 | + field1: {type: 'test', string: 'Field 1'}, |
571 | + }, [ |
572 | + '<search>', |
573 | + '<group string="Visibles">', |
574 | + '<field name="field0"/>', |
575 | + '<filter string="Filter 0"/>', |
576 | + '</group>', |
577 | + '<group string="Invisibles" modifiers="{"invisible": true}">', |
578 | + '<field name="field1"/>', |
579 | + '<filter string="Filter 1"/>', |
580 | + '</group>', |
581 | + '</search>' |
582 | + ].join('')); |
583 | + return view.appendTo($fix) |
584 | + .then(function () { |
585 | + strictEqual($fix.find('.oe_searchview_filters h3').length, |
586 | + 1, |
587 | + "should only display one group"); |
588 | + strictEqual($fix.find('.oe_searchview_filters h3').text(), |
589 | + 'w Visibles', |
590 | + "should only display the Visibles group (and its icon char)"); |
591 | + |
592 | + var $fs = $fix.find('.oe_searchview_filters ul'); |
593 | + strictEqual($fs.children().length, 1, |
594 | + "should only have one filter in the drawer"); |
595 | + strictEqual(_.str.trim($fs.text()), "Filter 0", |
596 | + "should have filter 0 as sole filter"); |
597 | + |
598 | + var done = $.Deferred(); |
599 | + view.complete_global_search({term: 'filter'}, function (compls) { |
600 | + done.resolve(); |
601 | + strictEqual(compls.length, 2, |
602 | + "should have 2 completions"); |
603 | + deepEqual(_.pluck(compls, 'label'), |
604 | + ['Field 0', 'Filter on: Filter 0'], |
605 | + "should complete on field 0 and filter 0"); |
606 | + }); |
607 | + return done; |
608 | + }); |
609 | + }); |
610 | + // Default on invisible fields should still work, for fields and filters both |
611 | + test('invisible-defaults', {asserts: 1}, function (instance, $fix, mock) { |
612 | + var view = makeView(instance, mock, { |
613 | + field: {type: 'char', string: "Field"}, |
614 | + field2: {type: 'char', string: "Field 2"}, |
615 | + }, [ |
616 | + '<search>', |
617 | + '<field name="field2"/>', |
618 | + '<filter name="filter2" string="Filter"', |
619 | + ' domain="[[\'qwa\', \'=\', 42]]"/>', |
620 | + '<group string="Invisibles" modifiers="{"invisible": true}">', |
621 | + '<field name="field"/>', |
622 | + '<filter name="filter" string="Filter"', |
623 | + ' domain="[[\'whee\', \'=\', \'42\']]"/>', |
624 | + '</group>', |
625 | + '</search>' |
626 | + ].join(''), {field: "foo", filter: true}); |
627 | + |
628 | + return view.appendTo($fix) |
629 | + .then(function () { |
630 | + deepEqual(view.build_search_data(), { |
631 | + errors: [], |
632 | + groupbys: [], |
633 | + contexts: [], |
634 | + domains: [ |
635 | + // Generated from field |
636 | + [['field', 'ilike', 'foo']], |
637 | + // generated from filter |
638 | + "[['whee', '=', '42']]" |
639 | + ], |
640 | + }, "should yield invisible fields selected by defaults"); |
641 | + }); |
642 | + }); |
643 | +}); |
It works
Test done: "base.group_ no_one"
- Install crm.
- Add a user1 with group Sales / User: Own Leads Only
- Add a user2 with groups Sales / User: Own Leads Only and Technical Features
- Edit the view search to set Customers filter under Technical Features group.
groups=
Expected:
user1 doesn't see the filter in partner search view OK
user2 see the filter in partner search view OK