Repository: akvo/akvo-rsr

Issue:
User management page times out when a user has many employments
The permissions system and the user_management view both make a huge number of queries when a user has multiple employments.  This causes the request to timeout. 

Created via Reamaze:

Link: https://akvoo.reamaze.com/admin/conversations/editor-rights

Message:
Dear Akvo team,

Over the last couple of days/weeks I tried several times to give a colleague editor rights for AKVO RSR. Apart from the system being very slow, I don’t succeed in opening the ‘user management’ tab, or, in the rare cases I succeeded, I was not able to give editor rights.  In all these cases I got the message  ‘504 gateway time-out’.

Thank you very much
Best regards,



Discussion:

Resolving patch:
diff --git a/akvo/rsr/models/user.py b/akvo/rsr/models/user.py
--- a/akvo/rsr/models/user.py
+++ b/akvo/rsr/models/user.py
@@ -373,7 +373,8 @@ def approved_employments(self):
         """
         Return all approved employments.
         """
-        return self.employers.all().exclude(is_approved=False)
+        employments = self.employers.all().exclude(is_approved=False)
+        return employments.select_related('organisation', 'group', 'user')
 
     def can_create_project(self):
         """Check to see if the user can create a project."""
diff --git a/akvo/rsr/permissions.py b/akvo/rsr/permissions.py
--- a/akvo/rsr/permissions.py
+++ b/akvo/rsr/permissions.py
@@ -13,6 +13,13 @@
                      PublishingStatus)
 
 
+ADMIN_GROUP = None
+PROJECT_EDITOR_GROUP = None
+ME_MANAGER_GROUP = None
+USER_GROUP = None
+USER_MANAGER_GROUP = None
+
+
 @rules.predicate
 def is_rsr_admin(user):
     if user.is_authenticated() and user.get_is_admin():
@@ -22,10 +29,13 @@ def is_rsr_admin(user):
 
 @rules.predicate
 def is_org_admin(user, obj):
+    global ADMIN_GROUP
+    if ADMIN_GROUP is None:
+        ADMIN_GROUP = Group.objects.get(name='Admins')
     if not user.is_authenticated():
         return False
-    for employment in user.employers.approved():
-        if employment.group == Group.objects.get(name='Admins'):
+    for employment in user.approved_employments():
+        if employment.group_id == ADMIN_GROUP.id:
             org = employment.organisation
             if not obj:
                 return True
@@ -110,10 +120,13 @@ def is_org_admin(user, obj):
 
 @rules.predicate
 def is_org_user_manager(user, obj):
+    global USER_MANAGER_GROUP
+    if USER_MANAGER_GROUP is None:
+        USER_MANAGER_GROUP = Group.objects.get(name='User Managers')
     if not user.is_authenticated():
         return False
-    for employment in user.employers.approved():
-        if employment.group == Group.objects.get(name='User Managers'):
+    for employment in user.approved_employments():
+        if employment.group_id == USER_MANAGER_GROUP.id:
             if not obj:
                 return True
             elif isinstance(obj, get_user_model()) and obj in employment.organisation.all_users():
@@ -133,10 +146,16 @@ def is_org_user_manager(user, obj):
 
 @rules.predicate
 def is_org_project_editor(user, obj):
+    global ME_MANAGER_GROUP
+    if ME_MANAGER_GROUP is None:
+        ME_MANAGER_GROUP = Group.objects.get(name='M&E Managers')
+    global PROJECT_EDITOR_GROUP
+    if PROJECT_EDITOR_GROUP is None:
+        PROJECT_EDITOR_GROUP = Group.objects.get(name='Project Editors')
     if not user.is_authenticated():
         return False
-    for employment in user.employers.approved():
-        if employment.group in Group.objects.filter(name__in=['Project Editors', 'M&E Managers']):
+    for employment in user.approved_employments():
+        if employment.group_id in set([PROJECT_EDITOR_GROUP.id, ME_MANAGER_GROUP.id]):
             if not obj:
                 return True
             if isinstance(obj, Organisation):
@@ -201,10 +220,13 @@ def is_org_project_editor(user, obj):
 
 @rules.predicate
 def is_org_user(user, obj):
+    global USER_GROUP
+    if USER_GROUP is None:
+        USER_GROUP = Group.objects.get(name='Users')
     if not user.is_authenticated():
         return False
-    for employment in user.employers.approved():
-        if employment.group == Group.objects.get(name='Users'):
+    for employment in user.approved_employments():
+        if employment.group_id == USER_GROUP.id:
             if not obj:
                 return True
             if isinstance(obj, Project) and obj in employment.organisation.all_projects():
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
@@ -458,7 +458,12 @@ def user_management(request):
     # Order employments in reverse chronological order, but also group
     # employments by the user.
     employments = employments.annotate(max_id=Max('user__employers__id'))
-    employments = employments.order_by('-max_id', '-id')
+    employments = employments.order_by('-max_id', '-id').select_related(
+        'user',
+        'organisation',
+        'group',
+        'country',
+    )
 
     qs = remove_empty_querydict_items(request.GET)
     page = request.GET.get('page')
@@ -500,10 +505,7 @@ def user_management(request):
             employment_dict["user"] = user_dict
         employments_array.append(employment_dict)
 
-    organisations_list = []
-    for organisation in organisations:
-        organisation_dict = {'id': organisation.id, 'name': organisation.name}
-        organisations_list.append(organisation_dict)
+    organisations_list = list(organisations.values('id', 'name'))
 
     roles_list = []
     for role in roles:


Test patch: