Browse Source

[ADD] advanced_filters

pull/29/head
Holger Brunn 11 years ago
parent
commit
37125e826b
  1. 22
      advanced_filters/__init__.py
  2. 78
      advanced_filters/__openerp__.py
  3. 21
      advanced_filters/model/__init__.py
  4. 149
      advanced_filters/model/ir_filters.py
  5. 14
      advanced_filters/static/src/css/advanced_filters.css
  6. BIN
      advanced_filters/static/src/img/icon.png
  7. 209
      advanced_filters/static/src/js/advanced_filters.js
  8. 44
      advanced_filters/view/ir_filters.xml
  9. 21
      advanced_filters/wizard/__init__.py
  10. 87
      advanced_filters/wizard/ir_filters_combine_with_existing.py
  11. 21
      advanced_filters/wizard/ir_filters_combine_with_existing.xml

22
advanced_filters/__init__.py

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import model
from . import wizard

78
advanced_filters/__openerp__.py

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
"name": "Advanced filters",
"version": "1.0",
"author": "Therp BV",
"license": "AGPL-3",
"complexity": "normal",
"description": """
Introduction
------------
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.
Usage
-----
After this addon is installed, every list view shows a new menu 'Advanced
filters'. Here the set operations can be applied as necessary.
Caution
-------
Deinstalling this module will leave you with filters with empty domains. Use
this query before uninstalling to avoid that:
``alter table ir_filters rename domain_this to domain``
""",
"category": "Tools",
"depends": [
'base',
'web',
],
"data": [
"wizard/ir_filters_combine_with_existing.xml",
"view/ir_filters.xml",
],
"js": [
'static/src/js/advanced_filters.js',
],
"css": [
'static/src/css/advanced_filters.css',
],
"qweb": [
],
"test": [
],
"auto_install": False,
"installable": True,
"application": False,
"external_dependencies": {
'python': [],
},
}

21
advanced_filters/model/__init__.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import ir_filters

149
advanced_filters/model/ir_filters.py

@ -0,0 +1,149 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import itertools
from openerp.osv.orm import Model
from openerp.osv import fields, expression
from openerp.tools.safe_eval import const_eval
from openerp.tools.translate import _
class IrFilters(Model):
_inherit = 'ir.filters'
def _is_frozen_get(self, cr, uid, ids, field_name, args, context=None):
'''determine if this is fixed list of ids'''
result = {}
for this in self.browse(cr, uid, ids, context=context):
domain = const_eval(this.domain)
result[this.id] = (len(domain) == 1 and
expression.is_leaf(domain[0]) and
domain[0][0] == 'id')
return result
def _domain_get(self, cr, uid, ids, field_name, args, context=None):
'''combine our domain with all domains to union/complement,
this works recursively'''
def eval_n(domain):
'''parse a domain and normalize it'''
return expression.normalize_domain(
const_eval(domain) or [expression.FALSE_LEAF])
result = {}
for this in self.read(
cr, uid, ids,
['domain_this', 'union_filter_ids', 'complement_filter_ids'],
context=context):
domain = eval_n(this['domain_this'])
domain = expression.OR(
[domain] +
[eval_n(u['domain']) for u in self.read(
cr, uid, this['union_filter_ids'], ['domain'],
context=context)])
for c in self.read(cr, uid, this['complement_filter_ids'],
['domain'], context=context):
domain = expression.AND([
domain,
['!'] + eval_n(c['domain'])])
result[this['id']] = str(domain)
return result
def _domain_set(self, cr, uid, ids, field_name, field_value, args,
context=None):
self.write(cr, uid, ids, {'domain_this': field_value})
_columns = {
'is_frozen': fields.function(
_is_frozen_get, type='boolean', string='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)]),
'active': fields.boolean('Active'),
'domain': fields.function(
_domain_get, type='text', string='Domain',
fnct_inv=_domain_set),
'domain_this': fields.text(
'This filter\'s own domain', oldname='domain'),
}
_defaults = {
'active': True,
}
def _evaluate(self, cr, uid, ids, context=None):
assert len(ids) == 1
this = self.browse(cr, uid, ids[0], context=context)
return self.pool[this.model_id].search(
cr, uid, const_eval(this.domain), context=const_eval(this.context))
def button_save(self, cr, uid, ids, context=None):
return {'type': 'ir.actions.act_window.close'}
def button_freeze(self, cr, uid, ids, context=None):
'''evaluate the filter and write a fixed [('ids', 'in', [])] domain'''
for this in self.browse(cr, uid, ids, context=context):
ids = this._evaluate()
removed_filter_ids = [f.id for f in itertools.chain(
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
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_filter_ids),))
def button_test(self, cr, uid, ids, context=None):
for this in self.browse(cr, uid, ids, context=None):
return {
'type': 'ir.actions.act_window',
'name': _('Testing %s') % this.name,
'res_model': this.model_id,
'domain': this.domain,
'view_type': 'form',
'view_mode': 'tree',
'context': {
'default_filter_id': this.id,
},
}
def _auto_init(self, cr, context=None):
cr.execute(
'SELECT count(attname) FROM pg_attribute '
'WHERE attrelid = '
'( SELECT oid FROM pg_class WHERE relname = %s) '
'AND attname = %s', (self._table, 'domain_this'))
if not cr.fetchone()[0]:
cr.execute(
'ALTER table %s RENAME domain TO domain_this' % self._table)
return super(IrFilters, self)._auto_init(cr, context=context)

14
advanced_filters/static/src/css/advanced_filters.css

@ -0,0 +1,14 @@
li.oe_advanced_filters_header
{
font-weight: bold;
}
.openerp .oe_dropdown_menu > li.oe_advanced_filters_header:hover
{
background-color: inherit;
background-image: inherit;
box-shadow: none;
}
.openerp .oe_dropdown_menu > li.oe_advanced_filters_header a:hover
{
cursor: default !important;
}

BIN
advanced_filters/static/src/img/icon.png

After

Width: 128  |  Height: 128  |  Size: 16 KiB

209
advanced_filters/static/src/js/advanced_filters.js

@ -0,0 +1,209 @@
//-*- coding: utf-8 -*-
//############################################################################
//
// OpenERP, Open Source Management Solution
// This module copyright (C) 2014 Therp BV (<http://therp.nl>).
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
//############################################################################
openerp.advanced_filters = function(instance)
{
var _t = instance.web._t;
instance.web.Sidebar.include({
init: function()
{
var result = this._super.apply(this, arguments);
this.sections.push({
'name': 'advanced_filters',
'label': _t('Advanced filters'),
});
this.items.advanced_filters = [];
return result;
},
});
instance.web.ListView.include({
do_select: function (ids, records)
{
var result = this._super(this, arguments);
if(this.sidebar)
{
this.sidebar.$el.show();
this.sidebar.$el.children().children().each(function(i, e)
{
$e = jQuery(e)
if($e.find('li.oe_advanced_filters_header').length)
{
$e.find('a[data-index="0"],a[data-index="1"],' +
'a[data-index="2"],a[data-index="3"]')
.parent().toggle(ids.length);
}
else
{
$e.toggle(ids.length);
}
});
}
return result;
},
load_list: function(data)
{
var result = this._super.apply(this, arguments),
self = this;
if(!this.sidebar || this.sidebar.items.advanced_filters.length)
{
return result;
}
this.sidebar.add_items(
'advanced_filters',
[
{
label: _t('Marked records'),
classname: 'oe_advanced_filters_header',
},
{
label: _t('To new selection'),
callback: function ()
{
self.advanced_filters_save_selection.apply(
self, arguments);
},
},
{
label: _t('To existing selection'),
callback: function (item)
{
self.advanced_filters_combine_with_existing.apply(
self, ['union', 'ids', item]);
},
},
{
label: _t('Remove from existing selection'),
callback: function (item)
{
self.advanced_filters_combine_with_existing.apply(
self, ['complement', 'ids', item]);
},
},
{
label: _t('Whole result set'),
classname: 'oe_advanced_filters_header',
},
{
label: _t('To existing selection'),
callback: function (item)
{
self.advanced_filters_combine_with_existing.apply(
self, ['union', 'domain', item]);
},
},
{
label: _t('Remove from existing selection'),
callback: function (item)
{
self.advanced_filters_combine_with_existing.apply(
self, ['complement', 'domain', item]);
},
},
]
);
this.do_select([], []);
return result;
},
advanced_filters_save_selection: function(item)
{
var self = this;
this.do_action({
name: item.label,
type: 'ir.actions.act_window',
res_model: 'ir.filters',
views: [[false, 'form']],
target: 'new',
context: {
default_model_id: this.dataset._model.name,
default_domain: JSON.stringify(
[
['id', 'in', this.groups.get_selection().ids],
]
),
default_context: JSON.stringify({}),
form_view_ref: 'advanced_filters.form_ir_filters_save_new',
},
},
{
on_close: function()
{
self.ViewManager.setup_search_view(
self.ViewManager.searchview.view_id,
self.ViewManager.searchview.defaults);
},
});
},
advanced_filters_combine_with_existing: function(action, type, item)
{
var search = this.ViewManager.searchview.build_search_data(),
self = this;
instance.web.pyeval.eval_domains_and_contexts({
domains: search.domains,
contexts: search.contexts,
group_by_seq: search.groupbys || []
}).done(function(search)
{
var domain = [], ctx = {};
switch(type)
{
case 'domain':
domain = search.domain;
ctx = search.context;
_(_.keys(instance.session.user_context)).each(
function (key) {delete ctx[key]});
break;
case 'ids':
domain = [
['id', 'in', self.groups.get_selection().ids],
]
ctx = {};
break;
}
self.do_action({
name: item.label,
type: 'ir.actions.act_window',
res_model: 'ir.filters.combine.with.existing',
views: [[false, 'form']],
target: 'new',
context: _.extend({
default_model: self.dataset._model.name,
default_domain: JSON.stringify(domain),
default_action: action,
default_context: JSON.stringify(ctx),
},
self.dataset.context.default_filter_id ? {
default_filter_id:
self.dataset.context.default_filter_id,
} : {}),
},
{
on_close: function()
{
self.ViewManager.setup_search_view(
self.ViewManager.searchview.view_id,
self.ViewManager.searchview.defaults);
},
});
});
},
});
}

