Skip to content

Commit

Permalink
[IMP] base_custom_filter: add search feature
Browse files Browse the repository at this point in the history
  • Loading branch information
AungKoKoLin1997 committed Sep 15, 2023
1 parent f5faf0a commit d5a06bc
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 64 deletions.
41 changes: 27 additions & 14 deletions base_custom_filter/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ Add custom filters in standard filters and group by dropdowns

|badge1| |badge2| |badge3| |badge4| |badge5|

This module allows to define custom filters to be shown under the standard
filters and group by menus of a model's search view.
This module enables the definition of bespoke searches within a model's search view, in
addition to permitting the creation of custom filters that will be displayed beneath the
standard filters, as well as within the group-by menus of a model's search view.

**Table of contents**

Expand All @@ -40,20 +41,28 @@ Configuration
=============

#. Go to *Settings > Custom Filters*.
#. Create a new Custom Filter, and define following information:
#. Create a record assigning model, type (search/filter/groupby) and necessary attributes.
Available fields and corresponding attributes (in brackets) for each type are as follows:

* The **Model** for which you are defining the filter.
* The **Type** depending on whether you want to add a filter or a grouping by
by a field.
* The **Domain** that will be applied with the filter.
* The **Group By Field** used to perform the group by.
* The **Group** to have filters under the same separator.
Search:

#. You can reorder records from the list view with the arrow handle. This will
determine the order in which they appear in the filters/groupby menu.
#. A separator is added between each custom filter added. You can create Custom
Filter Groups to group filters that you want to have displayed under the same
separator.
* Search Field (``name``)
* Filter Domain (``filter_domain``)
* Domain (``domain``)
* User Groups (``groups``)

Filter:

* Domain (``domain``)
* User Groups (``groups``)

Group By:

* Group By Field (field to be assigned to ``group_by`` context)
* User Groups (``groups``)

See `the official documentation <https://www.odoo.com/documentation/16.0/developer/reference/backend/views.html#search>`_ for the definition of each attribute.
Additionally, filter and group-by records can be respectively grouped together with "Group" assignment (there will be a separator in between groups).

Usage
=====
Expand Down Expand Up @@ -89,6 +98,10 @@ Contributors

* `Ashish Hirpara <https://www.ashish-hirpara.com>`

* `Quartile <https://www.quartile.co>`__:

* Aung Ko Ko Lin

Maintainers
~~~~~~~~~~~

Expand Down
37 changes: 37 additions & 0 deletions base_custom_filter/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@
class Base(models.AbstractModel):
_inherit = "base"

@api.model
def _user_has_access_to_item(self, item):
if not item.group_ids:
return True
return bool(set(self.env.user.groups_id.ids) & set(item.group_ids.ids))

@api.model
def _add_grouped_filters(self, res, custom_filters):
arch = etree.fromstring(res["arch"])
node = arch.xpath("//search/filter[last()]")
if node:
node[0].addnext(etree.Element("separator"))
for custom_filter in custom_filters:
if not self._user_has_access_to_item(custom_filter):
continue
node = arch.xpath("//search/separator[last()]")
if node:
elem = etree.Element(
Expand All @@ -38,6 +46,8 @@ def _add_grouped_groupby(self, res, custom_groupbys):
if node:
node[0].addnext(etree.Element("separator"))
for custom_groupby in custom_groupbys:
if not self._user_has_access_to_item(custom_groupby):
continue
node = arch.xpath("//group/separator[last()]")
if node:
elem = etree.Element(
Expand All @@ -54,6 +64,24 @@ def _add_grouped_groupby(self, res, custom_groupbys):
res["arch"] = etree.tostring(arch)
return res

@api.model
def _add_search_field(self, res, search_fields):
xml_arch = etree.fromstring(res["arch"])
for search_field in search_fields:
if not self._user_has_access_to_item(search_field):
continue
new_field = etree.Element(
"field",
name=search_field.search_field_id.sudo().name,
)
if search_field.filter_domain:
new_field.set("filter_domain", search_field.filter_domain)
if search_field.domain:
new_field.set("domain", search_field.domain)
xml_arch.append(new_field)
res["arch"] = etree.tostring(xml_arch)
return res

@api.model
def get_view(self, view_id=None, view_type="form", **options):
"""Add filters in search views."""
Expand Down Expand Up @@ -89,6 +117,13 @@ def get_view(self, view_id=None, view_type="form", **options):
],
order="sequence desc",
)
search_fields = self.env["ir.filters"].search(
[
("model_id", "=", res.get("model")),
("type", "=", "search"),
],
order="sequence desc",
)
# Add filter type
if filter_groups:
for filter_group in filter_groups:
Expand All @@ -107,4 +142,6 @@ def get_view(self, view_id=None, view_type="form", **options):
if groupbys_no_group:
for groupby_no_group in groupbys_no_group:
res = self._add_grouped_groupby(res, groupby_no_group)
if search_fields:
res = self._add_search_field(res, search_fields)
return res
10 changes: 10 additions & 0 deletions base_custom_filter/models/ir_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class IrFilters(models.Model):
def _selection_type(self):
return [
("favorite", "Favorite"),
("search", "Search"),
("filter", "Standard Filter"),
("groupby", "Standard Group By"),
]
Expand All @@ -23,11 +24,20 @@ def _selection_type(self):
required=True,
default="favorite",
)
search_field_id = fields.Many2one(
comodel_name="ir.model.fields",
ondelete="cascade",
)
groupby_field = fields.Many2one(
comodel_name="ir.model.fields",
string="Group By Field",
ondelete="cascade",
)
filter_domain = fields.Text(
help="""Enter a filter domain expression if necessary.
Example: [('default_code', 'ilike', self)]"""
)
group_ids = fields.Many2many("res.groups", string="User Groups")
group_id = fields.Many2one(comodel_name="ir.filters.group", string="Filter Group")

