Skip to content

Commit b4806e5

Browse files
authored
Add the ability to filter by empty values with "EMPTY" magic value #296 (#487)
Signed-off-by: Thomas Druez <[email protected]>
1 parent 3d28bf5 commit b4806e5

File tree

5 files changed

+93
-1
lines changed

5 files changed

+93
-1
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ v31.0.0 (next)
6868
https://github.com/nexB/scancode.io/issues/164
6969
https://github.com/nexB/scancode.io/issues/464
7070

71+
- Add the ability to filter by empty and none values providing the "EMPTY" magic value
72+
to any filters.
73+
https://github.com/nexB/scancode.io/issues/296
74+
7175
v30.2.0 (2021-12-17)
7276
--------------------
7377

scanpipe/filters.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
# Visit https://github.com/nexB/scancode.io for support and download.
2222

2323
from django.apps import apps
24+
from django.core.validators import EMPTY_VALUES
2425
from django.db import models
2526

2627
import django_filters
@@ -36,6 +37,8 @@
3637

3738

3839
class FilterSetUtilsMixin:
40+
empty_value = "EMPTY"
41+
3942
@staticmethod
4043
def remove_field_from_query_dict(query_dict, field_name, remove_value=None):
4144
"""
@@ -88,6 +91,20 @@ def get_filters_breadcrumb(self):
8891
def verbose_name_plural(cls):
8992
return cls.Meta.model._meta.verbose_name_plural
9093

94+
def filter_queryset(self, queryset):
95+
"""
96+
Adds the ability to filter by empty and none values providing the "magic"
97+
`empty_value` to any filters.
98+
"""
99+
100+
for name, value in self.form.cleaned_data.items():
101+
if value == self.empty_value:
102+
queryset = queryset.filter(**{f"{name}__in": EMPTY_VALUES})
103+
else:
104+
queryset = self.filters[name].filter(queryset, value)
105+
106+
return queryset
107+
91108

92109
class BulmaLinkWidget(LinkWidget):
93110
"""

scanpipe/templates/scanpipe/includes/run_modal_content.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
Resources status
9393
</p>
9494
{% for status, count in status_summary.items %}
95-
<a class="panel-block" href="{% url 'project_resources' run.project.uuid %}?status={{ status }}" target="_blank">
95+
<a class="panel-block" href="{% url 'project_resources' run.project.uuid %}?status={{ status|default:'EMPTY' }}" target="_blank">
9696
{% if status %}{{ status }}{% else %}<i>(no status)</i>{% endif %}: {{ count }}
9797
</a>
9898
{% endfor %}

scanpipe/templates/scanpipe/project_detail.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ <h3 class="title is-4 has-text-centered mb-3">
231231
// Retrieve the data ("name" value for filtering) from the .bb-tooltip section
232232
let name = document.querySelector(`#${base_chart_div.id} .bb-tooltip .name`).textContent;
233233
if (base_url && field) {
234+
if (name === "(No value detected)") name = "EMPTY";
234235
let full_url = `${base_url}?${field}=${name}`;
235236
if (in_package) full_url += `&in_package=${in_package}`;
236237
if (event.ctrlKey || event.metaKey) window.open(full_url, '_blank');

scanpipe/tests/test_filters.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
#
3+
# http://nexb.com and https://github.com/nexB/scancode.io
4+
# The ScanCode.io software is licensed under the Apache License version 2.0.
5+
# Data generated with ScanCode.io is provided as-is without warranties.
6+
# ScanCode is a trademark of nexB Inc.
7+
#
8+
# You may not use this software except in compliance with the License.
9+
# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
10+
# Unless required by applicable law or agreed to in writing, software distributed
11+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
12+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
13+
# specific language governing permissions and limitations under the License.
14+
#
15+
# Data Generated with ScanCode.io is provided on an "AS IS" BASIS, WITHOUT WARRANTIES
16+
# OR CONDITIONS OF ANY KIND, either express or implied. No content created from
17+
# ScanCode.io should be considered or used as legal advice. Consult an Attorney
18+
# for any legal advice.
19+
#
20+
# ScanCode.io is a free software code scanning tool from nexB Inc. and others.
21+
# Visit https://github.com/nexB/scancode.io for support and download.
22+
23+
from django.test import TestCase
24+
25+
from scanpipe.filters import ResourceFilterSet
26+
from scanpipe.models import CodebaseResource
27+
from scanpipe.models import Project
28+
29+
30+
class ScanPipeFiltersTest(TestCase):
31+
def setUp(self):
32+
self.project1 = Project.objects.create(name="Analysis")
33+
34+
def test_scanpipe_filters_filter_queryset_empty_values(self):
35+
resource1 = CodebaseResource.objects.create(
36+
project=self.project1,
37+
path="r1",
38+
# CharField blank=True null=False
39+
programming_language="Python",
40+
# JSONField default=list
41+
copyrights=[{"copyright": "Copyright (c)"}],
42+
)
43+
resource2 = CodebaseResource.objects.create(
44+
project=self.project1,
45+
path="r2",
46+
)
47+
48+
data = {"programming_language": ""}
49+
filterset = ResourceFilterSet(data=data)
50+
self.assertEqual([resource1, resource2], list(filterset.qs))
51+
52+
data = {"programming_language": "Python"}
53+
filterset = ResourceFilterSet(data=data)
54+
self.assertEqual([resource1], list(filterset.qs))
55+
56+
data = {"programming_language": "EMPTY"}
57+
filterset = ResourceFilterSet(data=data)
58+
self.assertEqual([resource2], list(filterset.qs))
59+
60+
data = {"copyrights": ""}
61+
filterset = ResourceFilterSet(data=data)
62+
self.assertEqual([resource1, resource2], list(filterset.qs))
63+
64+
data = {"copyrights": "Copyright"}
65+
filterset = ResourceFilterSet(data=data)
66+
self.assertEqual([resource1], list(filterset.qs))
67+
68+
data = {"copyrights": "EMPTY"}
69+
filterset = ResourceFilterSet(data=data)
70+
self.assertEqual([resource2], list(filterset.qs))

0 commit comments

Comments
 (0)