Browse Source

[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 roadmap
pull/1193/head
tarteo 5 years ago
committed by Dennis Sluijk
parent
commit
ae45dd9edc
  1. 100
      web_edit_user_filter/README.rst
  2. 1
      web_edit_user_filter/__init__.py
  3. 22
      web_edit_user_filter/__manifest__.py
  4. 1
      web_edit_user_filter/models/__init__.py
  5. 23
      web_edit_user_filter/models/ir_filters.py
  6. 1
      web_edit_user_filter/readme/CONTRIBUTORS.rst
  7. 4
      web_edit_user_filter/readme/DESCRIPTION.rst
  8. 2
      web_edit_user_filter/readme/ROADMAP.rst
  9. 15
      web_edit_user_filter/readme/USAGE.rst
  10. BIN
      web_edit_user_filter/static/description/edit_facet.png
  11. 242
      web_edit_user_filter/static/src/js/backend.js
  12. 27
      web_edit_user_filter/static/src/scss/backend.scss
  13. 30
      web_edit_user_filter/static/src/xml/backend.xml
  14. 16
      web_edit_user_filter/templates/assets.xml
  15. 1
      web_edit_user_filter/tests/__init__.py
  16. 32
      web_edit_user_filter/tests/test_edit_user_filter.py

100
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 <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.

1
web_edit_user_filter/__init__.py

@ -0,0 +1 @@
from . import models

22
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,
}

1
web_edit_user_filter/models/__init__.py

@ -0,0 +1 @@
from . import ir_filters

23
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

1
web_edit_user_filter/readme/CONTRIBUTORS.rst

@ -0,0 +1 @@
* Dennis Sluijk <d.sluijk@onestein.nl>

4
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).

2
web_edit_user_filter/readme/ROADMAP.rst

@ -0,0 +1,2 @@
* Make individual values in facets editable.
* Make saved filters easy overwritable.

15
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

BIN
web_edit_user_filter/static/description/edit_facet.png

After

Width: 414  |  Height: 150  |  Size: 12 KiB

242
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($('<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;
}
});
});

27
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;
}

30
web_edit_user_filter/static/src/xml/backend.xml

@ -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>

16
web_edit_user_filter/templates/assets.xml

@ -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>

1
web_edit_user_filter/tests/__init__.py

@ -0,0 +1 @@
from . import test_edit_user_filter

32
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'
)
Loading…
Cancel
Save