Repository: akvo/akvo-rsr

Issue:
Enumerators cannot see results page for private projects



Discussion:

Resolving patch:
diff --git a/akvo/rest/viewsets.py b/akvo/rest/viewsets.py
--- a/akvo/rest/viewsets.py
+++ b/akvo/rest/viewsets.py
@@ -133,12 +133,14 @@ def get_queryset(self):
 
         # filter projects if user is "non-privileged"
         if user.is_anonymous() or not (user.is_superuser or user.is_admin):
-            queryset = self.projects_filter_for_non_privileged_users(user, queryset, self.project_relation)
+            queryset = self.projects_filter_for_non_privileged_users(
+                user, queryset, self.project_relation, action=self.action
+            )
 
         return queryset.distinct()
 
     @staticmethod
-    def projects_filter_for_non_privileged_users(user, queryset, project_relation):
+    def projects_filter_for_non_privileged_users(user, queryset, project_relation, action='create'):
 
         if not user.is_anonymous() and (user.is_admin or user.is_superuser):
             return queryset.distinct()
@@ -160,8 +162,31 @@ def projects_filter_for_non_privileged_users(user, queryset, project_relation):
 
         # Otherwise, check to which objects the user has (change) permission
         elif private_objects.exists():
-            permission = type(private_objects[0])._meta.db_table.replace('_', '.change_')
-            filter_ = user.get_permission_filter(permission, project_relation)
-            queryset = public_objects | private_objects.filter(filter_).distinct()
+            include_user_owned = hasattr(queryset.model, 'user')
+            if action == 'list':
+                # The view permission is new, and previously only the change
+                # permission existed. To avoid adding new view permissions for
+                # all the objects, we also check if a user has change
+                # permissions, which implicitly implies view permissions.
+                change_permission = type(private_objects[0])._meta.db_table.replace('_', '.change_')
+                change_filter = user.get_permission_filter(
+                    change_permission, project_relation, include_user_owned
+                )
+                change_objects = private_objects.filter(change_filter).distinct()
+
+                # Check if user has view permission on the queryset
+                view_permission = change_permission.replace('.change_', '.view_')
+                view_filter = user.get_permission_filter(
+                    view_permission, project_relation, include_user_owned
+                )
+                view_objects = private_objects.filter(view_filter).distinct()
+
+                private_objects = (change_objects | view_objects).distinct()
+            else:
+                permission = type(private_objects[0])._meta.db_table.replace('_', '.change_')
+                filter_ = user.get_permission_filter(permission, project_relation, include_user_owned)
+                private_objects = private_objects.filter(filter_).distinct()
+
+            queryset = public_objects | private_objects
 
         return queryset.distinct()
diff --git a/akvo/rsr/models/__init__.py b/akvo/rsr/models/__init__.py
--- a/akvo/rsr/models/__init__.py
+++ b/akvo/rsr/models/__init__.py
@@ -205,10 +205,12 @@
 rules.add_perm('rsr.add_indicator', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
 rules.add_perm('rsr.change_indicator', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
 rules.add_perm('rsr.delete_indicator', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
+rules.add_perm('rsr.view_indicator', is_org_enumerator)
 
 rules.add_perm('rsr.add_indicatordimension', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
 rules.add_perm('rsr.change_indicatordimension', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
 rules.add_perm('rsr.delete_indicatordimension', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
+rules.add_perm('rsr.view_indicatordimension', is_org_enumerator)
 
 rules.add_perm('rsr.add_indicatorlabel', is_rsr_admin | is_org_admin |is_org_me_manager_or_project_editor)
 rules.add_perm('rsr.change_indicatorlabel', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
@@ -218,6 +220,7 @@
 rules.add_perm('rsr.change_indicatorperiod', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
 rules.add_perm('rsr.delete_indicatorperiod', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
 rules.add_perm('rsr.do_me_manager_actions', is_rsr_admin | is_org_admin | is_org_me_manager)
+rules.add_perm('rsr.view_indicatorperiod', is_org_enumerator)
 
 rules.add_perm('rsr.view_indicatorperioddata', is_rsr_admin | is_org_admin | is_org_me_manager)
 rules.add_perm(
@@ -236,6 +239,7 @@
 rules.add_perm('rsr.add_disaggregation', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
 rules.add_perm('rsr.change_disaggregation', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
 rules.add_perm('rsr.delete_disaggregation', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
+rules.add_perm('rsr.view_disaggregation', is_org_enumerator)
 
 rules.add_perm('rsr.add_indicatorperioddatacomment', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
 rules.add_perm('rsr.change_indicatorperioddatacomment', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
@@ -351,6 +355,7 @@
 rules.add_perm('rsr.add_result', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
 rules.add_perm('rsr.change_result', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
 rules.add_perm('rsr.delete_result', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
+rules.add_perm('rsr.view_result', is_org_enumerator)
 
 rules.add_perm('rsr.add_sector', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
 rules.add_perm('rsr.change_sector', is_rsr_admin | is_org_admin | is_org_me_manager_or_project_editor)
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
@@ -546,7 +546,7 @@ def enumerator_projects(self):
         orgs = self.approved_employments(group_names=['Enumerators']).organisations()
         return Project.objects.of_partners(orgs).distinct()
 
-    def get_permission_filter(self, permission, project_relation):
+    def get_permission_filter(self, permission, project_relation, include_user_owned=True):
         """Convert a rules permission predicate into a queryset filter using Q objects.
 
         project_relation is the string for constructing a field lookup to the
@@ -572,11 +572,12 @@ def get_permission_filter(self, permission, project_relation):
             'is_org_enumerator': (
                 Q(**{project_filter_name: self.enumerator_projects()})
             ),
-            'is_org_user_manager': (
-                Q(**{project_filter_name: self.user_manager_projects()}) & Q(user=self)
-            ),
-            'is_org_user': Q(**{project_filter_name: self.my_projects()}) & Q(user=self),
+            'is_org_user_manager': Q(**{project_filter_name: self.user_manager_projects()}),
+            'is_org_user': Q(**{project_filter_name: self.my_projects()}),
         }
+        if include_user_owned:
+            permissions['is_org_user'] = permissions['is_org_user'] & Q(user=self)
+            permissions['is_org_user_manager'] = permissions['is_org_user_manager'] & Q(user=self)
         operators = {'|': Q.OR, '&': Q.AND}
         return self.parse_permission_expression(permission_expression, permissions, operators)
 


Test patch: