Skip to content

Commit 9fa5c2a

Browse files
Merge pull request #2224 from laws-africa/not-facet
Facets can be negated
2 parents cf3b442 + 1164c94 commit 9fa5c2a

File tree

19 files changed

+400
-248
lines changed

19 files changed

+400
-248
lines changed

indigo_app/forms/tasks.py

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from django.http import QueryDict
1010

1111
from indigo_api.models import Task, TaskLabel, Country, TaskFile, Work
12-
from indigo_app.forms.works import WorkFilterForm
12+
from indigo_app.forms.works import WorkFilterForm, NegatableModelMultipleChoiceField
1313

1414

1515
class TaskForm(forms.ModelForm):
@@ -137,10 +137,10 @@ class Meta:
137137

138138

139139
class TaskFilterForm(WorkFilterForm):
140-
labels = forms.ModelMultipleChoiceField(label=_("Labels"), queryset=TaskLabel.objects, to_field_name='slug')
140+
labels = NegatableModelMultipleChoiceField(label=_("Labels"), queryset=TaskLabel.objects, to_field_name='slug')
141141
state = forms.MultipleChoiceField(label=_('State'), choices=Task.SIMPLE_STATE_CHOICES)
142142
assigned_to = forms.MultipleChoiceField(label=_('Assigned to'), choices=[])
143-
submitted_by = forms.ModelMultipleChoiceField(label=_('Submitted by'), queryset=User.objects)
143+
submitted_by = NegatableModelMultipleChoiceField(label=_('Submitted by'), queryset=User.objects)
144144
type = forms.MultipleChoiceField(label=_('Task type'), choices=Task.CODES)
145145
sortby = forms.ChoiceField(choices=[
146146
('-created_at', _('Created at (newest first)')), ('created_at', _('Created at (oldest first)')),
@@ -161,7 +161,19 @@ def __init__(self, country, locality, data, *args, **kwargs):
161161
super().__init__(country, params, *args, **kwargs)
162162

163163
# ensure assigned_to supports unassigned
164-
self.fields['assigned_to'].choices = [('-', _('(Not assigned)'))] + [(str(u.pk), u.username) for u in User.objects.all()]
164+
users = User.objects.all()
165+
self.fields['assigned_to'].choices = (
166+
[
167+
('0', _('(Not assigned)')),
168+
('-0', _('(Not assigned)')),
169+
] + [
170+
(str(u.pk), u.username)
171+
for u in users
172+
] + [
173+
(f'-{u.pk}', u.username)
174+
for u in users
175+
]
176+
)
165177

166178
self.locality = locality
167179
if country:
@@ -182,29 +194,23 @@ def filter_queryset(self, queryset, exclude=None):
182194

183195
if exclude != 'type':
184196
if self.cleaned_data.get('type'):
185-
queryset = queryset.filter(code__in=self.cleaned_data['type'])
197+
queryset = self.apply_values_filter(self.cleaned_data["type"], queryset, "code")
186198

187199
if exclude != 'labels':
188200
if self.cleaned_data.get('labels'):
189-
queryset = queryset.filter(labels__in=self.cleaned_data['labels'])
201+
queryset = self.apply_model_choices_filter(self.cleaned_data["labels"], queryset, "labels")
190202

191203
if exclude != 'state':
192204
if self.cleaned_data.get('state'):
193-
queryset = queryset.filter(state__in=self.cleaned_data['state'])
205+
queryset = self.apply_values_filter(self.cleaned_data["state"], queryset, "state")
194206

195207
if exclude != 'assigned_to':
196208
if self.cleaned_data.get('assigned_to'):
197-
options = [x for x in self.cleaned_data['assigned_to'] if x != '-']
198-
q = Q()
199-
if options:
200-
q |= Q(assigned_to__in=options)
201-
if '-' in self.cleaned_data['assigned_to']:
202-
q |= Q(assigned_to__isnull=True)
203-
queryset = queryset.filter(q)
209+
queryset = self.apply_assigned_to_filter(self.cleaned_data["assigned_to"], queryset)
204210

205211
if exclude != 'submitted_by':
206212
if self.cleaned_data.get('submitted_by'):
207-
queryset = queryset.filter(submitted_by_user__in=self.cleaned_data['submitted_by'])
213+
queryset = self.apply_model_choices_filter(self.cleaned_data["submitted_by"], queryset, "submitted_by_user")
208214

209215
if exclude != 'taxonomy_topic':
210216
if self.cleaned_data.get('taxonomy_topic'):
@@ -218,6 +224,27 @@ def filter_queryset(self, queryset, exclude=None):
218224

219225
return queryset
220226

227+
def apply_assigned_to_filter(self, values, queryset):
228+
# this is the same as the default apply_values_filter, but we need to handle unassigned
229+
values = self.cleaned_data['assigned_to']
230+
includes = [x for x in values if not x.startswith('-')]
231+
excludes = [x[1:] for x in values if x.startswith('-')]
232+
233+
def make_q(items):
234+
q = Q(assigned_to__in=items)
235+
# unassigned
236+
if '0' in items:
237+
q |= Q(assigned_to__isnull=True)
238+
return q
239+
240+
if includes:
241+
queryset = queryset.filter(make_q(includes))
242+
243+
if excludes:
244+
queryset = queryset.exclude(make_q(excludes))
245+
246+
return queryset
247+
221248
def task_facets(self, queryset, places_toc):
222249
facets = []
223250
self.facet_state(facets, queryset)
@@ -240,22 +267,26 @@ def facet_labels(self, facets, qs):
240267

241268
def facet_state(self, facets, qs):
242269
qs = self.filter_queryset(qs, exclude='state')
243-
counts = qs.values('state').annotate(count=Count('pk')).order_by()
270+
counts = {
271+
c['state']: c['count']
272+
for c in qs.values('state').annotate(count=Count('pk')).order_by()
273+
}
274+
# we always want to show all states, even if they are zero
244275
items = [
245-
(c['state'], c['count'])
246-
for c in counts
276+
(s, counts.get(s, 0))
277+
for s in Task.STATES
247278
]
248-
items.sort(key=lambda x: Task.STATES.index(x[0]))
249279
facets.append(self.facet("state", "checkbox", items))
250280

251281
for item in facets[-1].items:
252-
item.icon = f'task-icon-{item.value} text-{item.value} small'
282+
v = item.value[1:] if item.negated else item.value
283+
item.icon = f'task-icon-{v} text-{v} small'
253284

254285
def facet_assigned_to(self, facets, qs):
255286
qs = self.filter_queryset(qs, exclude='assigned_to')
256287
counts = qs.values('assigned_to').annotate(count=Count('pk')).order_by()
257288
items = [
258-
(str(c['assigned_to'] or '-'), c['count'])
289+
(str(c['assigned_to'] or '0'), c['count'])
259290
for c in counts
260291
]
261292
items.sort(key=lambda x: x[0])

0 commit comments

Comments
 (0)