You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

348 lines
11 KiB

[FIX] web_advanced_search: Ignore field domain If we honor the domain value, the user may not be getting all available records when selecting from an x2many dropdown, or even worse, if the domain references another field in the view, the user may be getting an error when opening the field dropdown, like this: Error: NameError: name 'company_id' is not defined http://localhost/web/static/lib/py.js/lib/py.js:370 Rastreo de error: PY_ensurepy@http://localhost/web/static/lib/py.js/lib/py.js:370:19 py.evaluate@http://localhost/web/static/lib/py.js/lib/py.js:1340:20 py.evaluate@http://localhost/web/static/lib/py.js/lib/py.js:1397:35 py.evaluate@http://localhost/web/static/lib/py.js/lib/py.js:1409:34 py.eval@http://localhost/web/static/lib/py.js/lib/py.js:1453:16 eval_domains/<@http://localhost/web/static/src/js/core/pyeval.js:886:39 _.forEach@http://localhost/web/static/lib/underscore/underscore.js:145:9 _.mixin/</_.prototype[name]@http://localhost/web/static/lib/underscore/underscore.js:1484:29 eval_domains@http://localhost/web/static/src/js/core/pyeval.js:877:16 pyeval@http://localhost/web/static/src/js/core/pyeval.js:988:16 stringToArray@http://localhost/web/static/src/js/core/domain.js:243:16 _getDomain@http://localhost/web/static/src/js/views/basic/basic_model.js:3004:24 _search@http://localhost/web_m2x_options/static/src/js/form.js:139:26 source@http://localhost/web/static/src/js/fields/relational_fields.js:198:17 _search@http://localhost/web/static/lib/jquery.ui/jquery-ui.js:6823:3 $.widget/</proxiedPrototype[prop]</<@http://localhost/web/static/lib/jquery.ui/jquery-ui.js:415:19 search@http://localhost/web/static/lib/jquery.ui/jquery-ui.js:6815:10 $.widget/</proxiedPrototype[prop]</<@http://localhost/web/static/lib/jquery.ui/jquery-ui.js:415:19 $.widget.bridge/$.fn[name]/<@http://localhost/web/static/lib/jquery.ui/jquery-ui.js:508:19 each@http://localhost/web/static/lib/jquery/jquery.js:383:49 each@http://localhost/web/static/lib/jquery/jquery.js:136:24 $.widget.bridge/$.fn[name]@http://localhost/web/static/lib/jquery.ui/jquery-ui.js:494:4 _onInputClick@http://localhost/web/static/src/js/fields/relational_fields.js:565:13 proxy/<@http://localhost/web/static/src/js/core/mixins.js:279:20 dispatch@http://localhost/web/static/lib/jquery/jquery.js:4640:50 add/elemData.handle@http://localhost/web/static/lib/jquery/jquery.js:4309:41
5 years ago
  1. /* Copyright 2015 Therp BV <http://therp.nl>
  2. * Copyright 2017-2018 Jairo Llopis <jairo.llopis@tecnativa.com>
  3. * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
  4. odoo.define("web_advanced_search", function (require) {
  5. "use strict";
  6. var core = require("web.core");
  7. var Domain = require("web.Domain");
  8. var DomainSelectorDialog = require("web.DomainSelectorDialog");
  9. var field_registry = require("web.field_registry");
  10. var FieldManagerMixin = require("web.FieldManagerMixin");
  11. var FiltersMenu = require("web.FiltersMenu");
  12. var human_domain = require("web_advanced_search.human_domain");
  13. var SearchView = require("web.SearchView");
  14. var Widget = require("web.Widget");
  15. var Char = core.search_filters_registry.get("char");
  16. SearchView.include({
  17. custom_events: _.extend({}, SearchView.prototype.custom_events, {
  18. "get_dataset": "_on_get_dataset",
  19. }),
  20. /**
  21. * Add or update a `dataset` attribute in event target
  22. *
  23. * The search view dataset includes things such as the model, which
  24. * is required to make some parts of search views smarter.
  25. *
  26. * @param {OdooEvent} event The target will get the dataset.
  27. */
  28. _on_get_dataset: function (event) {
  29. event.target.dataset = this.dataset;
  30. event.stopPropagation();
  31. },
  32. });
  33. /**
  34. * An almost dummy search proposition, to use with domain widget
  35. */
  36. var AdvancedSearchProposition = Widget.extend({
  37. /**
  38. * @override
  39. */
  40. init: function (parent, model, domain) {
  41. this._super(parent);
  42. this.model = model;
  43. this.domain = new Domain(domain);
  44. },
  45. /**
  46. * Produce a filter descriptor for advanced searches.
  47. *
  48. * @returns {Object} In the format expected by `web.FiltersMenu`.
  49. */
  50. get_filter: function () {
  51. var domain_array = this.domain.toArray();
  52. return {
  53. attrs: {
  54. domain: domain_array,
  55. // TODO Remove when merged
  56. // https://github.com/odoo/odoo/pull/25922
  57. string: human_domain.getHumanDomain(
  58. this,
  59. this.model,
  60. domain_array
  61. ),
  62. },
  63. children: [],
  64. tag: "filter",
  65. };
  66. },
  67. });
  68. // Add advanced search features
  69. FiltersMenu.include({
  70. custom_events: _.extend({}, FiltersMenu.prototype.custom_events, {
  71. "domain_selected": "advanced_search_commit",
  72. }),
  73. events: _.extend({}, FiltersMenu.prototype.events, {
  74. "click .o_add_advanced_search": "advanced_search_open",
  75. }),
  76. /**
  77. * @override
  78. */
  79. init: function () {
  80. this._super.apply(this, arguments);
  81. this.trigger_up("get_dataset");
  82. },
  83. /**
  84. * Open advanced search dialog
  85. *
  86. * @returns {$.Deferred} The opening dialog itself.
  87. */
  88. advanced_search_open: function () {
  89. var domain_selector_dialog = new DomainSelectorDialog(
  90. this,
  91. this.dataset.model,
  92. "[]",
  93. {
  94. debugMode: core.debug,
  95. readonly: false,
  96. }
  97. );
  98. domain_selector_dialog.opened(function () {
  99. // Add 1st domain node by default
  100. domain_selector_dialog.domainSelector._onAddFirstButtonClick();
  101. });
  102. return domain_selector_dialog.open();
  103. },
  104. /**
  105. * Apply advanced search on dialog save
  106. *
  107. * @param {OdooEvent} event A `domain_selected` event from the dialog.
  108. */
  109. advanced_search_commit: function (event) {
  110. _.invoke(this.propositions, "destroy");
  111. var proposition = new AdvancedSearchProposition(
  112. this,
  113. this.dataset.model,
  114. event.data.domain
  115. );
  116. this.propositions = [proposition];
  117. this._commitSearch();
  118. },
  119. });
  120. /**
  121. * A search field for relational fields.
  122. *
  123. * It implements and extends the `FieldManagerMixin`, and acts as if it
  124. * were a reduced dummy controller. Some actions "mock" the underlying
  125. * model, since sometimes we use a char widget to fill related fields
  126. * (which is not supported by that widget), and fields need an underlying
  127. * model implementation, which can only hold fake data, given a search view
  128. * has no data on it by definition.
  129. */
  130. var Relational = Char.extend(FieldManagerMixin, {
  131. tagName: "div",
  132. className: "x2x_container",
  133. attributes: {},
  134. /**
  135. * @override
  136. */
  137. init: function () {
  138. this._super.apply(this, arguments);
  139. // To make widgets work, we need a model and an empty record
  140. FieldManagerMixin.init.call(this);
  141. this.trigger_up("get_dataset");
  142. // Make equal and not equal appear 1st and 2nd
  143. this.operators = _.sortBy(
  144. this.operators,
  145. function (op) {
  146. switch (op.value) {
  147. case "=":
  148. return -2;
  149. case "!=":
  150. return -1;
  151. default:
  152. return 0;
  153. }
  154. });
  155. // Create dummy record with only the field the user is searching
  156. var params = {
  157. fieldNames: [this.field.name],
  158. modelName: this.dataset.model,
  159. context: this.dataset.context,
  160. fields: {},
  161. type: "record",
  162. viewType: "default",
  163. fieldsInfo: {
  164. default: {},
  165. },
  166. };
  167. // See https://stackoverflow.com/a/11508530/1468388
  168. // to know how to include this in the previous step in ES6
  169. params.fields[this.field.name] = _.omit(
  170. this.field,
  171. // User needs all records, to actually produce a new domain
  172. "domain",
  173. // Onchanges make no sense in this context, there's no record
  174. "onChange"
  175. );
  176. if (this.field.type.endsWith("2many")) {
  177. // X2many fields behave like m2o in the search context
  178. params.fields[this.field.name].type = "many2one";
  179. }
  180. params.fieldsInfo.default[this.field.name] = {};
  181. // Emulate `model.load()`, without RPC-calling `default_get()`
  182. this.datapoint_id = this.model._makeDataPoint(params).id;
  183. this.model.applyDefaultValues(
  184. this.datapoint_id,
  185. {},
  186. params.fieldNames
  187. );
  188. // To generate a new fake ID
  189. this._fake_id = -1;
  190. },
  191. /**
  192. * @override
  193. */
  194. start: function () {
  195. var result = this._super.apply(this, arguments);
  196. // Render the initial widget
  197. result.done($.proxy(this, "show_inputs", $("<input value='='/>")));
  198. return result;
  199. },
  200. /**
  201. * @override
  202. */
  203. destroy: function () {
  204. if (this._field_widget) {
  205. this._field_widget.destroy();
  206. }
  207. this.model.destroy();
  208. delete this.record;
  209. return this._super.apply(this, arguments);
  210. },
  211. /**
  212. * Get record object for current datapoint.
  213. *
  214. * @returns {Object}
  215. */
  216. _get_record: function () {
  217. return this.model.get(this.datapoint_id);
  218. },
  219. /**
  220. * @override
  221. */
  222. show_inputs: function ($operator) {
  223. // Get widget class to be used
  224. switch ($operator.val()) {
  225. case "=":
  226. case "!=":
  227. this._field_widget_name = "many2one";
  228. break;
  229. default:
  230. this._field_widget_name = "char";
  231. }
  232. var _Widget = field_registry.get(this._field_widget_name);
  233. // Destroy previous widget, if any
  234. if (this._field_widget) {
  235. this._field_widget.destroy();
  236. delete this._field_widget;
  237. }
  238. // Create new widget
  239. var options = {
  240. mode: "edit",
  241. attrs: {
  242. options: {
  243. no_create_edit: true,
  244. no_create: true,
  245. no_open: true,
  246. no_quick_create: true,
  247. },
  248. },
  249. };
  250. this._field_widget = new _Widget(
  251. this,
  252. this.field.name,
  253. this._get_record(),
  254. options
  255. );
  256. this._field_widget.appendTo(this.$el);
  257. return this._super.apply(this, arguments);
  258. },
  259. /**
  260. * @override
  261. */
  262. _applyChanges: function (dataPointID, changes, event) {
  263. // Make char updates look like valid x2one updates
  264. if (_.isNaN(changes[this.field.name].id)) {
  265. changes[this.field.name] = {
  266. id: this._fake_id--,
  267. display_name: event.target.lastSetValue,
  268. };
  269. }
  270. return FieldManagerMixin._applyChanges.apply(this, arguments);
  271. },
  272. /**
  273. * @override
  274. */
  275. _confirmChange: function (id, fields, event) {
  276. this.datapoint_id = id;
  277. return this._field_widget.reset(this._get_record(), event);
  278. },
  279. /**
  280. * @override
  281. */
  282. get_value: function () {
  283. try {
  284. switch (this._field_widget_name) {
  285. case "many2one":
  286. return this._field_widget.value.res_id;
  287. default:
  288. return this._field_widget.value.data.display_name;
  289. }
  290. } catch (error) {
  291. if (error.name === "TypeError") {
  292. return false;
  293. }
  294. }
  295. },
  296. /**
  297. * Extract the field's value in a human-readable format.
  298. *
  299. * @override
  300. */
  301. toString: function () {
  302. try {
  303. switch (this._field_widget_name) {
  304. case "many2one":
  305. return this._field_widget.value.data.display_name;
  306. }
  307. return this._super.apply(this, arguments);
  308. } catch (error) {
  309. if (error.name === "TypeError") {
  310. return "";
  311. }
  312. }
  313. },
  314. });
  315. // Register search filter widgets
  316. core.search_filters_registry
  317. .add("many2many", Relational)
  318. .add("many2one", Relational)
  319. .add("one2many", Relational);
  320. return {
  321. AdvancedSearchProposition: AdvancedSearchProposition,
  322. Relational: Relational,
  323. };
  324. });