@api.model
Expand Down
36 changes: 22 additions & 14 deletions base_custom_filter/readme/CONFIGURE.rst
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
#. Go to *Settings > Custom Filters*.
#. Create a new Custom Filter, and define following information:

* The **Model** for which you are defining the filter.
* The **Type** depending on whether you want to add a filter or a grouping by
by a field.
* The **Domain** that will be applied with the filter.
* The **Group By Field** used to perform the group by.
* The **Group** to have filters under the same separator.

#. You can reorder records from the list view with the arrow handle. This will
determine the order in which they appear in the filters/groupby menu.
#. A separator is added between each custom filter added. You can create Custom
Filter Groups to group filters that you want to have displayed under the same
separator.
#. Create a record assigning model, type (search/filter/groupby) and necessary attributes.
Available fields and corresponding attributes (in brackets) for each type are as follows:

Search:

* Search Field (``name``)
* Filter Domain (``filter_domain``)
* Domain (``domain``)
* User Groups (``groups``)

Filter:

* Domain (``domain``)
* User Groups (``groups``)

Group By:

* Group By Field (field to be assigned to ``group_by`` context)
* User Groups (``groups``)

See `the official documentation <https://www.odoo.com/documentation/16.0/developer/reference/backend/views.html#search>`_ for the definition of each attribute.
Additionally, filter and group-by records can be respectively grouped together with "Group" assignment (there will be a separator in between groups).
4 changes: 4 additions & 0 deletions base_custom_filter/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
* Jordi Masvidal

* `Ashish Hirpara <https://www.ashish-hirpara.com>`

* `Quartile <https://www.quartile.co>`__:

* Aung Ko Ko Lin
5 changes: 3 additions & 2 deletions base_custom_filter/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
This module allows to define custom filters to be shown under the standard
filters and group by menus of a model's search view.
This module enables the definition of bespoke searches within a model's search view, in
addition to permitting the creation of custom filters that will be displayed beneath the
standard filters, as well as within the group-by menus of a model's search view.
19 changes: 14 additions & 5 deletions base_custom_filter/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -398,12 +398,17 @@ <h1><a class="toc-backref" href="#toc-entry-1">Configuration</a></h1>
<li>The <strong>Group By Field</strong> used to perform the group by.</li>
<li>The <strong>Group</strong> to have filters under the same separator.</li>
</ul>
</blockquote>
<p>Group By:</p>
<blockquote>
<ul class="simple">
<li>Group By Field (field to be assigned to <tt class="docutils literal">group_by</tt> context)</li>
<li>User Groups (<tt class="docutils literal">groups</tt>)</li>
</ul>
</blockquote>
<p>See <a class="reference external" href="https://www.odoo.com/documentation/16.0/developer/reference/backend/views.html#search">the official documentation</a> for the definition of each attribute.
Additionally, filter and group-by records can be respectively grouped together with “Group” assignment (there will be a separator in between groups).</p>
</li>
<li>You can reorder records from the list view with the arrow handle. This will
determine the order in which they appear in the filters/groupby menu.</li>
<li>A separator is added between each custom filter added. You can create Custom
Filter Groups to group filters that you want to have displayed under the same
separator.</li>
</ol>
</div>
<div class="section" id="usage">
Expand Down Expand Up @@ -438,6 +443,10 @@ <h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
</ul>
</li>
<li><cite>Ashish Hirpara &lt;https://www.ashish-hirpara.com&gt;</cite></li>
<li><a class="reference external" href="https://www.quartile.co">Quartile</a>:<ul>
<li>Aung Ko Ko Lin</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">
Expand Down
94 changes: 66 additions & 28 deletions base_custom_filter/tests/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,78 @@
@tagged("post_install", "-at_install")
class Test(TransactionCase):
@classmethod
def setUpClass(cls, chart_template_ref=None):
def setUpClass(cls):
super().setUpClass()
filters_obj = cls.env["ir.filters"]
filters_group = Form(filters_obj)
cls.filters_group_obj = cls.env["ir.filters.group"]
cls.filters_obj = cls.env["ir.filters"]
filters_group = Form(cls.filters_obj)
filters_group.name = "Test No groupby group"
filters_group.type = "groupby"
filters_group.model_id = "ir.filters.group"
filters_group.groupby_field = cls.env.ref(
"base_custom_filter.field_ir_filters_group__name"
)
cls.filters_groupby = filters_group.save()
filters_group = filters_group.save()

filters_group = Form(filters_obj)
filters_group = Form(cls.filters_obj)
filters_group.name = "Test No filters group"
filters_group.type = "filter"
filters_group.model_id = "ir.filters.group"
filters_group.domain = '[["id","=",1]]'
cls.filters_filter = filters_group.save()
filters_group = filters_group.save()

filters_group = Form(filters_obj)
filters_group.name = "Test favorite"
filters_group.type = "favorite"
filters_group.model_id = "ir.filters.group"
filters_group.domain = '[["id","=",1]]'
cls.filters_favorite = filters_group.save()
def test_get_view_content_search(self):
with Form(self.filters_obj) as filters_search:
filters_search.name = "Test Search Field"
filters_search.type = "search"
filters_search.model_id = "ir.filters.group"
filters_search.search_field_id = self.env.ref(
"base_custom_filter.field_ir_filters_group__display_name"
)
filters_search.filter_domain = "['display_name', 'ilike', self]"
filters_search.group_ids.add(self.env.ref("base.group_system"))

def test_filters_favorite(self):
res = self.env["ir.filters"].get_filters("ir.filters.group")
res_ids = [item["id"] for item in res]
self.assertNotIn(self.filters_groupby.id, res_ids)
self.assertNotIn(self.filters_filter.id, res_ids)
self.assertIn(self.filters_favorite.id, res_ids)
filter_search = self.filters_obj.search([("name", "=", "Test Search Field")])
self.assertEqual(filter_search.name, "Test Search Field")

# Test get_view() content
view_dict = self.filters_group_obj.get_view(view_type="search")
view_content = view_dict.get("arch", b"").decode("utf-8")
search_string = '<field name="display_name" filter_domain="[\'display_name\', \'ilike\', self]" domain="[]"/>' # noqa: B950
self.assertIn(
search_string, view_content, "The string is not in the returned view"
)

def test_sale_order_line(self):
filters_group_obj = self.env["ir.filters.group"]
filters_obj = self.env["ir.filters"]
filters_obj.unlink()
filters_group_obj.unlink()
with Form(filters_group_obj) as filters_group:
def test_get_view_content_filter(self):
with Form(self.filters_group_obj) as filters_group:
filters_group.name = "Test filters group"
filters_group.type = "filter"
filters_group.model_id = "ir.filters.group"
with filters_group.filter_ids.new() as line:
line.name = "Test filter line"
line.domain = '[["id","=",1]]'

filter_group = filters_group_obj.search([("name", "=", "Test filters group")])
filter_group = self.filters_group_obj.search(
[("name", "=", "Test filters group")]
)
self.assertEqual(filter_group.name, "Test filters group")

with Form(filters_group_obj) as filters_group:
view_dict = self.filters_group_obj.get_view(view_type="search")
view_content = view_dict.get("arch", b"").decode("utf-8")
filter_name = "ir_custom_filter_" + str(
self.filters_obj.search([("name", "=", "Test filter line")]).id
)
filter_string = '<filter name="{}" string="Test filter line" domain="[[&quot;id&quot;,&quot;=&quot;,1]]"/>'.format( # noqa: B950
filter_name
)
self.assertIn(
filter_string,
view_content,
"The string is not in the returned view",
)

def test_get_view_content_groupby(self):
with Form(self.filters_group_obj) as filters_group:
filters_group.name = "Test groupby group"
filters_group.type = "groupby"
filters_group.model_id = "ir.filters.group"
Expand All @@ -63,5 +85,21 @@ def test_sale_order_line(self):
"base_custom_filter.field_ir_filters_group__name"
)

filters_group_obj.get_view(view_type="search")
filter_group.unlink()
filter_group = self.filters_group_obj.search(
[("name", "=", "Test groupby group")]
)
self.assertEqual(filter_group.name, "Test groupby group")

view_dict = self.filters_group_obj.get_view(view_type="search")
view_content = view_dict.get("arch", b"").decode("utf-8")
filter_name = "ir_custom_filter_" + str(
self.filters_obj.search([("name", "=", "Test groupby line")]).id
)
groupby_string = '<filter name="{}" string="Test groupby line" context="{{\'group_by\': \'name\'}}"/>'.format( # noqa: B950
filter_name
)
self.assertIn(
groupby_string,
view_content,
"The string is not in the returned view",
)
Loading

0 comments on commit d5a06bc

Please sign in to comment.