Repository: akvo/akvo-rsr

Issue:
My Projects page search times out with long query strings
Created via Reamaze:

Link: https://akvoo.reamaze.com/admin/conversations/akvorsr-support-request-number-51646-from-marten-at-akvo-dot-org
Assignee: Camille Clerx

Message:
[AKVORSR] Support request #51,646 from marten@akvo.org      

User email: marten@akvo.org  

Request Info
Device:  Personal computer
Browser:  Chrome 57.0.2987.133
OS:  OS X 10.10.5
IP Address:  82.72.249.50
URL:  http://rsrsupport.akvo.org/form  

1- What do you need help with?
I have a bug  

2- What is the name of your organisation?
akvo  

Tell us more about your request
I searched for a project name in my projects in my rsr. the project name: Natural Fertilizer - Building up the business case for efficient fertilization and soil conservation with coffee farmers - Honduras. Result: RSR gives timeout error: 504 Gateway Time-out.




Discussion:

Resolving patch:
diff --git a/akvo/rsr/views/my_rsr.py b/akvo/rsr/views/my_rsr.py
--- a/akvo/rsr/views/my_rsr.py
+++ b/akvo/rsr/views/my_rsr.py
@@ -12,7 +12,7 @@
 from django.contrib.auth.models import Group
 from django.core.exceptions import PermissionDenied
 from django.core.urlresolvers import reverse
-from django.db.models import Max
+from django.db.models import Max, Q
 from django.forms.models import model_to_dict
 from django.http import HttpResponseRedirect, Http404
 from django.shortcuts import get_object_or_404, render
@@ -164,10 +164,11 @@ def my_projects(request):
         except Project.DoesNotExist:
             Project.objects.none()
         except ValueError:
-            q_list = q.split()
-            for q_item in q_list:
-                projects = projects.filter(title__icontains=q_item) | \
-                    projects.filter(subtitle__icontains=q_item)
+            filter_expressions = [
+                Q(title__icontains=q_item) | Q(subtitle__icontains=q_item)
+                for q_item in q.split()
+            ]
+            projects = projects.filter(reduce(lambda x, y: x & y, filter_expressions))
 
     # Pagination
     qs = remove_empty_querydict_items(request.GET)


Test patch:
diff --git a/akvo/rsr/tests/views/test_my_rsr.py b/akvo/rsr/tests/views/test_my_rsr.py
--- a/akvo/rsr/tests/views/test_my_rsr.py
+++ b/akvo/rsr/tests/views/test_my_rsr.py
@@ -16,7 +16,7 @@
 from django.test import TestCase, Client
 from lxml import html
 
-from akvo.rsr.models import Employment, Organisation, User
+from akvo.rsr.models import Employment, Organisation, Project, User
 from akvo.utils import check_auth_groups
 
 
@@ -56,6 +56,43 @@ def test_user_management_employments_ordering(self):
                           (self.user1.id, u'Admins'),
                           (self.user1.id, u'Users')])
 
+    def test_search_with_long_query_strings_works(self):
+        """Test that search works with long query strings.
+
+        *NOTE*: This test was written to ensure that the search works correctly
+        when refactoring the code to make searches with long query strings
+        faster.  The test doesn't actually test that the new code is faster and
+        doesn't timeout, because there's no good way to do this (especially on
+        a small test database). If ever we start using something like
+        django_perf_rec, this may be feasible to test.
+
+        """
+        # Given
+        title = 'This is a super long project title that will be used as a search query'
+        Project.objects.create(title=title)
+        url = '/myrsr/projects?q={}'.format(title.replace(' ', '+'))
+
+        # When
+        response = self.c.get(url, follow=True)
+
+        # Then
+        self.assertEqual(200, response.status_code)
+        self.assertIn(title, response.content)
+
+    def test_search_filters_projects(self):
+        # Given
+        title = 'Project Title'
+        query = 'search query'
+        Project.objects.create(title=title)
+        url = '/myrsr/projects?q={}'.format(query.replace(' ', '+'))
+
+        # When
+        response = self.c.get(url, follow=True)
+
+        # Then
+        self.assertEqual(200, response.status_code)
+        self.assertNotIn(title, response.content)
+
     def _create_user(self, email, password, is_active=True, is_admin=False):
         """Create a user with the given email and password."""