Browse Source
[ADD] web_edit_user_filter
[ADD] web_edit_user_filter
[FIX] Readme [FIX] Lint [ADD] Tests [IMP] UI/UX [IMP] Hide popover when other is opened [IMP] Add item to roadmappull/1193/head
tarteo
6 years ago
committed by
Dennis Sluijk
16 changed files with 517 additions and 0 deletions
-
100web_edit_user_filter/README.rst
-
1web_edit_user_filter/__init__.py
-
22web_edit_user_filter/__manifest__.py
-
1web_edit_user_filter/models/__init__.py
-
23web_edit_user_filter/models/ir_filters.py
-
1web_edit_user_filter/readme/CONTRIBUTORS.rst
-
4web_edit_user_filter/readme/DESCRIPTION.rst
-
2web_edit_user_filter/readme/ROADMAP.rst
-
15web_edit_user_filter/readme/USAGE.rst
-
BINweb_edit_user_filter/static/description/edit_facet.png
-
242web_edit_user_filter/static/src/js/backend.js
-
27web_edit_user_filter/static/src/scss/backend.scss
-
30web_edit_user_filter/static/src/xml/backend.xml
-
16web_edit_user_filter/templates/assets.xml
-
1web_edit_user_filter/tests/__init__.py
-
32web_edit_user_filter/tests/test_edit_user_filter.py
@ -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 <https://github.com/OCA/web/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 <https://github.com/OCA/web/issues/new?body=module:%20web_edit_user_filter%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. |
||||
|
|
||||
|
Do not contact contributors directly about support or help with technical issues. |
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Authors |
||||
|
~~~~~~~ |
||||
|
|
||||
|
* Onestein |
||||
|
|
||||
|
Contributors |
||||
|
~~~~~~~~~~~~ |
||||
|
|
||||
|
* Dennis Sluijk <d.sluijk@onestein.nl> |
||||
|
|
||||
|
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 <https://github.com/OCA/web/tree/12.0/web_edit_user_filter>`_ project on GitHub. |
||||
|
|
||||
|
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. |
@ -0,0 +1 @@ |
|||||
|
from . import models |
@ -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, |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
from . import ir_filters |
@ -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 |
@ -0,0 +1 @@ |
|||||
|
* Dennis Sluijk <d.sluijk@onestein.nl> |
@ -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). |
@ -0,0 +1,2 @@ |
|||||
|
* Make individual values in facets editable. |
||||
|
* Make saved filters easy overwritable. |
@ -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 |
After Width: 414 | Height: 150 | Size: 12 KiB |
@ -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($('<span>', { |
||||
|
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; |
||||
|
} |
||||
|
}); |
||||
|
}); |
@ -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; |
||||
|
} |
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- Copyright 2019 Onestein |
||||
|
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> |
||||
|
|
||||
|
<template> |
||||
|
<div t-name="web_edit_user_filter.Popover"> |
||||
|
|
||||
|
<div class="list-group"> |
||||
|
<t t-foreach="values" t-as="value"> |
||||
|
<a class="list-group-item list-group-item-action" |
||||
|
t-attf-data-value="#{typeof value.value === 'object' ? value.value.attrs.name : value.value}"> |
||||
|
<span> |
||||
|
<t t-esc="value.label"/> |
||||
|
</span> |
||||
|
<button class="btn btn-sm btn-link pull-right"> |
||||
|
<span class="fa fa-close" /> |
||||
|
</button> |
||||
|
</a> |
||||
|
</t> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<t t-name="web_edit_user_filter.PopoverTemplate"> |
||||
|
<div class="popover o-edit-user-filter-popover" role="tooltip"> |
||||
|
<h3 class="popover-header"></h3> |
||||
|
<div class="arrow"></div> |
||||
|
<div class="popover-body"></div> |
||||
|
</div> |
||||
|
</t> |
||||
|
</template> |
@ -0,0 +1,16 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- Copyright 2019 Onestein |
||||
|
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> |
||||
|
|
||||
|
<odoo> |
||||
|
<template id="assets_backend" inherit_id="web.assets_backend"> |
||||
|
<xpath expr="."> |
||||
|
<script src="/web_edit_user_filter/static/src/js/backend.js"></script> |
||||
|
</xpath> |
||||
|
<xpath expr="//link[last()]"> |
||||
|
<link href="/web_edit_user_filter/static/src/scss/backend.scss" |
||||
|
rel="stylesheet" |
||||
|
type="text/scss"/> |
||||
|
</xpath> |
||||
|
</template> |
||||
|
</odoo> |
@ -0,0 +1 @@ |
|||||
|
from . import test_edit_user_filter |
@ -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' |
||||
|
) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue