Repository: carpentries/amy

Issue:
Filter workshop staff by knowledge domain
I was wondering if it would be possible to add an option to the [Find Workshop Staff](https://amy.carpentries.org/workshops/workshop_staff/) page in Amy so that search results can be filtered by knowledge domain (which is a field each person has in the database)?

That would help when looking for instructors and helpers for research domain specific workshops. 




Discussion:
Yes, this is possible.

Note: this requires changes to the `WorkshopStaffFilter` in `amy/workshops/filters.py` - a similar field to `languages` would have to be introduced.

Resolving patch:
diff --git a/amy/workshops/filters.py b/amy/workshops/filters.py
--- a/amy/workshops/filters.py
+++ b/amy/workshops/filters.py
@@ -23,6 +23,7 @@
     Tag,
     Task,
     Award,
+    KnowledgeDomain,
     Language,
     Lesson,
 )
@@ -385,6 +386,11 @@ class WorkshopStaffFilter(AMYFilterSet):
         queryset=Language.objects.all(),
         conjoined=True,  # `AND`
     )
+    domains = django_filters.ModelMultipleChoiceFilter(
+        label="Knowledge Domains",
+        queryset=KnowledgeDomain.objects.all(),
+        conjoined=True,  # `AND`
+    )
     gender = django_filters.ChoiceFilter(
         label="Gender",
         choices=Person.GENDER_CHOICES,
diff --git a/amy/workshops/forms.py b/amy/workshops/forms.py
--- a/amy/workshops/forms.py
+++ b/amy/workshops/forms.py
@@ -31,6 +31,7 @@
     Membership,
     Tag,
     Language,
+    KnowledgeDomain,
     Badge,
 )
 
@@ -251,7 +252,15 @@ class WorkshopStaffForm(forms.Form):
             attrs=SELECT2_SIDEBAR,
         ),
     )
-
+    domains = forms.ModelMultipleChoiceField(
+        label="Knowlege Domains",
+        required=False,
+        queryset=KnowledgeDomain.objects.all(),
+        widget=ModelSelect2MultipleWidget(
+            data_view="knowledge-domains-lookup",
+            attrs=SELECT2_SIDEBAR,
+        ),
+    )
     country = forms.MultipleChoiceField(
         choices=list(Countries()),
         required=False,
@@ -320,6 +329,7 @@ def __init__(self, *args, **kwargs):
             "was_organizer",
             "is_in_progress_trainee",
             "languages",
+            "domains",
             "gender",
             "lessons",
             Submit("", "Submit"),
diff --git a/amy/workshops/lookups.py b/amy/workshops/lookups.py
--- a/amy/workshops/lookups.py
+++ b/amy/workshops/lookups.py
@@ -253,6 +253,29 @@ def get_queryset(self):
         return results
 
 
+class KnowledgeDomainLookupView(OnlyForAdminsNoRedirectMixin, AutoResponseView):
+    def dispatch(self, request, *args, **kwargs):
+        self.subtag = "subtag" in request.GET.keys()
+        return super().dispatch(request, *args, **kwargs)
+
+    def get_queryset(self):
+        results = models.KnowledgeDomain.objects.all()
+
+        if self.term:
+            results = results.filter(
+                Q(name__icontains=self.term) | Q(subtag__icontains=self.term)
+            )
+
+            if self.subtag:
+                return results.filter(subtag__iexact=self.term)
+
+        results = results.annotate(person_count=Count("person")).order_by(
+            "-person_count"
+        )
+
+        return results
+
+
 class TrainingRequestLookupView(OnlyForAdminsNoRedirectMixin, AutoResponseView):
     """The same as PersonLookup, but allows only to select administrators.
 
@@ -311,6 +334,11 @@ def get_queryset(self):
     url(r"^admins/$", AdminLookupView.as_view(), name="admin-lookup"),
     url(r"^airports/$", AirportLookupView.as_view(), name="airport-lookup"),
     url(r"^languages/$", LanguageLookupView.as_view(), name="language-lookup"),
+    url(
+        r"^knowledge_domains/$",
+        KnowledgeDomainLookupView.as_view(),
+        name="knowledge-domains-lookup",
+    ),
     url(
         r"^training_requests/$",
         TrainingRequestLookupView.as_view(),


Test patch:
diff --git a/amy/workshops/tests/base.py b/amy/workshops/tests/base.py
--- a/amy/workshops/tests/base.py
+++ b/amy/workshops/tests/base.py
@@ -17,6 +17,7 @@
     Role,
     Tag,
     Language,
+    KnowledgeDomain,
 )
 from workshops.util import universal_date_format
 
@@ -143,6 +144,23 @@ def _setUpLanguages(self):
         self.latin, _ = Language.objects.get_or_create(
             name="Latin",
         )
+        self.english, _ = Language.objects.get_or_create(name="English",)
+        self.french, _ = Language.objects.get_or_create(name="French",)
+        self.latin, _ = Language.objects.get_or_create(name="Latin",)
+    
+
+    def _setUpDomains(self):
+        """Set up knowledge domain objects."""
+
+        self.chemistry, _ = KnowledgeDomain.objects.get_or_create(
+            name="Chemistry",
+        )
+        self.medicine, _ = KnowledgeDomain.objects.get_or_create(
+            name="Medicine",
+        )
+        self.humanities, _ = KnowledgeDomain.objects.get_or_create(
+            name="Humanities",
+        )
 
     def _setUpBadges(self):
         """Set up badge objects."""
diff --git a/amy/workshops/tests/test_workshop_staff_searching.py b/amy/workshops/tests/test_workshop_staff_searching.py
--- a/amy/workshops/tests/test_workshop_staff_searching.py
+++ b/amy/workshops/tests/test_workshop_staff_searching.py
@@ -229,6 +229,51 @@ def test_match_on_many_language(self):
 
         self.assertIn(self.ron, response.context["persons"])
         self.assertNotIn(self.harry, response.context["persons"])
+        self.assertIn(self.ron, response.context['persons'])
+        self.assertNotIn(self.harry, response.context['persons'])
+    
+
+    def test_match_on_one_domain(self):
+        """Ensure people with one particular knowledge domain preference
+        are returned by search."""
+        # prepare knowledge domains
+        self._setUpDomains()
+        # Ron has a humanities knowledge domain
+        self.ron.domains.add(self.humanities)
+        # Harry has a chemistry knowledge domain
+        self.harry.domains.add(self.chemistry)
+
+        response = self.client.get(
+            self.url,
+            {
+                "domains": [self.chemistry.pk],
+            },
+        )
+        self.assertEqual(response.status_code, 200)
+
+        self.assertIn(self.harry, response.context["persons"])
+        self.assertNotIn(self.ron, response.context["persons"])
+
+    def test_match_on_many_domains(self):
+        """Ensure people with a set of knowledge domain preferences
+        are returned by search."""
+        # prepare knowledge domains
+        self._setUpDomains()
+        # Ron has a humanities and chemistry knowledge domain
+        self.ron.domains.add(self.humanities, self.chemistry)
+        # Harry has a chemistry knowledge domain
+        self.harry.domains.add(self.chemistry)
+
+        response = self.client.get(
+            self.url,
+            {
+                "domains": [self.chemistry.pk, self.humanities.pk],
+            },
+        )
+        self.assertEqual(response.status_code, 200)
+
+        self.assertIn(self.ron, response.context["persons"])
+        self.assertNotIn(self.harry, response.context["persons"])
 
     def test_roles(self):
         """Ensure people with at least one helper/organizer roles are returned