Repository: carpentries/amy

Issue:
Search feature not working properly
The search feature is not working properly.  For example, searching for "florida" should return all organisations with "florida" in the name, all persons with "florida" in their email address or profile, etc.  Instead it goes to one single person's record.  


Discussion:

Resolving patch:
diff --git a/amy/dashboard/views.py b/amy/dashboard/views.py
--- a/amy/dashboard/views.py
+++ b/amy/dashboard/views.py
@@ -273,37 +273,40 @@ def search(request):
     airports = None
     training_requests = None
     comments = None
-    single_results = []
 
     if request.method == "GET" and "term" in request.GET:
         form = SearchForm(request.GET)
         if form.is_valid():
-            term = form.cleaned_data.get("term", "")
+            term = form.cleaned_data.get("term", "").strip()
             tokens = re.split(r"\s+", term)
+            results_combined = []
 
-            organizations = Organization.objects.filter(
-                Q(domain__icontains=term) | Q(fullname__icontains=term)
-            ).order_by("fullname")
-            if len(organizations) == 1:
-                single_results.append(organizations[0])
-
-            memberships = Membership.objects.filter(
-                Q(name__icontains=term) | Q(registration_code__icontains=term)
-            ).order_by("-agreement_start")
-            if len(memberships) == 1:
-                single_results.append(memberships[0])
-
-            events = Event.objects.filter(
-                Q(slug__icontains=term)
-                | Q(host__domain__icontains=term)
-                | Q(host__fullname__icontains=term)
-                | Q(url__icontains=term)
-                | Q(contact__icontains=term)
-                | Q(venue__icontains=term)
-                | Q(address__icontains=term)
-            ).order_by("-slug")
-            if len(events) == 1:
-                single_results.append(events[0])
+            organizations = list(
+                Organization.objects.filter(
+                    Q(domain__icontains=term) | Q(fullname__icontains=term)
+                ).order_by("fullname")
+            )
+            results_combined += organizations
+
+            memberships = list(
+                Membership.objects.filter(
+                    Q(name__icontains=term) | Q(registration_code__icontains=term)
+                ).order_by("-agreement_start")
+            )
+            results_combined += memberships
+
+            events = list(
+                Event.objects.filter(
+                    Q(slug__icontains=term)
+                    | Q(host__domain__icontains=term)
+                    | Q(host__fullname__icontains=term)
+                    | Q(url__icontains=term)
+                    | Q(contact__icontains=term)
+                    | Q(venue__icontains=term)
+                    | Q(address__icontains=term)
+                ).order_by("-slug")
+            )
+            results_combined += events
 
             # if user searches for two words, assume they mean a person
             # name
@@ -316,52 +319,56 @@ def search(request):
                     | Q(secondary_email__icontains=term)
                     | Q(github__icontains=term)
                 )
-                persons = Person.objects.filter(complex_q)
+                persons = list(Person.objects.filter(complex_q))
             else:
-                persons = Person.objects.filter(
-                    Q(personal__icontains=term)
+                persons = list(
+                    Person.objects.filter(
+                        Q(personal__icontains=term)
+                        | Q(family__icontains=term)
+                        | Q(email__icontains=term)
+                        | Q(secondary_email__icontains=term)
+                        | Q(github__icontains=term)
+                    ).order_by("family")
+                )
+
+            results_combined += persons
+
+            airports = list(
+                Airport.objects.filter(
+                    Q(iata__icontains=term) | Q(fullname__icontains=term)
+                ).order_by("iata")
+            )
+            results_combined += airports
+
+            training_requests = list(
+                TrainingRequest.objects.filter(
+                    Q(group_name__icontains=term)
                     | Q(family__icontains=term)
                     | Q(email__icontains=term)
-                    | Q(secondary_email__icontains=term)
                     | Q(github__icontains=term)
-                ).order_by("family")
-
-            if len(persons) == 1:
-                single_results.append(persons[0])
-
-            airports = Airport.objects.filter(
-                Q(iata__icontains=term) | Q(fullname__icontains=term)
-            ).order_by("iata")
-            if len(airports) == 1:
-                single_results.append(airports[0])
-
-            training_requests = TrainingRequest.objects.filter(
-                Q(group_name__icontains=term)
-                | Q(family__icontains=term)
-                | Q(email__icontains=term)
-                | Q(github__icontains=term)
-                | Q(affiliation__icontains=term)
-                | Q(location__icontains=term)
-                | Q(user_notes__icontains=term)
+                    | Q(affiliation__icontains=term)
+                    | Q(location__icontains=term)
+                    | Q(user_notes__icontains=term)
+                )
+            )
+            results_combined += training_requests
+
+            comments = list(
+                Comment.objects.filter(
+                    Q(comment__icontains=term)
+                    | Q(user_name__icontains=term)
+                    | Q(user_email__icontains=term)
+                    | Q(user__personal__icontains=term)
+                    | Q(user__family__icontains=term)
+                    | Q(user__email__icontains=term)
+                    | Q(user__github__icontains=term)
+                ).prefetch_related("content_object")
             )
-            if len(training_requests) == 1:
-                single_results.append(training_requests[0])
-
-            comments = Comment.objects.filter(
-                Q(comment__icontains=term)
-                | Q(user_name__icontains=term)
-                | Q(user_email__icontains=term)
-                | Q(user__personal__icontains=term)
-                | Q(user__family__icontains=term)
-                | Q(user__email__icontains=term)
-                | Q(user__github__icontains=term)
-            ).prefetch_related("content_object")
-            if len(comments) == 1:
-                single_results.append(comments[0])
+            results_combined += comments
 
             # only 1 record found? Let's move to it immediately
-            if len(single_results) == 1 and not form.cleaned_data["no_redirect"]:
-                result = single_results[0]
+            if len(results_combined) == 1 and not form.cleaned_data["no_redirect"]:
+                result = results_combined[0]
                 msg = format_html(
                     "You were moved to this page, because your search <i>{}</i> "
                     "yields only this result.",


Test patch:
diff --git a/amy/dashboard/tests/test_search.py b/amy/dashboard/tests/test_search.py
--- a/amy/dashboard/tests/test_search.py
+++ b/amy/dashboard/tests/test_search.py
@@ -222,3 +222,30 @@ def test_search_redirect_two_single_results(self):
         self.assertEqual(response.status_code, 200)  # doesn't redirect
         self.assertEqual(len(response.context["organisations"]), 1)
         self.assertEqual(len(response.context["comments"]), 1)
+
+    def test_search_redirect_one_single_result(self):
+        """Regression test: make sure redirect doesn't happen if there's a singular
+        result in one of the groups, but other groups contain >= 2 elements.
+
+        https://github.com/carpentries/amy/issues/2014
+        """
+        Comment.objects.create(
+            content_object=self.org_alpha,
+            user=self.hermione,
+            comment="Testing commenting system for Alpha Organization",
+            submit_date=datetime.now(tz=timezone.utc),
+            site=Site.objects.get_current(),
+        )
+
+        Comment.objects.create(
+            content_object=self.org_beta,
+            user=self.hermione,
+            comment="Cross-posting an Alpha comment on Beta Organization page.",
+            submit_date=datetime.now(tz=timezone.utc),
+            site=Site.objects.get_current(),
+        )
+
+        response = self.search_for("Alpha", no_redirect=False, follow=False)
+        self.assertEqual(response.status_code, 200)  # doesn't redirect
+        self.assertEqual(len(response.context["organisations"]), 1)
+        self.assertEqual(len(response.context["comments"]), 2)