[MIG] web_advanced_filter

This commit is contained in:
Holger Brunn
2020-04-21 23:47:25 +02:00
committed by Jan Verbeek
parent 969446594c
commit bbeddba6e1
35 changed files with 1481 additions and 854 deletions

View File

@@ -0,0 +1,100 @@
================
Advanced filters
================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |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_advanced_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_advanced_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|
This addon allows users to apply set operations on filters: Remove or add
certain ids from/to a selection, but also to remove or add another filter's
outcome from/to a filter. This can be stacked, so the filter domain can be
arbitrarily complicated.
The math is hidden from the user as far as possible, in the hope it's still
user friendly.
**Table of contents**
.. contents::
:local:
Usage
=====
After this addon is installed, every search view shows a new menu `Advanced
filters`. Here the set operations can be applied as necessary.
Note the menu is only visible when there's search criteria or some record
selected.
Known issues / Roadmap
======================
* client side tests
* consider renaming all instances of `Favorites` in core to `Filters` for consistency
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_advanced_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
~~~~~~~
* Therp BV
Contributors
~~~~~~~~~~~~
* Holger Brunn <hbrunn@therp.nl>
Other credits
~~~~~~~~~~~~~
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
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_advanced_filter>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,18 @@
from . import models
from . import wizards
def pre_init_hook(cr):
"""take care to move the domain field to domain_this to keep the values"""
cr.execute(
'SELECT count(attname) FROM pg_attribute '
'WHERE attrelid = '
'(SELECT oid FROM pg_class WHERE relname = %s) '
'AND attname = %s', ('ir_filters', 'domain_this'))
if not cr.fetchone()[0]:
cr.execute('ALTER table ir_filters RENAME domain TO domain_this')
def uninstall_hook(cr, registry):
"""move domain_this back to domain"""
cr.execute('ALTER TABLE ir_filters RENAME domain_this TO domain')

View File

@@ -0,0 +1,24 @@
# Copyright 2014 Therp BV <https://therp.nl>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{
"name": "Advanced filters",
"version": "12.0.1.0.0",
"author": "Therp BV,Odoo Community Association (OCA)",
"license": "AGPL-3",
"complexity": "normal",
"summary": "Set operations on filter results",
"category": "Tools",
"depends": [
'web',
],
"qweb": [
"static/src/xml/web_advanced_filter.xml",
],
"data": [
"wizards/ir_filters_combine_with_existing.xml",
"views/ir_filters.xml",
"views/templates.xml",
],
"pre_init_hook": "pre_init_hook",
"uninstall_hook": "uninstall_hook",
}

View File

@@ -0,0 +1,195 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * web_advanced_filters
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-07-29 10:55+0000\n"
"PO-Revision-Date: 2014-07-29 10:55+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Save filter"
msgstr ""
#. module: web_advanced_filters
#: selection:ir.filters.combine.with.existing,action:0
msgid "Union"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Add the result of following filters"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0
#: view:ir.filters.combine.with.existing:0
msgid "Save"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Freeze filter"
msgstr ""
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:94
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:114
#, python-format
msgid "Remove from existing selection"
msgstr ""
#. module: web_advanced_filters
#: code:addons/web_advanced_filters/model/ir_filters.py:131
#, python-format
msgid "Testing %s"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0
#: view:ir.filters.combine.with.existing:0
msgid "or"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters,complement_filter_ids:0
msgid "Remove result of filters"
msgstr ""
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:86
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:106
#, python-format
msgid "To existing selection"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters.combine.with.existing:0
msgid "Combine with existing filter"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Have this filter contain extly the records it currently contains, with no changes in the future. Be careful, you can't undo this operation!"
msgstr ""
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:32
#, python-format
msgid "Advanced filters"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters,active:0
msgid "Active"
msgstr ""
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:74
#, python-format
msgid "Marked records"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters,is_frozen:0
msgid "Frozen"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,filter_id:0
msgid "Filter"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Test filter"
msgstr ""
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:78
#, python-format
msgid "To new selection"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters,domain_this:0
msgid "This filter's own domain"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Remove the result of following filters"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,context:0
msgid "Context"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,action:0
msgid "Action"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,model:0
msgid "Model"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,domain:0
msgid "Domain"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters,union_filter_ids:0
msgid "Add result of filters"
msgstr ""
#. module: web_advanced_filters
#: model:ir.model,name:web_advanced_filters.model_ir_filters_combine_with_existing
msgid "Combine a selection with an existing filter"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Are you sure? You can't undo this operation!"
msgstr ""
#. module: web_advanced_filters
#: model:ir.model,name:web_advanced_filters.model_ir_filters
msgid "Filters"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0
#: view:ir.filters.combine.with.existing:0
msgid "Cancel"
msgstr ""
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:102
#, python-format
msgid "Whole selection"
msgstr ""
#. module: web_advanced_filters
#: selection:ir.filters.combine.with.existing,action:0
msgid "Complement"
msgstr ""

View File

