Skip to content

Commit

Permalink
Merge pull request #4025 from unicef/api-endpoints-list-ba-and-program
Browse files Browse the repository at this point in the history
[207085] New REST API endpoints - Program and BA list
  • Loading branch information
pkujawa authored Jul 10, 2024
2 parents 0b9f2db + 2d57cf1 commit 511211d
Show file tree
Hide file tree
Showing 10 changed files with 321 additions and 14 deletions.
1 change: 1 addition & 0 deletions backend/hct_mis_api/api/endpoints/__init__.py
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions backend/hct_mis_api/api/endpoints/core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from hct_mis_api.api.endpoints.core.views import BusinessAreaListView # noqa: F401
18 changes: 18 additions & 0 deletions backend/hct_mis_api/api/endpoints/core/serializers.py
Original file line number Diff line number Diff line change
@@ -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",
)
10 changes: 10 additions & 0 deletions backend/hct_mis_api/api/endpoints/core/views.py
Original file line number Diff line number Diff line change
@@ -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()
Empty file.
25 changes: 25 additions & 0 deletions backend/hct_mis_api/api/endpoints/program/serializers.py
Original file line number Diff line number Diff line change
@@ -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",
)
10 changes: 10 additions & 0 deletions backend/hct_mis_api/api/endpoints/program/views.py
Original file line number Diff line number Diff line change
@@ -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()
94 changes: 94 additions & 0 deletions backend/hct_mis_api/api/tests/test_business_area.py
Original file line number Diff line number Diff line change
@@ -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"],
)
173 changes: 159 additions & 14 deletions backend/hct_mis_api/api/tests/test_program.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 = []

Expand Down Expand Up @@ -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"],
)
3 changes: 3 additions & 0 deletions backend/hct_mis_api/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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(
"<slug:business_area>/",
include(
Expand Down

0 comments on commit 511211d

Please sign in to comment.