The filter allows filtering by project-specific and global labels.
It also supports negative filtering. Prepending a label name with
a minus sign excludes patches with that label from search results.

Signed-off-by: Franciszek Stachura <[email protected]>
---
I'm not sure if this new "q" method is the best possible interface, but
I'm pretty sure the current interface is not flexible enough. Is there
any reason why filters return a dict that is unpacked into Django ORM
filter call? Wouldn't it make more sense to just pass the queryset
to each filter?
---
 patchwork/filters.py | 73 ++++++++++++++++++++++++++++++++++++++++++--
 patchwork/models.py  | 18 +++++++++++
 2 files changed, 88 insertions(+), 3 deletions(-)

diff --git a/patchwork/filters.py b/patchwork/filters.py
index 99aad8c3..9195cbce 100644
--- a/patchwork/filters.py
+++ b/patchwork/filters.py
@@ -13,6 +13,8 @@ from django.utils.safestring import mark_safe
 from patchwork.models import Person
 from patchwork.models import Series
 from patchwork.models import State
+from patchwork.models import exclude_submissions_by_labels
+from patchwork.models import filter_submissions_by_labels
 
 
 class Filter(object):
@@ -511,6 +513,62 @@ class DelegateFilter(Filter):
             self.forced = True
 
 
+class LabelsFilter(Filter):
+    name = 'Labels'
+    param = 'labels'
+
+    def __init__(self, filters):
+        super(LabelsFilter, self).__init__(filters)
+        self.labels = None
+
+    @property
+    def condition(self):
+        return self.labels
+
+    @property
+    def key(self):
+        return self.labels
+
+    @key.setter
+    def key(self, key):
+        key = key.strip()
+        if not key:
+            return
+
+        self.labels = key
+        self.applied = True
+
+    @property
+    def kwargs(self):
+        return None
+
+    def q(self, queryset):
+        label_names = self.labels.split(' ')
+        if len(label_names) == 0:
+            return queryset
+
+        labels_pos, labels_neg = [], []
+        for label in label_names:
+            if not label.startswith('-'):
+                labels_pos.append(label)
+            else:
+                labels_neg.append(label[1:])
+
+        queryset = exclude_submissions_by_labels(queryset, labels_neg)
+        queryset = filter_submissions_by_labels(queryset, labels_pos)
+        return queryset
+
+    @property
+    def form(self):
+        value = ''
+        if self.labels:
+            value = escape(self.labels)
+        return mark_safe(
+            '<input type="text" id="labels_input" name="%s" 
class="form-control" value="%s">'
+            % (self.param, value)
+        )
+
+
 FILTERS = [
     SeriesFilter,
     SubmitterFilter,
@@ -518,6 +576,7 @@ FILTERS = [
     SearchFilter,
     ArchiveFilter,
     DelegateFilter,
+    LabelsFilter,
 ]
 
 
@@ -547,14 +606,22 @@ class Filters:
 
     def apply(self, queryset):
         kwargs = collections.OrderedDict()
+        q_filters = []
         for f in self._filters:
             if f.applied:
-                kwargs.update(f.kwargs)
+                if f.kwargs is not None:
+                    kwargs.update(f.kwargs)
+                else:
+                    q_filters.append(f)
 
-        if not kwargs:
+        if not kwargs and not q_filters:
             return queryset
 
-        return queryset.filter(**kwargs)
+        queryset = queryset.filter(**kwargs)
+        for f in q_filters:
+            queryset = f.q(queryset)
+
+        return queryset
 
     def querystring(self, remove=None):
         params = self.params
diff --git a/patchwork/models.py b/patchwork/models.py
index e122ca3d..28bfe680 100644
--- a/patchwork/models.py
+++ b/patchwork/models.py
@@ -498,6 +498,24 @@ class SubmissionMixin(FilenameMixin, EmailMixin, 
models.Model):
         abstract = True
 
 
+def exclude_submissions_by_labels(queryset, labels):
+    if len(labels) > 0:
+        queryset = queryset.exclude(labels__name__in=labels)
+
+    return queryset
+
+
+def filter_submissions_by_labels(queryset, labels):
+    if len(labels) > 0:
+        queryset = (
+            queryset.filter(labels__name__in=labels)
+            .annotate(num_labels=models.Count('labels', distinct=True))
+            .filter(num_labels__gte=len(labels))
+        )
+
+    return queryset
+
+
 class Cover(SubmissionMixin):
     def get_absolute_url(self):
         return reverse(
-- 
2.55.0

_______________________________________________
Patchwork mailing list
[email protected]
https://lists.ozlabs.org/listinfo/patchwork

Reply via email to