@@ -0,0 +1,203 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * web_advanced_filters
#
# Rudolf Schnapka <schnapkar@golive-saar.de>, 2015.
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-07-29 10:55+0000\n"
"PO-Revision-Date: 2015-01-04 14:05+0100\n"
"Last-Translator: Rudolf Schnapka <schnapkar@golive-saar.de>\n"
"Language-Team: German <kde-i18n-de@kde.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
"Language: de\n"
"X-Generator: Lokalize 1.5\n"
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Save filter"
msgstr "Filter sichern"
#. module: web_advanced_filters
#: selection:ir.filters.combine.with.existing,action:0
msgid "Union"
msgstr "Verbund"
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Add the result of following filters"
msgstr "Die Ergebnisse der folgenden Filter hinzufügen"
#. module: web_advanced_filters
#: view:ir.filters:0
#: view:ir.filters.combine.with.existing:0
msgid "Save"
msgstr "Speichern"
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Freeze filter"
msgstr "Filter einfrieren"
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:94
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:114
#, python-format
msgid "Remove from existing selection"
msgstr "Aus aktueller Auswahl entfernen"
#. module: web_advanced_filters
#: code:addons/web_advanced_filters/model/ir_filters.py:131
#, python-format
msgid "Testing %s"
msgstr "Teste %s"
#. module: web_advanced_filters
#: view:ir.filters:0
#: view:ir.filters.combine.with.existing:0
msgid "or"
msgstr "oder"
#. module: web_advanced_filters
#: field:ir.filters,complement_filter_ids:0
msgid "Remove result of filters"
msgstr "Ergebnis der Filter entfernen"
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:86
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:106
#, python-format
msgid "To existing selection"
msgstr "Zu bestehender Auswahl"
#. module: web_advanced_filters
#: view:ir.filters.combine.with.existing:0
msgid "Combine with existing filter"
msgstr "Mit bestehendem Filter kombinieren"
#. module: web_advanced_filters
#: view:ir.filters:0
msgid ""
"Have this filter contain extly the records it currently contains, with no "
"changes in the future. Be careful, you can't undo this operation!"
msgstr ""
"Diesem Filter genau die aktuellen Datensätze, künftig unveränderlich, "
"zuordnen. Vorsicht, dieser Vorgang kann nicht rückgängig gemacht werden!"
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:32
#, python-format
msgid "Advanced filters"
msgstr "Fortgeschrittene Filter"
#. module: web_advanced_filters
#: field:ir.filters,active:0
msgid "Active"
msgstr "Aktiv"
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:74
#, python-format
msgid "Marked records"
msgstr "Markierte Datensätze"
#. module: web_advanced_filters
#: field:ir.filters,is_frozen:0
msgid "Frozen"
msgstr "Eingefroren"
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,filter_id:0
msgid "Filter"
msgstr "Filter"
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Test filter"
msgstr "Filtertest"
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:78
#, python-format
msgid "To new selection"
msgstr "Zu neuer Auswahl"
#. module: web_advanced_filters
#: field:ir.filters,domain_this:0
msgid "This filter's own domain"
msgstr "Dieses Filters eigene Domäne (Abgrenzung)"
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Remove the result of following filters"
msgstr "Das Ergebnis des folgenden Filters entfernen"
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,context:0
msgid "Context"
msgstr "Kontext"
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,action:0
msgid "Action"
msgstr "Aktion"
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,model:0
msgid "Model"
msgstr "Modell"
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,domain:0
msgid "Domain"
msgstr "Domäne"
#. module: web_advanced_filters
#: field:ir.filters,union_filter_ids:0
msgid "Add result of filters"
msgstr "Ergebnis von Filtern hinzufügen"
#. module: web_advanced_filters
#: model:ir.model,name:web_advanced_filters.model_ir_filters_combine_with_existing
msgid "Combine a selection with an existing filter"
msgstr "Eine Auswahl mit bestehendem Filter kombinieren"
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Are you sure? You can't undo this operation!"
msgstr "Sind Sie sicher? Sie können diesen Vorgang nicht mehr rückgängig machen!"
#. module: web_advanced_filters
#: model:ir.model,name:web_advanced_filters.model_ir_filters
msgid "Filters"
msgstr "Filter"
#. module: web_advanced_filters
#: view:ir.filters:0
#: view:ir.filters.combine.with.existing:0
msgid "Cancel"
msgstr "Abbrechen"
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:102
#, python-format
msgid "Whole selection"
msgstr "Gesamte Auswahl"
#. module: web_advanced_filters
#: selection:ir.filters.combine.with.existing,action:0
msgid "Complement"
msgstr "Ergänzung"

View File

