diff --git a/partner_programs/permissions.py b/partner_programs/permissions.py index e97fa2e5..ce9803d0 100644 --- a/partner_programs/permissions.py +++ b/partner_programs/permissions.py @@ -40,6 +40,29 @@ def has_permission(self, request, view): return program.experts.filter(user=request.user).exists() +class IsAdminManagerOrExpertOfProgram(BasePermission): + """Allow administrators, managers, and experts of one program.""" + + def has_permission(self, request, view): + user = request.user + if not user or not user.is_authenticated: + return False + + program_id = view.kwargs.get("pk") or view.kwargs.get("program_id") + if not program_id: + return False + + try: + program = PartnerProgram.objects.get(pk=program_id) + except PartnerProgram.DoesNotExist: + return False + + return ( + can_manage_program(user, program) + or program.experts.filter(user=user).exists() + ) + + class IsAdminOrManagerOfProgram(BasePermission): """ Доступ разрешён только админам и менеджерам конкретной программы. diff --git a/partner_programs/tests/test_program_filters.py b/partner_programs/tests/test_program_filters.py index 8416e988..e57a94e6 100644 --- a/partner_programs/tests/test_program_filters.py +++ b/partner_programs/tests/test_program_filters.py @@ -12,6 +12,7 @@ create_project, create_user, ) +from users.models import CustomUser class PartnerProgramProjectFilterAPITests(TestCase): @@ -21,6 +22,11 @@ def setUp(self): self.program = create_partner_program() self.program.managers.add(self.manager) + def _create_expert(self, *, prefix, program): + expert = create_user(prefix=prefix, user_type=CustomUser.EXPERT) + expert.expert.programs.add(program) + return expert + def test_manager_can_get_filterable_program_fields(self): filterable = create_program_field( self.program, @@ -53,6 +59,92 @@ def test_non_manager_cannot_get_filterable_program_fields(self): self.assertEqual(response.status_code, 403) + def test_anonymous_cannot_get_filterable_program_fields(self): + response = self.client.get(f"/programs/{self.program.id}/filters/") + + self.assertEqual(response.status_code, 401) + + def test_program_expert_can_get_filterable_program_fields(self): + expert = self._create_expert( + prefix="program-filter-expert", + program=self.program, + ) + self.client.force_authenticate(expert) + + response = self.client.get(f"/programs/{self.program.id}/filters/") + + self.assertEqual(response.status_code, 200) + + def test_other_program_expert_cannot_get_filterable_program_fields(self): + other_program = create_partner_program() + expert = self._create_expert( + prefix="other-program-filter-expert", + program=other_program, + ) + self.client.force_authenticate(expert) + + response = self.client.get(f"/programs/{self.program.id}/filters/") + + self.assertEqual(response.status_code, 403) + + def test_staff_can_get_filterable_program_fields(self): + staff = create_user(prefix="program-filter-staff", is_staff=True) + self.client.force_authenticate(staff) + + response = self.client.get(f"/programs/{self.program.id}/filters/") + + self.assertEqual(response.status_code, 200) + + def test_superuser_can_get_filterable_program_fields(self): + superuser = create_user( + prefix="program-filter-superuser", + is_staff=True, + is_superuser=True, + ) + self.client.force_authenticate(superuser) + + response = self.client.get(f"/programs/{self.program.id}/filters/") + + self.assertEqual(response.status_code, 200) + + def test_expert_receives_same_filter_schema_as_manager(self): + create_program_field( + self.program, + name="track", + label="Track", + field_type="select", + options=["ai", "edu"], + show_filter=True, + ) + expert = self._create_expert( + prefix="program-filter-schema-expert", + program=self.program, + ) + + self.client.force_authenticate(self.manager) + manager_response = self.client.get(f"/programs/{self.program.id}/filters/") + self.client.force_authenticate(expert) + expert_response = self.client.get(f"/programs/{self.program.id}/filters/") + + self.assertEqual(manager_response.status_code, 200) + self.assertEqual(expert_response.status_code, 200) + self.assertEqual(expert_response.data, manager_response.data) + + def test_filter_schema_endpoint_remains_read_only(self): + expert = self._create_expert( + prefix="program-filter-read-only-expert", + program=self.program, + ) + self.client.force_authenticate(expert) + + response = self.client.post( + f"/programs/{self.program.id}/filters/", + {}, + format="json", + ) + + self.assertEqual(response.status_code, 405) + def test_manager_can_filter_program_projects_by_field_value(self): field = create_program_field( self.program, diff --git a/partner_programs/views.py b/partner_programs/views.py index 885ad76e..001a94a0 100644 --- a/partner_programs/views.py +++ b/partner_programs/views.py @@ -24,6 +24,7 @@ ) from partner_programs.pagination import PartnerProgramPagination from partner_programs.permissions import ( + IsAdminManagerOrExpertOfProgram, IsAdminOrManagerOfProgram, IsProjectLeader, can_manage_program, @@ -418,7 +419,7 @@ def post(self, request, pk, *args, **kwargs): class ProgramFiltersAPIView(APIView): - permission_classes = [IsAuthenticated, IsAdminOrManagerOfProgram] + permission_classes = [IsAuthenticated, IsAdminManagerOrExpertOfProgram] def get(self, request, pk): program = get_object_or_404(PartnerProgram, pk=pk)