diff --git a/web_edit_user_filter/README.rst b/web_edit_user_filter/README.rst new file mode 100644 index 00000000..b4498972 --- /dev/null +++ b/web_edit_user_filter/README.rst @@ -0,0 +1,100 @@ +================= +Edit User Filters +================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png + :target: https://odoo-community.org/page/development-status + :alt: Production/Stable +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github + :target: https://github.com/OCA/web/tree/12.0/web_edit_user_filter + :alt: OCA/web +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/web-12-0/web-12-0-web_edit_user_filter + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/162/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +In standard Odoo you can edit user filters via the debug module. +The problem is that normal users often don't have access to this menu therefore can't adjust filters once they're saved. +This module makes this feature available for normal users with a user friendly interface. +It also adds the ability to adjust facets (a single part of the filter). + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Edit a favourite filter: + +#. Go to a list or kanban view; +#. open the advanced search options; +#. open the 'Favorites' menu; +#. click on the pencil icon to start editing the filter. + +Edit a facet: + +#. Click on the facet; +#. a menu is now shown which allows you to remove values from the facet; +#. to cancel removal you can click outside the popover. + +.. image:: /web_edit_user_filter/static/description/edit_facet.png + :alt: Edit Facet + +Known issues / Roadmap +====================== + +* Make individual values in facets editable. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Onestein + +Contributors +~~~~~~~~~~~~ + +* Dennis Sluijk + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +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. + +This module is part of the `OCA/web `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/web_edit_user_filter/__init__.py b/web_edit_user_filter/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/web_edit_user_filter/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/web_edit_user_filter/__manifest__.py b/web_edit_user_filter/__manifest__.py new file mode 100644 index 00000000..1b068468 --- /dev/null +++ b/web_edit_user_filter/__manifest__.py @@ -0,0 +1,22 @@ +# Copyright 2019 Onestein +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'Edit User Filters', + 'category': 'Extra Tools', + 'version': '12.0.1.0.0', + 'development_status': 'Production/Stable', + 'author': 'Onestein,Odoo Community Association (OCA)', + 'license': 'AGPL-3', + 'website': 'https://github.com/OCA/web', + 'depends': [ + 'web' + ], + 'data': [ + 'templates/assets.xml' + ], + 'qweb': [ + 'static/src/xml/backend.xml' + ], + 'installable': True, +} diff --git a/web_edit_user_filter/models/__init__.py b/web_edit_user_filter/models/__init__.py new file mode 100644 index 00000000..4c520abb --- /dev/null +++ b/web_edit_user_filter/models/__init__.py @@ -0,0 +1 @@ +from . import ir_filters diff --git a/web_edit_user_filter/models/ir_filters.py b/web_edit_user_filter/models/ir_filters.py new file mode 100644 index 00000000..05925613 --- /dev/null +++ b/web_edit_user_filter/models/ir_filters.py @@ -0,0 +1,23 @@ +# Copyright 2019 Onestein +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class IrFilters(models.Model): + _inherit = 'ir.filters' + + facet = fields.Text() + + @api.model + def get_filters(self, model, action_id=None): + res = super().get_filters(model, action_id) + ids = map(lambda f: f['id'], res) + # Browse filters that are in res + filters = self.browse(ids) + for i, res_filter in enumerate(res): + # Add the field 'facet' to the result + res[i]['facet'] = filters.filtered( + lambda f: f.id == res_filter['id'] + ).facet + return res diff --git a/web_edit_user_filter/readme/CONTRIBUTORS.rst b/web_edit_user_filter/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..47b6403d --- /dev/null +++ b/web_edit_user_filter/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Dennis Sluijk diff --git a/web_edit_user_filter/readme/DESCRIPTION.rst b/web_edit_user_filter/readme/DESCRIPTION.rst new file mode 100644 index 00000000..68f177e9 --- /dev/null +++ b/web_edit_user_filter/readme/DESCRIPTION.rst @@ -0,0 +1,4 @@ +In standard Odoo you can edit user filters via the debug module. +The problem is that normal users often don't have access to this menu therefore can't adjust filters once they're saved. +This module makes this feature available for normal users with a user friendly interface. +It also adds the ability to adjust facets (a single part of the filter). diff --git a/web_edit_user_filter/readme/ROADMAP.rst b/web_edit_user_filter/readme/ROADMAP.rst new file mode 100644 index 00000000..3a953110 --- /dev/null +++ b/web_edit_user_filter/readme/ROADMAP.rst @@ -0,0 +1,2 @@ +* Make individual values in facets editable. +* Make saved filters easy overwritable. diff --git a/web_edit_user_filter/readme/USAGE.rst b/web_edit_user_filter/readme/USAGE.rst new file mode 100644 index 00000000..2225d9b4 --- /dev/null +++ b/web_edit_user_filter/readme/USAGE.rst @@ -0,0 +1,15 @@ +Edit a favourite filter: + +#. Go to a list or kanban view; +#. open the advanced search options; +#. open the 'Favorites' menu; +#. click on the pencil icon to start editing the filter. + +Edit a facet: + +#. Click on the facet; +#. a menu is now shown which allows you to remove values from the facet; +#. to cancel removal you can click outside the popover. + +.. image:: /web_edit_user_filter/static/description/edit_facet.png + :alt: Edit Facet diff --git a/web_edit_user_filter/static/description/edit_facet.png b/web_edit_user_filter/static/description/edit_facet.png new file mode 100644 index 00000000..8932b290 Binary files /dev/null and b/web_edit_user_filter/static/description/edit_facet.png differ diff --git a/web_edit_user_filter/static/src/js/backend.js b/web_edit_user_filter/static/src/js/backend.js new file mode 100644 index 00000000..02a3373a --- /dev/null +++ b/web_edit_user_filter/static/src/js/backend.js @@ -0,0 +1,242 @@ +/* Copyright 2019 Onestein + * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ + +odoo.define('web_edit_user_filter', function (require) { + "use strict"; + + var FavoriteMenu = require('web.FavoriteMenu'), + core = require('web.core'), + SearchView = require('web.SearchView'); + var qweb = core.qweb; + var _t = core._t; + + + FavoriteMenu.include({ + + /** + * Adds the facets data to the filter. + * + * @override + * @private + */ + _createFilter: function (filter) { + var facets = []; + + this.query.each(function (facet) { + var json_facet = facet.attributes; + json_facet.values = facet.get('values'); + + _.each(json_facet.values, function (value, i) { + if (typeof value.value === 'object' && + 'attrs' in value.value) { + json_facet.values[i] = { + attrs: value.value.attrs, + }; + } + }); + + if ('field' in json_facet) { + json_facet.field = { + attrs: json_facet.field.attrs, + }; + } + + facets.push(json_facet); + }); + + filter.facet = JSON.stringify(facets); + return this._super(filter); + }, + + /** + * Adds the edit button to the favourite filter menu item. + * + * @override + * @private + */ + append_filter: function (filter) { + var self = this; + var res = this._super(filter); + var key = this.key_for(filter); + this.$filters[key].append($('', { + class: 'fa fa-pencil o-edit-user-filter', + on: { + click: function (event) { + event.stopImmediatePropagation(); + self._unpackFilter(filter); + }, + }, + })); + return res; + }, + + /** + * Unpacks a saved filter and updates the search view's facets. + * + * @private + */ + _unpackFilter: function (filter) { + var self = this; + var facets = JSON.parse(filter.facet); + + var new_facets = []; + this.query.reset([]); + + _.each(facets, function (segment) { + if (segment.cat === 'groupByCategory') { + _.each(segment.values, function (value) { + var groupBy = _.find( + self.searchview.groupbysMapping, + function (mapping) { + return mapping.groupby.attrs.context === value.attrs.context; + } + ); + var eventData = { + category: 'groupByCategory', + itemId: groupBy.groupbyId, + isActive: true, + groupId: groupBy.groupId, + }; + self.trigger_up('menu_item_toggled', eventData); + }); + } else if (segment.cat === 'filterCategory') { + _.each(segment.values, function (value) { + var filterDomain = _.find( + self.searchview.filtersMapping, + function (mapping) { + return mapping.filter.attrs.domain === value.attrs.domain; + } + ); + var eventData = { + category: 'filterCategory', + itemId: filterDomain.filterId, + isActive: true, + groupId: filterDomain.groupId, + }; + self.trigger_up('menu_item_toggled', eventData); + }); + } else { + var search_widget = _.find( + self.searchview.search_fields, function (f) { + return f.attrs.name === segment.field.attrs.name; + } + ); + new_facets.push({ + category: segment.category, + field: search_widget, + values: segment.values, + }); + } + }); + + this.query.add(new_facets); + }, + }); + + SearchView.include({ + + /** + * Removes a value from a facet. + * + * @private + * @param {Backbone.Model} model + * @param {Integer|Object} value The value to remove + */ + _removeValue: function (model, value) { + var toRemove = model.values.filter(function (v) { + if (typeof v.attributes.value === 'object') { + return v.attributes.value.attrs.name === value; + } + + return v.attributes.value.toString() === value; + }); + model.values.remove(toRemove); + }, + + /** + * Renders a popover for a facet. + * + * @private + * @param {jQuery} $facet Element of the facet + * @param {Backbone.Model} model + */ + _renderPopover: function ($facet, model) { + var self = this; + + var $content = $(qweb.render('web_edit_user_filter.Popover', { + values: model.get('values'), + })); + // Cannot use Widget.events here because renderFacets is + // triggered apart from renderElement + $content.find('.list-group-item').click(function () { + self._removeValue(model, $(this).attr('data-value')); + }); + + $facet.popover({ + title: _t('Edit Facet'), + template: qweb.render('web_edit_user_filter.PopoverTemplate'), + content: $content, + container: this.$el, + html: true, + trigger: 'manual', + placement: 'bottom', + animation: false, + }); + }, + + /** + * Hides all popovers. + * + * @private + */ + _hidePopovers: function () { + this.$el.find('.popover').popover('hide'); + }, + + /** + * @override + */ + renderFacets: function () { + var self = this; + var res = this._super.apply(this, arguments); + + this.$el.find('.o-edit-user-filter-popover').remove(); + + _.each(this.input_subviews, function (input_subview) { + if (!input_subview.model || + input_subview.model.attributes.is_custom_filter) { + return; + } + + input_subview.$el.addClass('o-edit-user-filter-editable'); + self._renderPopover(input_subview.$el, input_subview.model); + + input_subview.$el.click(function () { + self._hidePopovers(); + input_subview.$el.popover('show'); + }); + }); + return res; + }, + + /** + * @override + */ + start: function () { + var self = this; + var res = this._super.apply(this, arguments); + this._proxyHidePopovers = this.proxy('_hidePopovers'); + $(document).click(this._proxyHidePopovers); + return res; + }, + + /** + * @override + */ + destroy: function () { + var res = this._super.apply(this, arguments); + $(document).unbind('click', this._proxyHidePopovers); + return res; + } + }); +}); diff --git a/web_edit_user_filter/static/src/scss/backend.scss b/web_edit_user_filter/static/src/scss/backend.scss new file mode 100644 index 00000000..eb0c8cd1 --- /dev/null +++ b/web_edit_user_filter/static/src/scss/backend.scss @@ -0,0 +1,27 @@ +/* Copyright 2019 Onestein + * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ + +.o_favorites_menu { + .o-edit-user-filter { + @include o-position-absolute(50%, 30px); + margin-top: -6px; + cursor: pointer; + } +} + +.o-edit-user-filter-popover { + a { + cursor: pointer; + padding: 0.3rem 0.75rem; + font-size: 1.2rem; + + .fa-close { + padding-left: 13px; + font-size: 1.2rem; + } + } +} + +.o-edit-user-filter-editable { + cursor: pointer; +} diff --git a/web_edit_user_filter/static/src/xml/backend.xml b/web_edit_user_filter/static/src/xml/backend.xml new file mode 100644 index 00000000..de4a1668 --- /dev/null +++ b/web_edit_user_filter/static/src/xml/backend.xml @@ -0,0 +1,30 @@ + + + + diff --git a/web_edit_user_filter/templates/assets.xml b/web_edit_user_filter/templates/assets.xml new file mode 100644 index 00000000..5ba1b305 --- /dev/null +++ b/web_edit_user_filter/templates/assets.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/web_edit_user_filter/tests/__init__.py b/web_edit_user_filter/tests/__init__.py new file mode 100644 index 00000000..b069e657 --- /dev/null +++ b/web_edit_user_filter/tests/__init__.py @@ -0,0 +1 @@ +from . import test_edit_user_filter diff --git a/web_edit_user_filter/tests/test_edit_user_filter.py b/web_edit_user_filter/tests/test_edit_user_filter.py new file mode 100644 index 00000000..4819d826 --- /dev/null +++ b/web_edit_user_filter/tests/test_edit_user_filter.py @@ -0,0 +1,32 @@ +# Copyright 2019 Onestein +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import SingleTransactionCase, post_install + + +@post_install(True) +class TestEditUserFilter(SingleTransactionCase): + def test_filter_facet_inclusion(self): + self.env['ir.filters'].create({ + 'name': 'any2', + 'model_id': 'ir.filters', + 'domain': '[]', + 'facet': 'test2' + }) + new_filter = self.env['ir.filters'].create({ + 'name': 'any', + 'model_id': 'ir.filters', + 'domain': '[]', + 'facet': 'test' + }) + res = self.env['ir.filters'].get_filters('ir.filters') + self.assertTrue('facet' in res[0]) + self.assertEqual( + list( + filter( + lambda f: f['id'] == new_filter.id, + res + ) + )[0]['facet'], + 'test' + )