@@ -0,0 +1,199 @@
# This file contains the translation of the following modules:
# * web_advanced_filters
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-07-29 09:54+0000\n"
"PO-Revision-Date: 2014-07-29 09:54+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Save filter"
msgstr "Opslaan filter"
#. module: web_advanced_filters
#: selection:ir.filters.combine.with.existing,action:0
msgid "Union"
msgstr "Union"
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Add the result of following filters"
msgstr "Voeg resultaat van onderstaande filteringen toe aan filter"
#. module: web_advanced_filters
#: view:ir.filters:0
#: view:ir.filters.combine.with.existing:0
msgid "Save"
msgstr "Opslaan"
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Freeze filter"
msgstr "Bevries filter"
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:94
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:114
#, python-format
msgid "Remove from existing filter"
msgstr "Verwijder uit bestaand filter"
#. module: web_advanced_filters
#: code:addons/web_advanced_filters/model/ir_filters.py:131
#, python-format
msgid "Testing %s"
msgstr "Testen %s"
#. module: web_advanced_filters
#: view:ir.filters:0
#: view:ir.filters.combine.with.existing:0
msgid "or"
msgstr "or"
#. module: web_advanced_filters
#: field:ir.filters,save_as_public:0
msgid "Share with all users"
msgstr "Deel met alle gebruikers"
#. module: web_advanced_filters
#: field:ir.filters,complement_filter_ids:0
msgid "Remove result of filters"
msgstr "Verwijder resultaat van filters"
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:86
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:106
#, python-format
msgid "To existing filter"
msgstr "Voeg toe aan bestaand filter"
#. module: web_advanced_filters
#: view:ir.filters.combine.with.existing:0
msgid "Combine with existing filter"
msgstr "Combineer met bestaande filter"
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Have this filter contain exactly the records it currently contains, with no changes in the future. Be careful, you can't undo this operation!"
msgstr "Laat deze filter precies deze records bevatten, zonder veranderingen in de toekomst. Wees voorzichtig, je kan dit niet meer veranderen!"
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:32
#, python-format
msgid "Advanced filters"
msgstr "Geavanceerde filters"
#. module: web_advanced_filters
#: field:ir.filters,active:0
msgid "Active"
msgstr "Actief"
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:74
#, python-format
msgid "Marked records"
msgstr "Aangevinkte records (verzameling)"
#. module: web_advanced_filters
#: field:ir.filters,is_frozen:0
msgid "Frozen"
msgstr "Bevroren"
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,filter_id:0
msgid "Filter"
msgstr "Filter"
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Test filter"
msgstr "Test filter"
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:78
#, python-format
msgid "To new filter"
msgstr "Opslaan als nieuw filter"
#. module: web_advanced_filters
#: field:ir.filters,domain_this:0
msgid "This filter's own domain"
msgstr "Deze filters eigen domein"
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Remove the result of following filters"
msgstr "Verwijder resultaat van onderstaande filteringen uit filter"
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,context:0
msgid "Context"
msgstr "Context"
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,action:0
msgid "Action"
msgstr "Actie"
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,model:0
msgid "Model"
msgstr "Model"
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:102
#, python-format
msgid "Whole selection (criteria)"
msgstr "Alle records (als criteria)"
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,domain:0
msgid "Domain"
msgstr "Domein"
#. module: web_advanced_filters
#: field:ir.filters,union_filter_ids:0
msgid "Add result of filters"
msgstr "Voeg resultaat toe aan filters"
#. module: web_advanced_filters
#: model:ir.model,name:web_advanced_filters.model_ir_filters_combine_with_existing
msgid "Combine a selection with an existing filter"
msgstr "Combineer een selectie met een bestaande filter"
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Are you sure? You can't undo this operation!"
msgstr "Weet u het zeker? U kan deze operatie niet meer ongedaan maken"
#. module: web_advanced_filters
#: model:ir.model,name:web_advanced_filters.model_ir_filters
msgid "Filters"
msgstr "Filters"
#. module: web_advanced_filters
#: view:ir.filters:0
#: view:ir.filters.combine.with.existing:0
msgid "Cancel"
msgstr "Annuleren"
#. module: web_advanced_filters
#: selection:ir.filters.combine.with.existing,action:0
msgid "Complement"
msgstr "Aanvullen"

View File

