diff --git a/web_advanced_search_x2x/README.rst b/web_advanced_search_x2x/README.rst index f9346d82..548ef398 100644 --- a/web_advanced_search_x2x/README.rst +++ b/web_advanced_search_x2x/README.rst @@ -1,3 +1,8 @@ +.. image:: https://img.shields.io/badge/license-LGPL--3-blue.svg + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 + +========================================= Search for x2x records in advanced search ========================================= @@ -12,6 +17,7 @@ To use this module, you need to: * select a one2many, many2many or many2one field * select operator `is equal to` or `is not equal to` * the textfield changes to a many2one selection field where you can search for the record in question +* click *Apply* To search for properties of linked records (ie invoices for customers with a credit limit higher than X): @@ -19,17 +25,21 @@ To search for properties of linked records (ie invoices for customers with a cre * select a one2many, many2many or many2one field * select operator `is in selection` * in the search view that pops up, select the criteria -* click `Use criteria` -* if you're only interested in certain records, mark them en click `Select` -* if you want to change your selection afterwards, click the search symbol right of the selection term - -In both cases, don't forget to click `Apply` to actually execute the search. +* select the records you want, or select the top corner box to select all matching records with that criteria +* click *Select* Note that you can stack searching for properties: Simply add another advanced search in the selection search window. You can do this indefinetely, so it is possible to search for moves belonging to a journal which has a user who is member of a certain group etc. -For further information, please visit: +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/154/9.0 + +Known issues / Roadmap +====================== -* https://www.odoo.com/forum/help-1 +* When you use *is in selection* search system and choose a domain, it gets + immediately applied, so to add a new condition, you will have to use again + the *Filters* menu. Credits ======= @@ -38,16 +48,20 @@ Contributors ------------ * Holger Brunn +* Vicent Cubells +* Jairo Llopis Maintainer ---------- -.. image:: http://odoo-community.org/logo.png - :alt: Odoo Community Association - :target: http://odoo-community.org +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org This module is maintained by the OCA. -OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. -To contribute to this module, please visit http://odoo-community.org. +To contribute to this module, please visit https://odoo-community.org. diff --git a/web_advanced_search_x2x/__init__.py b/web_advanced_search_x2x/__init__.py index faef9dac..723825bd 100644 --- a/web_advanced_search_x2x/__init__.py +++ b/web_advanced_search_x2x/__init__.py @@ -1,20 +1,3 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2015 Therp BV . -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## +# Copyright 2015 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/web_advanced_search_x2x/__openerp__.py b/web_advanced_search_x2x/__openerp__.py index be2fa24b..738746e2 100644 --- a/web_advanced_search_x2x/__openerp__.py +++ b/web_advanced_search_x2x/__openerp__.py @@ -1,27 +1,13 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2015 Therp BV . -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## +# Copyright 2015 Therp BV +# Copyright 2017 Tecnativa - Vicent Cubells +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + { "name": "Search x2x fields", - "version": "8.0.1.0.0", + "version": "9.0.1.0.0", "author": "Therp BV, " + "Tecnativa, " "Odoo Community Association (OCA)", "license": "AGPL-3", "category": "Usability", @@ -35,12 +21,7 @@ "qweb": [ 'static/src/xml/web_advanced_search_x2x.xml', ], - "test": [ - ], "auto_install": False, - 'installable': False, + 'installable': True, "application": False, - "external_dependencies": { - 'python': [], - }, } diff --git a/web_advanced_search_x2x/static/src/css/web_advanced_search_x2x.css b/web_advanced_search_x2x/static/src/css/web_advanced_search_x2x.css deleted file mode 100644 index 67c3e194..00000000 --- a/web_advanced_search_x2x/static/src/css/web_advanced_search_x2x.css +++ /dev/null @@ -1,36 +0,0 @@ -.openerp .searchview_extended_prop_value .oe_form_field_with_button -{ - position: relative; -} -.openerp .oe_searchview_drawer .web_advanced_search_x2x_domain -{ - max-width: 20em; - overflow: hidden; - text-overflow: ellipsis; - display: inline-block; -} -/* copy search view's button style */ -.openerp .oe_searchview_drawer .web_advanced_search_x2x_search:before -{ - font: 21px "mnmliconsRegular"; - content: "r"; - color: #a3a3a3; - margin-left: 5px; -} -.openerp .oe_searchview_drawer .web_advanced_search_x2x_search -{ - font-size: 1px; - letter-spacing: -1px; - color: transparent; - text-shadow: none; - font-weight: normal; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; - -moz-border-radius: 0; - -webkit-border-radius: 0; - border-radius: 0; - padding: 0; - border: none; - background: transparent; -} diff --git a/web_advanced_search_x2x/static/src/css/web_advanced_search_x2x.less b/web_advanced_search_x2x/static/src/css/web_advanced_search_x2x.less new file mode 100644 index 00000000..008e4b56 --- /dev/null +++ b/web_advanced_search_x2x/static/src/css/web_advanced_search_x2x.less @@ -0,0 +1,16 @@ +.openerp { + .oe-search-options { + .searchview_extended_prop_value { + .oe_form { + .ui-autocomplete-input { + .form-control(); + } + + .oe_m2o_drop_down_button { + top: 6px; + right: 2px; + } + } + } + } +} diff --git a/web_advanced_search_x2x/static/src/js/web_advanced_search_x2x.js b/web_advanced_search_x2x/static/src/js/web_advanced_search_x2x.js index f233c235..0684aa16 100644 --- a/web_advanced_search_x2x/static/src/js/web_advanced_search_x2x.js +++ b/web_advanced_search_x2x/static/src/js/web_advanced_search_x2x.js @@ -1,34 +1,23 @@ -//-*- coding: utf-8 -*- -//############################################################################ -// -// OpenERP, Open Source Management Solution -// This module copyright (C) 2015 Therp BV . -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . -// -//############################################################################ +/* Copyright 2015 Therp BV + * Copyright 2017 Jairo Llopis + * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ -openerp.web_advanced_search_x2x = function(instance) -{ - instance.web_advanced_search_x2x.ExtendedSearchPropositionMany2One = - instance.web.search.ExtendedSearchProposition.Char.extend( - instance.web.form.FieldManagerMixin, - { - template: 'web_advanced_search_x2x.extended_search.proposition.many2one', - searchfield: null, +odoo.define('web_advanced_search_x2x.search_filters', function (require) { + "use strict"; + + require('web.form_relational'); + require('web.form_widgets'); + var search_filters = require('web.search_filters'); + var form_common = require('web.form_common'); + var SearchView = require('web.SearchView'); + var data = require('web.data'); + var core = require('web.core'); + + var X2XAdvancedSearchPropositionMixin = { + template: "web_advanced_search_x2x.proposition", init: function() { + // Make equal and not equal appear 1st and 2nd this.operators = _.sortBy( this.operators, function(op) @@ -43,99 +32,132 @@ openerp.web_advanced_search_x2x = function(instance) return 0; } }); + // Append domain operator this.operators.push({ - 'value': 'domain', 'text': instance.web._lt('is in selection'), + 'value': 'domain', 'text': core._lt('is in selection'), }); return this._super.apply(this, arguments); }, - start: function() - { - this.getParent().$('.searchview_extended_prop_op') - .on('change', this.proxy('operator_changed')); - return this._super.apply(this, arguments).then( - this.proxy(this.operator_changed)); - }, get_field_desc: function() { return this.field; }, - create_searchfield_node: function() - { + /** + * Add the right relational field to the template. + */ + renderElement: function () { + try { + this._x2x_field.destroy(); + } catch (error) {} + this.relational = this.x2x_widget_name(); + this._super.apply(this, arguments); + if (this.relational) { + this.x2x_field().appendTo(this.$el); + this._x2x_field.$el.on( + "autocompleteopen", + this.proxy('x2x_autocomplete_open') + ); + } + delete this.relational; + }, + /** + * Create a relational field for the user. + * + * @return {Field} + */ + x2x_field: function () { + if (this._x2x_field) { + this._x2x_field.destroy(); + delete this._x2x_field; + } + var widget = this.x2x_widget(); + if (!widget) return; + this._x2x_field = new widget( + this, + this.x2x_field_create_options() + ); + this._x2x_field.on( + "change:value", + this, + this.proxy("x2x_value_changed") + ); + return this._x2x_field; + }, + x2x_field_create_options: function () { return { attrs: { name: this.field.name, - options: '{"no_create": true}', + options: JSON.stringify({ + no_create: true, + no_open: true, + model: this.field.relation, + }), }, - } + }; }, - create_searchfield: function() - { - if(this.searchfield) - { - this.searchfield.destroy(); + x2x_value_changed: function () { + switch (this.x2x_widget_name()) { + case "char_domain": + // Apply domain when selected + this.getParent().getParent().commit_search(); + break; } - this.searchfield = new instance.web.form.FieldMany2One( - this, this.create_searchfield_node()); - return this.searchfield; }, - operator_changed: function(e) - { - if(this.searchfield) - { - this.searchfield.destroy(); - } - this.renderElement(); - if(this.show_searchfield()) - { - this.create_searchfield().appendTo(this.$el.empty()); - } - if(this.show_domain_selection()) - { - this.$el.filter('input').remove(); - this.$el.filter('button.web_advanced_search_x2x_search').click( - this.proxy(this.popup_domain_selection)); - this.popup_domain_selection(); - } + x2x_widget: function () { + var name = this.x2x_widget_name(); + return name && core.form_widget_registry.get(name); }, - get_operator: function() - { - if(this.isDestroyed()) - { - return false; + x2x_widget_name: function () { + switch (this.get_operator()) { + case "=": + case "!=": + return "many2one"; + case "domain": + return "char_domain"; } - return this.getParent().$('.searchview_extended_prop_op').val(); - }, - show_searchfield: function() - { - var operator = this.get_operator() - return operator == '=' || operator == '!='; }, - show_domain_selection: function() + x2x_autocomplete_open: function() { - return this.get_operator() == 'domain'; + var widget = this._x2x_field.$input.autocomplete("widget"); + widget.on('click', 'li', function(event) { + event.stopPropagation(); + }); }, - get_value: function() - { - if(this.show_searchfield() && this.searchfield) - { - return this.searchfield.get_value(); + get_domain: function () { + // Special way to get domain if user chose "domain" filter + if (this.get_operator() == "domain") { + var value = this._x2x_field.get_value(); + var domain = new data.CompoundDomain(), + name = this.field.name; + $.map(value, function (el) { + domain.add([[ + _.str.sprintf("%s.%s", name, el[0]), + el[1], + el[2], + ]]); + }); + return domain; + } else { + return this._super.apply(this, arguments); } - return this._super.apply(this, arguments); }, - format_label: function(format, field, operator) - { - var value = null; - if(this.show_searchfield() && this.searchfield) - { - value = this.searchfield.display_value[ - String(this.searchfield.get_value())]; - } - if(this.show_domain_selection() && this.domain_representation) - { - value = this.domain_representation; + get_operator: function () { + return !this.isDestroyed() && + this.getParent().$('.searchview_extended_prop_op').val(); + }, + get_value: function () { + try { + return this._x2x_field.get_value(); + } catch (error) { + return this._super.apply(this, arguments); } - if(value) - { + }, + format_label: function (format, field, operator) { + if (this.x2x_widget()) { + var value = String(this._x2x_field.get_value()); + if (this._x2x_field.display_value) { + value = this._x2x_field.display_value[value]; + } return _.str.sprintf( format, { @@ -144,146 +166,50 @@ openerp.web_advanced_search_x2x = function(instance) value: value, } ); + } else { + return this._super.apply(this, arguments); } - return this._super.apply(this, arguments); }, - get_domain: function() - { - if(this.show_domain_selection()) - { - var self = this; - if(!this.domain || this.domain.length == 0) - { - throw new instance.web.search.Invalid( - this.field.string, this.domain_representation, - instance.web._lt('invalid search domain')); - } - return _.extend(new instance.web.CompoundDomain(), { - __domains: [ - _.map(this.domain, function(leaf) - { - if(_.isArray(leaf) && leaf.length == 3) - { - return [ - self.field.name + '.' + leaf[0], - leaf[1], - leaf[2] - ] - } - return leaf; - }), - ], - }) + }; + + var ExtendedSearchProposition = search_filters.ExtendedSearchProposition, + Char = ExtendedSearchProposition.Char, + affected_types = ["one2many", "many2one", "many2many"], + X2XAdvancedSearchProposition = Char.extend( + form_common.FieldManagerMixin, + X2XAdvancedSearchPropositionMixin + ); + + ExtendedSearchProposition.include({ + /** + * Force re-rendering the value widget if needed. + */ + operator_changed: function (event) { + if (this.value instanceof X2XAdvancedSearchProposition) { + this.value_rerender(); } return this._super.apply(this, arguments); }, - popup_domain_selection: function() - { - var self = this, - popup = new instance.web_advanced_search_x2x.SelectCreatePopup(this); - popup.on('domain_selected', this, function(domain, domain_representation) - { - self.$el.filter('.web_advanced_search_x2x_domain').text( - domain_representation); - self.domain = domain; - self.domain_representation = domain_representation; - }); - popup.select_element( - this.field.relation, {}, this.field.domain, - new instance.web.CompoundContext( - instance.session.user_context, this.field.context)); + /** + * Re-render proposition's value widget. + * + * @return {jQuery.Deferred} + */ + value_rerender: function () { + this.value._x2x_field && this.value._x2x_field.destroy(); + delete this.value._x2x_field; + return this.value.appendTo( + this.$(".searchview_extended_prop_value").show().empty() + ); }, }); - instance.web.search.custom_filters.add( - 'one2many', - 'instance.web_advanced_search_x2x.ExtendedSearchPropositionMany2One'); - instance.web.search.custom_filters.add( - 'many2many', - 'instance.web_advanced_search_x2x.ExtendedSearchPropositionMany2One'); - instance.web.search.custom_filters.add( - 'many2one', - 'instance.web_advanced_search_x2x.ExtendedSearchPropositionMany2One'); - - instance.web_advanced_search_x2x.SelectCreatePopup = instance.web.form.SelectCreatePopup.extend({ - setup_search_view: function() - { - var self = this; - this._super.apply(this, arguments); - this.searchview.on("search_view_loaded", this, function() - { - self.view_list.on("list_view_loaded", self, function() - { - self.$buttonpane.find(".oe_selectcreatepopup-search-create").remove(); - self.$buttonpane.prepend( - jQuery(' + + - diff --git a/web_advanced_search_x2x/views/templates.xml b/web_advanced_search_x2x/views/templates.xml index 29424680..43eebace 100644 --- a/web_advanced_search_x2x/views/templates.xml +++ b/web_advanced_search_x2x/views/templates.xml @@ -1,11 +1,13 @@ - - - - - + + + +