diff --git a/backend/hct_mis_api/api/endpoints/__init__.py b/backend/hct_mis_api/api/endpoints/__init__.py index 9fb32ba254..66ec0f655b 100644 --- a/backend/hct_mis_api/api/endpoints/__init__.py +++ b/backend/hct_mis_api/api/endpoints/__init__.py @@ -1,2 +1,3 @@ +from hct_mis_api.api.endpoints.core import * # noqa: F401, F403 from hct_mis_api.api.endpoints.lookups import * # noqa: F401, F403 from hct_mis_api.api.endpoints.rdi import * # noqa: F401, F403 diff --git a/backend/hct_mis_api/api/endpoints/core/__init__.py b/backend/hct_mis_api/api/endpoints/core/__init__.py new file mode 100644 index 0000000000..c98e98e7f5 --- /dev/null +++ b/backend/hct_mis_api/api/endpoints/core/__init__.py @@ -0,0 +1 @@ +from hct_mis_api.api.endpoints.core.views import BusinessAreaListView # noqa: F401 diff --git a/backend/hct_mis_api/api/endpoints/core/serializers.py b/backend/hct_mis_api/api/endpoints/core/serializers.py new file mode 100644 index 0000000000..fc0a335555 --- /dev/null +++ b/backend/hct_mis_api/api/endpoints/core/serializers.py @@ -0,0 +1,18 @@ +from rest_framework import serializers + +from hct_mis_api.apps.core.models import BusinessArea + + +class BusinessAreaSerializer(serializers.ModelSerializer): + class Meta: + model = BusinessArea + fields = ( + "id", + "name", + "code", + "long_name", + "slug", + "parent", + "is_split", + "active", + ) diff --git a/backend/hct_mis_api/api/endpoints/core/views.py b/backend/hct_mis_api/api/endpoints/core/views.py new file mode 100644 index 0000000000..5a3abd7b79 --- /dev/null +++ b/backend/hct_mis_api/api/endpoints/core/views.py @@ -0,0 +1,10 @@ +from rest_framework.generics import ListAPIView + +from hct_mis_api.api.endpoints.base import HOPEAPIView +from hct_mis_api.api.endpoints.core.serializers import BusinessAreaSerializer +from hct_mis_api.apps.core.models import BusinessArea + + +class BusinessAreaListView(HOPEAPIView, ListAPIView): + serializer_class = BusinessAreaSerializer + queryset = BusinessArea.objects.all() diff --git a/backend/hct_mis_api/api/endpoints/program/__init__.py b/backend/hct_mis_api/api/endpoints/program/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/hct_mis_api/api/endpoints/program/serializers.py b/backend/hct_mis_api/api/endpoints/program/serializers.py new file mode 100644 index 0000000000..bd9fc3e0ff --- /dev/null +++ b/backend/hct_mis_api/api/endpoints/program/serializers.py @@ -0,0 +1,25 @@ +from rest_framework import serializers + +from hct_mis_api.apps.program.models import Program + + +class ProgramGlobalSerializer(serializers.ModelSerializer): + business_area_code = serializers.CharField(source="business_area.code", read_only=True) + + class Meta: + model = Program + fields = ( + "id", + "name", + "programme_code", + "status", + "start_date", + "end_date", + "budget", + "frequency_of_payments", + "sector", + "scope", + "cash_plus", + "population_goal", + "business_area_code", + ) diff --git a/backend/hct_mis_api/api/endpoints/program/views.py b/backend/hct_mis_api/api/endpoints/program/views.py new file mode 100644 index 0000000000..78e77da2f3 --- /dev/null +++ b/backend/hct_mis_api/api/endpoints/program/views.py @@ -0,0 +1,10 @@ +from rest_framework.generics import ListAPIView + +from hct_mis_api.api.endpoints.base import HOPEAPIView +from hct_mis_api.api.endpoints.program.serializers import ProgramGlobalSerializer +from hct_mis_api.apps.program.models import Program + + +class ProgramGlobalListView(HOPEAPIView, ListAPIView): + serializer_class = ProgramGlobalSerializer + queryset = Program.objects.all() diff --git a/backend/hct_mis_api/api/tests/test_business_area.py b/backend/hct_mis_api/api/tests/test_business_area.py new file mode 100644 index 0000000000..ecf7fdde7c --- /dev/null +++ b/backend/hct_mis_api/api/tests/test_business_area.py @@ -0,0 +1,94 @@ +import contextlib +from typing import Iterator + +from rest_framework.reverse import reverse + +from hct_mis_api.api.models import APIToken, Grant +from hct_mis_api.api.tests.base import HOPEApiTestCase +from hct_mis_api.apps.account.fixtures import BusinessAreaFactory +from hct_mis_api.apps.core.models import BusinessArea + + +@contextlib.contextmanager +def token_grant_permission(token: APIToken, grant: Grant) -> Iterator: + old = token.grants + token.grants += [grant.name] + token.save() + yield + token.grants = old + token.save() + + +class APIBusinessAreaTests(HOPEApiTestCase): + databases = {"default"} + user_permissions = [] + + @classmethod + def setUpTestData(cls) -> None: + super().setUpTestData() + cls.list_url = reverse("api:business-area-list") + + def test_list_business_area(self) -> None: + business_area1: BusinessArea = BusinessAreaFactory( + slug="ukraine11", + code="1234", + name="Ukraine", + long_name="the long name of Ukraine", + active=True, + ) + business_area2: BusinessArea = BusinessAreaFactory( + slug="BA 2", + code="5678", + name="Bus Area 2", + long_name="Business Area 2", + active=False, + parent=self.business_area, + ) + self.business_area.refresh_from_db() + business_area1.refresh_from_db() + business_area2.refresh_from_db() + response = self.client.get(self.list_url) + assert response.status_code == 403 + with token_grant_permission(self.token, Grant.API_READ_ONLY): + response = self.client.get(self.list_url) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.json()["results"]), 3) + self.assertIn( + { + "id": str(self.business_area.id), + "name": self.business_area.name, + "code": self.business_area.code, + "long_name": self.business_area.long_name, + "slug": self.business_area.slug, + "parent": None, + "is_split": self.business_area.is_split, + "active": self.business_area.active, + }, + response.json()["results"], + ) + self.assertIn( + { + "id": str(business_area1.id), + "name": business_area1.name, + "code": business_area1.code, + "long_name": business_area1.long_name, + "slug": business_area1.slug, + "parent": None, + "is_split": business_area1.is_split, + "active": business_area1.active, + }, + response.json()["results"], + ) + self.assertIn( + { + "id": str(business_area2.id), + "name": business_area2.name, + "code": business_area2.code, + "long_name": business_area2.long_name, + "slug": business_area2.slug, + "parent": str(business_area2.parent.id), + "is_split": business_area2.is_split, + "active": business_area2.active, + }, + response.json()["results"], + ) diff --git a/backend/hct_mis_api/api/tests/test_program.py b/backend/hct_mis_api/api/tests/test_program.py index 99fd0c1ee7..9fc0af9130 100644 --- a/backend/hct_mis_api/api/tests/test_program.py +++ b/backend/hct_mis_api/api/tests/test_program.py @@ -5,6 +5,7 @@ from hct_mis_api.api.models import APIToken, Grant from hct_mis_api.api.tests.base import HOPEApiTestCase +from hct_mis_api.apps.account.fixtures import BusinessAreaFactory from hct_mis_api.apps.program.fixtures import ProgramFactory from hct_mis_api.apps.program.models import Program @@ -19,7 +20,7 @@ def token_grant_permission(token: APIToken, grant: Grant) -> Iterator: token.save() -class CreateProgramTests(HOPEApiTestCase): +class APIProgramTests(HOPEApiTestCase): databases = {"default"} user_permissions = [] @@ -74,28 +75,172 @@ def test_create_program(self) -> None: self.assertEqual(program.business_area, self.business_area) def test_list_program(self) -> None: - program: Program = ProgramFactory( + program1: Program = ProgramFactory( budget=10000, start_date="2022-01-12", end_date="2022-09-12", business_area=self.business_area, population_goal=200, + status=Program.ACTIVE, ) + program2: Program = ProgramFactory( + budget=200, + start_date="2022-01-10", + end_date="2022-09-10", + business_area=self.business_area, + population_goal=200, + status=Program.DRAFT, + ) + program1.refresh_from_db() + program2.refresh_from_db() + + # program from another BA + ProgramFactory( + budget=200, + start_date="2022-01-10", + end_date="2022-09-10", + business_area=BusinessAreaFactory(name="Ukraine"), + population_goal=400, + status=Program.ACTIVE, + ) + + response = self.client.get(self.list_url) + assert response.status_code == 403 + with token_grant_permission(self.token, Grant.API_READ_ONLY): response = self.client.get(self.list_url) + self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.json()), 1) - self.assertDictEqual( - response.json()[0], + self.assertEqual(len(response.json()), 2) + self.assertIn( { - "budget": "10000.00", - "cash_plus": program.cash_plus, - "end_date": "2022-09-12", - "frequency_of_payments": program.frequency_of_payments, - "id": str(program.id), - "name": program.name, - "population_goal": 200, - "sector": program.sector, - "start_date": "2022-01-12", + "budget": str(program1.budget), + "cash_plus": program1.cash_plus, + "end_date": program1.end_date.strftime("%Y-%m-%d"), + "frequency_of_payments": program1.frequency_of_payments, + "id": str(program1.id), + "name": program1.name, + "population_goal": program1.population_goal, + "sector": program1.sector, + "start_date": program1.start_date.strftime("%Y-%m-%d"), + }, + response.json(), + ) + self.assertIn( + { + "budget": str(program2.budget), + "cash_plus": program2.cash_plus, + "end_date": program2.end_date.strftime("%Y-%m-%d"), + "frequency_of_payments": program2.frequency_of_payments, + "id": str(program2.id), + "name": program2.name, + "population_goal": program2.population_goal, + "sector": program2.sector, + "start_date": program2.start_date.strftime("%Y-%m-%d"), + }, + response.json(), + ) + + +class APIGlobalProgramTests(HOPEApiTestCase): + databases = {"default"} + user_permissions = [] + + @classmethod + def setUpTestData(cls) -> None: + super().setUpTestData() + cls.list_url = reverse("api:program-global-list") + + def test_list_program(self) -> None: + program1: Program = ProgramFactory( + budget=10000, + start_date="2022-01-12", + end_date="2022-09-12", + business_area=self.business_area, + population_goal=200, + status=Program.ACTIVE, + ) + program2: Program = ProgramFactory( + budget=200, + start_date="2022-01-10", + end_date="2022-09-10", + business_area=self.business_area, + population_goal=200, + status=Program.DRAFT, + ) + + # program from another BA - also listed as we do not filter by BA + business_area2 = BusinessAreaFactory(name="Ukraine") + program_from_another_ba = ProgramFactory( + budget=200, + start_date="2022-01-10", + end_date="2022-09-10", + business_area=business_area2, + population_goal=400, + status=Program.ACTIVE, + ) + program1.refresh_from_db() + program2.refresh_from_db() + program_from_another_ba.refresh_from_db() + + response = self.client.get(self.list_url) + assert response.status_code == 403 + + with token_grant_permission(self.token, Grant.API_READ_ONLY): + response = self.client.get(self.list_url) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.json()["results"]), 3) + self.assertIn( + { + "budget": str(program1.budget), + "business_area_code": self.business_area.code, + "cash_plus": program1.cash_plus, + "end_date": program1.end_date.strftime("%Y-%m-%d"), + "frequency_of_payments": program1.frequency_of_payments, + "id": str(program1.id), + "name": program1.name, + "population_goal": program1.population_goal, + "programme_code": program1.programme_code, + "scope": program1.scope, + "sector": program1.sector, + "status": program1.status, + "start_date": program1.start_date.strftime("%Y-%m-%d"), + }, + response.json()["results"], + ) + self.assertIn( + { + "budget": str(program2.budget), + "business_area_code": self.business_area.code, + "cash_plus": program2.cash_plus, + "end_date": program2.end_date.strftime("%Y-%m-%d"), + "frequency_of_payments": program2.frequency_of_payments, + "id": str(program2.id), + "name": program2.name, + "population_goal": program2.population_goal, + "programme_code": program2.programme_code, + "scope": program2.scope, + "sector": program2.sector, + "status": program2.status, + "start_date": program2.start_date.strftime("%Y-%m-%d"), + }, + response.json()["results"], + ) + self.assertIn( + { + "budget": str(program_from_another_ba.budget), + "business_area_code": business_area2.code, + "cash_plus": program_from_another_ba.cash_plus, + "end_date": program_from_another_ba.end_date.strftime("%Y-%m-%d"), + "frequency_of_payments": program_from_another_ba.frequency_of_payments, + "id": str(program_from_another_ba.id), + "name": program_from_another_ba.name, + "population_goal": program_from_another_ba.population_goal, + "programme_code": program_from_another_ba.programme_code, + "scope": program_from_another_ba.scope, + "sector": program_from_another_ba.sector, + "status": program_from_another_ba.status, + "start_date": program_from_another_ba.start_date.strftime("%Y-%m-%d"), }, + response.json()["results"], ) diff --git a/backend/hct_mis_api/api/urls.py b/backend/hct_mis_api/api/urls.py index 339ab63584..eb2a035d1d 100644 --- a/backend/hct_mis_api/api/urls.py +++ b/backend/hct_mis_api/api/urls.py @@ -8,6 +8,7 @@ from hct_mis_api.api import endpoints from hct_mis_api.api.endpoints.base import ConstanceSettingsAPIView +from hct_mis_api.api.endpoints.program.views import ProgramGlobalListView from hct_mis_api.api.router import APIRouter app_name = "api" @@ -35,6 +36,8 @@ ), path("lookups/role/", endpoints.lookups.Roles().as_view(), name="role-list"), path("lookups/sex/", endpoints.lookups.Sex().as_view(), name="sex-list"), + path("business_areas/", endpoints.core.BusinessAreaListView.as_view(), name="business-area-list"), + path("programs/", ProgramGlobalListView.as_view(), name="program-global-list"), path( "/", include(