@@ -0,0 +1,231 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * web_advanced_filters
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: web (7.0)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-06-10 11:28+0000\n"
"PO-Revision-Date: 2015-10-07 17:50+0000\n"
"Last-Translator: <>\n"
"Language-Team: Dutch (Netherlands) (http://www.transifex.com/oca/OCA-web-7-0/language/nl_NL/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: nl_NL\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Save filter"
msgstr ""
#. module: web_advanced_filters
#: selection:ir.filters.combine.with.existing,action:0
msgid "Union"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters,evaluate_always:0
msgid "Always evaluate this filter before using it"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Add the result of following filters"
msgstr ""
#. module: web_advanced_filters
#: help:ir.filters,evaluate_before_negate:0
msgid ""
"This is necessary if this filter contains positive operatorson x2many fields"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0 view:ir.filters.combine.with.existing:0
msgid "Save"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Freeze filter"
msgstr ""
#. module: web_advanced_filters
#: help:ir.filters,evaluate_always:0
msgid ""
"This is necessary if this filter contains x2many fields with_auto_join "
"activated"
msgstr ""
#. module: web_advanced_filters
#: code:addons/web_advanced_filters/model/ir_filters.py:218
#, python-format
msgid "Testing %s"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0 view:ir.filters.combine.with.existing:0
msgid "or"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters,complement_filter_ids:0
msgid "Remove result of filters"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0
msgid ""
"{'readonly': ['|', ('union_filter_ids', '!=', [[6, False, []]]), "
"('complement_filter_ids', '!=', [[6, False, []]])]}"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters.combine.with.existing:0
msgid "Combine with existing filter"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters,evaluate_before_negate:0
msgid "Evaluate this filter before negating"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0
msgid ""
"Have this filter contain extly the records it currently contains, with no "
"changes in the future. Be careful, you can't undo this operation!"
msgstr ""
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:32
#, python-format
msgid "Advanced filters"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters,active:0
msgid "Active"
msgstr "Actief"
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:86
#, python-format
msgid "Marked records"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters,save_as_public:0
msgid "Share with all users"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters,is_frozen:0
msgid "Frozen"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,filter_id:0
msgid "Filter"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Test filter"
msgstr ""
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:58
#, python-format
msgid "Whole selection (criteria)"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters,domain_this:0
msgid "This filter's own domain"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Remove the result of following filters"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,context:0
msgid "Context"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,action:0
msgid "Action"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,model:0
msgid "Model"
msgstr ""
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:62
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:90
#, python-format
msgid "To new filter"
msgstr ""
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:78
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:106
#, python-format
msgid "Remove from existing filter"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters.combine.with.existing,domain:0
msgid "Domain"
msgstr ""
#. module: web_advanced_filters
#: field:ir.filters,union_filter_ids:0
msgid "Add result of filters"
msgstr ""
#. module: web_advanced_filters
#. openerp-web
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:70
#: code:addons/web_advanced_filters/static/src/js/web_advanced_filters.js:98
#, python-format
msgid "To existing filter"
msgstr ""
#. module: web_advanced_filters
#: model:ir.model,name:web_advanced_filters.model_ir_filters_combine_with_existing
msgid "Combine a selection with an existing filter"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0
msgid "Are you sure? You can't undo this operation!"
msgstr ""
#. module: web_advanced_filters
#: model:ir.model,name:web_advanced_filters.model_ir_filters
msgid "Filters"
msgstr ""
#. module: web_advanced_filters
#: view:ir.filters:0 view:ir.filters.combine.with.existing:0
msgid "Cancel"
msgstr "Verwijderen"
#. module: web_advanced_filters
#: selection:ir.filters.combine.with.existing,action:0
msgid "Complement"
msgstr ""

View File

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

View File

@@ -0,0 +1,215 @@
# Copyright 2014-2020 Therp BV <https://therp.nl>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
import logging
from odoo import _, api, fields, models
from odoo.tools.safe_eval import safe_eval
from odoo.osv import expression
_logger = logging.getLogger(__name__)
class IrFilters(models.Model):
_inherit = 'ir.filters'
_evaluate_before_negate = ['one2many', 'many2many']
is_frozen = fields.Boolean('Frozen', compute='_compute_is_frozen')
union_filter_ids = fields.Many2many(
'ir.filters', 'ir_filters_union_rel', 'left_filter_id',
'right_filter_id', 'Add result of filters',
domain=['|', ('active', '=', False), ('active', '=', True)],
)
complement_filter_ids = fields.Many2many(
'ir.filters', 'ir_filters_complement_rel', 'left_filter_id',
'right_filter_id', 'Remove result of filters',
domain=['|', ('active', '=', False), ('active', '=', True)],
)
domain = fields.Text(compute='_compute_domain', inverse='_inverse_domain')
domain_this = fields.Text('This filter\'s own domain', oldname='domain')
evaluate_before_negate = fields.Boolean(
'Evaluate this filter before negating',
compute='_compute_flags',
help='This is necessary if this filter contains positive operators'
'on x2many fields',
)
evaluate_always = fields.Boolean(
'Always evaluate this filter before using it',
compute='_compute_flags',
help='This is necessary if this filter contains x2many fields with'
'_auto_join activated',
)
save_as_public = fields.Boolean(
'Share with all users', default=False,
compute=lambda self: None, inverse=lambda self: None,
)
@api.multi
@api.depends('domain')
def _compute_is_frozen(self):
'''determine if this is fixed list of ids'''
for this in self:
try:
domain = safe_eval(this.domain)
except (SyntaxError, TypeError, ValueError, NameError):
domain = [expression.FALSE_LEAF]
this.is_frozen = (
len(domain) == 1 and
expression.is_leaf(domain[0]) and
domain[0][0] == 'id'
)
@api.multi
@api.depends(
'domain_this', 'union_filter_ids.domain',
'complement_filter_ids.domain',
)
def _compute_domain(self):
'''combine our domain with all domains to union/complement,
this works recursively'''
def eval_n(domain):
'''parse a domain and normalize it'''
try:
domain = safe_eval(domain)
except (SyntaxError, TypeError, ValueError, NameError):
domain = [expression.FALSE_LEAF]
return expression.normalize_domain(
domain or [expression.FALSE_LEAF])
for this in self:
if this.model_id not in self.env:
this.domain = '[]'
_logger.error('Unknown model %s used in filter', this.model_id)
continue
domain = eval_n(this.domain_this)
for u in this.union_filter_ids:
if u.evaluate_always:
matching_ids = self.env[u.model_id].search(
eval_n(u.domain),
).ids
domain = expression.OR([
domain,
[('id', 'in', matching_ids)],
])
else:
domain = expression.OR([domain, eval_n(u.domain)])
for c in this.complement_filter_ids:
if c.evaluate_before_negate:
matching_ids = self.env[c.model_id].search(
eval_n(c.domain),
).ids
domain = expression.AND([
domain,
[('id', 'not in', matching_ids)],
])
else:
domain = expression.AND([
domain,
['!'] + eval_n(c['domain']),
])
this.domain = str(expression.normalize_domain(domain))
@api.multi
def _inverse_domain(self):
for this in self:
this.domain_this = this.domain
@api.multi
@api.depends('domain')
def _compute_flags(self):
"""check if this filter contains references to x2many fields. If so,
then negation goes wrong in nearly all cases, so we evaluate the
filter and remove its resulting ids"""
for this in self:
this.evaluate_before_negate = False
this.evaluate_always = False
domain = expression.normalize_domain(
safe_eval(this.domain or 'False') or [expression.FALSE_LEAF]
)
for arg in domain:
current_model = self.env.get(this.model_id)
if current_model is None:
_logger.error(
'Unknown model %s used in filter', this.model_id)
continue
if not expression.is_leaf(arg) or not isinstance(arg[0], str):
continue
has_x2many = False
has_auto_join = False
for field_name in arg[0].split('.'):
if field_name in models.MAGIC_COLUMNS:
continue
field = current_model._fields[field_name]
has_x2many |= field.type in self._evaluate_before_negate
has_x2many |= bool(field.compute)
has_auto_join |= getattr(field, 'auto_join', False)
has_auto_join |= bool(field.compute)
if field.comodel_name:
current_model = self.env.get(field.comodel_name)
if current_model is None or has_x2many and has_auto_join:
break
this.evaluate_before_negate |= has_x2many
this.evaluate_always |= has_auto_join
if this.evaluate_before_negate and this.evaluate_always:
break
@api.model_create_multi
def create(self, vals_list):
for values in vals_list:
values.setdefault(
'user_id',
False if values.get('save_as_public') else self.env.user.id,
)
return super(IrFilters, self).create(vals_list)
@api.multi
def _evaluate(self):
self.ensure_one()
return self.env[self.model_id].with_context(
safe_eval(self.context)
).search(safe_eval(self.domain))
@api.multi
def button_save(self):
return {'type': 'ir.actions.act_window_close'}
@api.multi
def button_freeze(self):
'''evaluate the filter and write a fixed [('id', 'in', [])] domain'''
for this in self:
ids = this._evaluate().ids
removed_filters = \
this.union_filter_ids + this.complement_filter_ids
this.write({
'domain': str([('id', 'in', ids)]),
'union_filter_ids': [(6, 0, [])],
'complement_filter_ids': [(6, 0, [])],
})
# if we removed inactive filters which are orphaned now, delete
# them
if removed_filters:
self.env.cr.execute(
'''delete from ir_filters
where
not active and id in %s
and not exists (select right_filter_id
from ir_filters_union_rel where left_filter_id=id)
and not exists (select right_filter_id
from ir_filters_complement_rel where
left_filter_id=id)''',
(tuple(removed_filters.ids),)
)
@api.multi
def button_test(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': _('Testing %s') % self.name,
'res_model': self.model_id,
'domain': self.domain,
'view_type': 'form',
'view_mode': 'tree,form',
'context': {
'default_filter_id': self.id,
},
}

View File

@@ -0,0 +1 @@
* Holger Brunn <hbrunn@therp.nl>

View File

@@ -0,0 +1 @@
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.

View File

@@ -0,0 +1,8 @@
This addon allows users to apply set operations on filters: Remove or add
certain ids from/to a selection, but also to remove or add another filter's
outcome from/to a filter. This can be stacked, so the filter domain can be
arbitrarily complicated.
The math is hidden from the user as far as possible, in the hope it's still
user friendly.

View File

@@ -0,0 +1,2 @@
* client side tests
* consider renaming all instances of `Favorites` in core to `Filters` for consistency

View File

@@ -0,0 +1,5 @@
After this addon is installed, every search view shows a new menu `Advanced
filters`. Here the set operations can be applied as necessary.
Note the menu is only visible when there's search criteria or some record
selected.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,447 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" />
<title>Advanced filters</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="advanced-filters">
<h1 class="title">Advanced filters</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/web/tree/12.0/web_advanced_filter"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/web-12-0/web-12-0-web_advanced_filter"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/162/12.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>This addon allows users to apply set operations on filters: Remove or add
certain ids from/to a selection, but also to remove or add another filters
outcome from/to a filter. This can be stacked, so the filter domain can be
arbitrarily complicated.</p>
<p>The math is hidden from the user as far as possible, in the hope its still
user friendly.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="id1">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="id2">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id6">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="id7">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="id8">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id1">Usage</a></h1>
<p>After this addon is installed, every search view shows a new menu <cite>Advanced
filters</cite>. Here the set operations can be applied as necessary.</p>
<p>Note the menu is only visible when theres search criteria or some record
selected.</p>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#id2">Known issues / Roadmap</a></h1>
<ul class="simple">
<li>client side tests</li>
<li>consider renaming all instances of <cite>Favorites</cite> in core to <cite>Filters</cite> for consistency</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id3">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
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
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_advanced_filter%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id4">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id5">Authors</a></h2>
<ul class="simple">
<li>Therp BV</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id6">Contributors</a></h2>
<ul class="simple">
<li>Holger Brunn &lt;<a class="reference external" href="mailto:hbrunn&#64;therp.nl">hbrunn&#64;therp.nl</a>&gt;</li>
</ul>
</div>
<div class="section" id="other-credits">
<h2><a class="toc-backref" href="#id7">Other credits</a></h2>
<ul class="simple">
<li>Odoo Community Association: <a class="reference external" href="https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg">Icon</a>.</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id8">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>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.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/12.0/web_advanced_filter">OCA/web</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,7 @@
.o_advanced_filter_menu .o_menu_item[data-id$='-header'] a {
font-weight: bold;
cursor: default;
}
.o_advanced_filter_menu .o_menu_item[data-id$='-header'] a:hover {
background-color: inherit;
}

View File