44
advanced_filters/view/ir_filters.xml

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<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="oe_highlight" />
<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>
</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"/>
</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>
</data>
</openerp>

21
advanced_filters/wizard/__init__.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import ir_filters_combine_with_existing

87
advanced_filters/wizard/ir_filters_combine_with_existing.py

@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import time
from openerp.osv.orm import TransientModel
from openerp.osv import fields, expression
from openerp.tools.safe_eval import const_eval
class IrFiltersCombineWithExisting(TransientModel):
_name = 'ir.filters.combine.with.existing'
_description = 'Combine a selection with an existing filter'
_columns = {
'action': fields.selection(
[('union', 'Union'), ('complement', 'Complement')],
'Action', required=True),
'domain': fields.char('Domain', required=True),
'context': fields.char('Context', required=True),
'model': fields.char('Model', required=True),
'filter_id': fields.many2one('ir.filters', 'Filter', required=True),
}
def button_save(self, cr, uid, ids, context=None):
assert len(ids) == 1
this = self.browse(cr, uid, ids[0], context=context)
domain = const_eval(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(const_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': uid,
})],
})
elif this.action == 'complement':
if is_frozen and this.filter_id.is_frozen:
complement_set = set(const_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': uid,
})],
})
return {'type': 'ir.actions.act_window.close'}

21
advanced_filters/wizard/ir_filters_combine_with_existing.xml

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<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)]" />
</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>
</data>
</openerp>
Loading…
Cancel
Save