@@ -0,0 +1,256 @@
// Copyright 2014-2020 Therp BV <https://therp.nl>
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
odoo.define('web_advanced_filter', function(require) {
var core = require('web.core'),
_t = core._t,
QWeb = core.qweb,
data_manager = require('web.data_manager'),
DropdownMenu = require('web.DropdownMenu'),
FavoriteMenu = require('web.FavoriteMenu'),
pyUtils = require('web.py_utils'),
SearchView = require('web.SearchView'),
AdvancedFiltersMenu = DropdownMenu.extend({
init: function (parent) {
this._super(
parent, this._advanced_filter_header(),
this._advanced_filter_items()
);
this.search_view = parent;
this.action_manager = this.search_view.getParent();
this.action_manager
.on('switch_view', this, this._advanced_filter_update);
this.action_manager
.on('selection_changed', this, this._advanced_filter_update);
this.action_manager.on('search', this, this._advanced_filter_update);
},
start: function () {
this._super.apply(this, arguments);
this.$menu.addClass('o_advanced_filter_menu');
this._advanced_filter_update();
},
_onItemClick: function (event) {
event.preventDefault();
event.stopPropagation();
var itemId = $(event.currentTarget).data('id'),
item = _.findWhere(this.items, {itemId: itemId});
if (item && item.callback) {
item.callback.apply(this, [item]);
}
},
_advanced_filter_header: function () {
return {
category: 'advanced_filters',
title: _t('Advanced filters'),
icon: 'fa fa-cubes',
symbol:
this.isMobile ? 'fa fa-chevron-right float-right mt4' : false,
};
},
_advanced_filter_items: function () {
return [
{
itemId: 'domain-header',
description: _t('Whole selection (criteria)'),
},
{
itemId: 'domain-new',
description: _t('To new filter'),
callback: this._advanced_filters_save_criteria,
},
{
itemId: 'domain-union',
description: _t('To existing filter'),
callback: this._advanced_filters_combine_with_existing.bind(
this, 'union', 'domain'
),
},
{
itemId: 'domain-complement',
description: _t('Remove from existing filter'),
callback: this._advanced_filters_combine_with_existing.bind(
this, 'complement', 'domain'
),
},
{
itemId: 'ids-header',
description: _t('Marked records'),
},
{
itemId: 'ids-new',
description: _t('To new filter'),
callback: this._advanced_filters_save_selection,
},
{
itemId: 'ids-union',
description: _t('To existing filter'),
callback: this._advanced_filters_combine_with_existing.bind(
this, 'union', 'ids'
),
},
{
itemId: 'ids-complement',
description: _t('Remove from existing filter'),
callback: this._advanced_filters_combine_with_existing.bind(
this, 'complement', 'ids'
),
},
]
},
_advanced_filters_save_criteria: function(item)
{
var search_data = this.search_view.build_search_data(),
search = pyUtils.eval_domains_and_contexts({
domains: search_data.domains,
contexts: search_data.contexts,
group_by_seq: search_data.groupbys || []
}),
ctx = search.context;
return this.do_action({
name: item.description,
type: 'ir.actions.act_window',
res_model: 'ir.filters',
views: [[false, 'form']],
target: 'new',
context: {
default_model_id: this.search_view.dataset._model.name,
default_domain: JSON.stringify(search.domain),
default_context: JSON.stringify(ctx),
default_user_id: false,
form_view_ref: 'web_advanced_filter.form_ir_filters_save_new',
},
},
{
on_close: this.search_view._reloadFilters.bind(this.search_view),
});
},
_advanced_filters_save_selection: function(item)
{
var controller = this.action_manager.getCurrentController(),
widget = controller.widget,
ids = widget.getSelectedIds && widget.getSelectedIds() || [];
this.do_action({
name: item.description,
type: 'ir.actions.act_window',
res_model: 'ir.filters',
views: [[false, 'form']],
target: 'new',
context: {
default_model_id: this.search_view.dataset._model.name,
default_domain: JSON.stringify([['id', 'in', ids]]),
default_context: JSON.stringify({}),
default_user_id: false,
form_view_ref: 'web_advanced_filter.form_ir_filters_save_new',
},
},
{
on_close: this.search_view._reloadFilters.bind(this.search_view),
});
},
_advanced_filters_combine_with_existing: function(action, type, item)
{
var search_data = this.search_view.build_search_data(),
search = pyUtils.eval_domains_and_contexts({
domains: search_data.domains,
contexts: search_data.contexts,
group_by_seq: search_data.groupbys || []
}),
domain = [], ctx = {};
switch(type)
{
case 'domain':
domain = search.domain;
ctx = search.context;
break;
case 'ids':
var controller = this.action_manager.getCurrentController(),
widget = controller.widget;
domain = [[
'id', 'in',
widget.getSelectedIds && widget.getSelectedIds() || [],
]]
ctx = {};
break;
}
this.do_action({
name: item.description,
type: 'ir.actions.act_window',
res_model: 'ir.filters.combine.with.existing',
views: [[false, 'form']],
target: 'new',
context: {
default_model: this.search_view.dataset._model.name,
default_domain: JSON.stringify(domain),
default_action: action,
default_context: JSON.stringify(ctx),
default_filter_id:
this.search_view.dataset.context.default_filter_id || false,
},
},
{
on_close: this.search_view._reloadFilters.bind(this.search_view),
});
},
_advanced_filter_update: function(event) {
var show_ids = false,
show_domain = this.search_view.query.length > 0;
switch ((event || {}).name) {
case 'selection_changed':
show_ids = event.data.selection.length > 0;
break;
case 'search':
show_domain = event.data.domains.length > 0;
break;
}
this.$menu.children('.o_menu_item[data-id^="ids-"]')
.toggle(show_ids);
this.$menu.children('.o_menu_item[data-id^="domain-"]')
.toggle(show_domain);
this.$dropdownReference.toggle(show_ids || show_domain);
},
});
SearchView.include({
start: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
self.advanced_filters_menu = self._createAdvancedFiltersMenu();
return self.advanced_filters_menu.insertBefore(
self.favorite_menu.$el
);
});
},
_createAdvancedFiltersMenu: function () {
return new AdvancedFiltersMenu(this);
},
_reloadFilters: function () {
if (this.options.disable_favorites) {
return;
}
var self = this;
data_manager._invalidate(
data_manager._cache.filters,
data_manager._gen_key(this.dataset.model, this.action.id)
);
this.loadFilters(this.dataset, this.action.id)
.then(function (filters) {
self.favorite_filters = filters;
var favorite_menu = new FavoriteMenu(
self, self.query, self.dataset.model, self.action,
self.favorite_filters
);
favorite_menu.replace(self.favorite_menu.$el);
self.favorite_menu = favorite_menu;
});
},
});
return {
AdvancedFiltersMenu: AdvancedFiltersMenu,
}
});

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="AdvancedFilter">
<div role="separator" class="dropdown-divider"/>
<button type="button" class="dropdown-item o_advanced_filter o_closed_menu" aria-expanded="false">Whole selection (criteria)</button>
<div class="dropdown-item-text o_advanced_filter-domain">
<button type="button" class="dropdown-item o_advanced_filter-add-new">To new filter</button>
<button type="button" class="dropdown-item o_advanced_filter-add-existing">To existing filter</button>
<button type="button" class="dropdown-item o_advanced_filter-remove">Remove from existing filter</button>
</div>
<button type="button" class="dropdown-item o_advanced_filter o_closed_menu" aria-expanded="false">Marked records</button>
<div class="dropdown-item-text o_advanced_filter-ids">
<button type="button" class="dropdown-item o_advanced_filter-add-new">To new filter</button>
<button type="button" class="dropdown-item o_advanced_filter-add-existing">To existing filter</button>
<button type="button" class="dropdown-item o_advanced_filter-remove">Remove from existing filter</button>
</div>
</t>
</templates>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="ir_filters_view_form" model="ir.ui.view">
<field name="model">ir.filters</field>
<field name="inherit_id" ref="base.ir_filters_view_form" />
<field name="arch" type="xml">
<sheet position="before">
<header>
<button type="object" string="Test filter" name="button_test" class="btn-primary" />
<button type="object" string="Freeze filter" name="button_freeze" attrs="{'invisible': [('is_frozen', '=', True)]}" help="Have this filter contain extly the records it currently contains, with no changes in the future. Be careful, you can&apos;t undo this operation!" confirm="Are you sure? You can&apos;t undo this operation!" />
</header>
</sheet>
<group position="after">
<field name="is_frozen" invisible="True" />
<field name="id" invisible="True" />
<group string="Add the result of following filters">
<field name="union_filter_ids" nolabel="1" domain="[('user_id', 'in', [False, uid]), ('id', '!=', id), ('model_id', '=', model_id)]" />
</group>
<group string="Remove the result of following filters">
<field name="complement_filter_ids" nolabel="1" domain="[('user_id', 'in', [False, uid]),('id', '!=', id), ('model_id', '=', model_id)]" />
</group>
</group>
<field name="domain" position="attributes">
<attribute name="attrs">{'readonly': ['|', ('union_filter_ids', '!=', [[6, False, []]]), ('complement_filter_ids', '!=', [[6, False, []]])]}</attribute>
</field>
<field name="domain" position="after">
<field name="domain_this" attrs="{'invisible': [('union_filter_ids', '=', [[6, False, []]]), ('complement_filter_ids', '=', [[6, False, []]])]}" />
</field>
</field>
</record>
<record id="form_ir_filters_save_new" model="ir.ui.view">
<field name="model">ir.filters</field>
<field name="priority">999</field>
<field name="arch" type="xml">
<form string="Save filter" version="7.0">
<group>
<field name="name" />
<field name="is_default"/>
<field name="save_as_public" />
</group>
<footer>
<button class="oe_highlight" type="object" name="button_save" string="Save" />
or
<button class="oe_link" special="cancel" string="Cancel" />
</footer>
</form>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<template id="assets_backend" name="web_advanced_filter assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/web_advanced_filter/static/src/js/web_advanced_filter.js"></script>
<link rel="stylesheet" href="/web_advanced_filter/static/src/css/web_advanced_filter.css"/>
</xpath>
</template>
</data>
</odoo>

View File

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

View File

@@ -0,0 +1,68 @@
# Copyright 2014 Therp BV <https://therp.nl>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
import time
import json
from odoo import api, fields, models
from odoo.osv import expression
from odoo.tools.safe_eval import safe_eval
class IrFiltersCombineWithExisting(models.TransientModel):
_name = 'ir.filters.combine.with.existing'
_description = 'Combine a selection with an existing filter'
action = fields.Selection(
[('union', 'Union'), ('complement', 'Complement')],
'Action', required=True,
)
domain = fields.Char('Domain', required=True)
context = fields.Char('Context', required=True, default='{}')
model = fields.Char('Model', required=True)
filter_id = fields.Many2one('ir.filters', 'Filter', required=True)
@api.multi
def button_save(self):
self.ensure_one()
this = self
domain = json.loads(this.domain)
is_frozen = (len(domain) == 1 and
expression.is_leaf(domain[0]) and
domain[0][0] == 'id')
if this.action == 'union':
if is_frozen and this.filter_id.is_frozen:
domain[0][2] = list(set(domain[0][2]).union(
set(safe_eval(this.filter_id.domain)[0][2])))
this.filter_id.write({'domain': str(domain)})
else:
this.filter_id.write({
'union_filter_ids': [(0, 0, {
'name': '%s_%s_%d' % (
this.filter_id.name, 'add', time.time()),
'active': False,
'domain': str(domain),
'context': this.context,
'model_id': this.model,
'user_id': this.filter_id.user_id.id or False,
})],
})
elif this.action == 'complement':
if is_frozen and this.filter_id.is_frozen:
complement_set = set(safe_eval(this.filter_id.domain)[0][2])
domain[0][2] = list(
complement_set.difference(set(domain[0][2])))
this.filter_id.write({'domain': str(domain)})
else:
this.filter_id.write({
'complement_filter_ids': [(0, 0, {
'name': '%s_%s_%d' % (
this.filter_id.name, 'remove', time.time()),
'active': False,
'domain': str(domain),
'context': this.context,
'model_id': this.model,
'user_id': this.filter_id.user_id.id or False,
})],
})
return {'type': 'ir.actions.act_window_close'}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="form_ir_filters_combine_with_existing" model="ir.ui.view">
<field name="model">ir.filters.combine.with.existing</field>
<field name="arch" type="xml">
<form string="Combine with existing filter" version="7.0">
<group>
<field name="model" invisible="1" />
<field name="filter_id" domain="[('model_id', '=', model)]" context="{'default_model_id': model}" />
</group>
<footer>
<button class="btn-primary" type="object" name="button_save" string="Save" />
<button class="btn-secondary" special="cancel" string="Cancel" />
</footer>
</form>
</field>
</record>
</odoo>