Skip to content

Commit 63f1a50

Browse files
committed
Filters and tests for Health units
1 parent 167c32d commit 63f1a50

File tree

3 files changed

+182
-0
lines changed

3 files changed

+182
-0
lines changed

local_units/filterset.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,29 @@ class Meta:
5757
"country__iso": ["exact", "in"],
5858
"country__id": ["exact", "in"],
5959
}
60+
61+
62+
class HealthLocalUnitFilters(filters.FilterSet):
63+
# Simple filters for health-local-units endpoint
64+
region = filters.NumberFilter(field_name="country__region_id", label="Region")
65+
country = filters.NumberFilter(field_name="country_id", label="Country")
66+
iso3 = filters.CharFilter(field_name="country__iso3", lookup_expr="exact", label="ISO3")
67+
validated = filters.BooleanFilter(method="filter_validated", label="Validated")
68+
subtype = filters.CharFilter(field_name="subtype", lookup_expr="icontains", label="Subtype")
69+
70+
class Meta:
71+
model = LocalUnit
72+
fields = (
73+
"region",
74+
"country",
75+
"iso3",
76+
"validated",
77+
"subtype",
78+
)
79+
80+
def filter_validated(self, queryset, name, value):
81+
if value is True:
82+
return queryset.filter(status=LocalUnit.Status.VALIDATED)
83+
if value is False:
84+
return queryset.exclude(status=LocalUnit.Status.VALIDATED)
85+
return queryset

local_units/test_views.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,3 +1580,157 @@ def test_empty_health_template_file(cls):
15801580
cls.assertIsNotNone(cls.bulk_upload.error_message)
15811581
cls.assertEqual(LocalUnit.objects.count(), 5)
15821582
cls.assertEqual(HealthData.objects.count(), 5)
1583+
1584+
1585+
class TestHealthLocalUnitsPublicList(APITestCase):
1586+
"""
1587+
Tests for the public, flattened health local units endpoint: /api/v2/health-local-units/
1588+
Only add new tests; existing code remains untouched.
1589+
"""
1590+
1591+
def setUp(self):
1592+
super().setUp()
1593+
# Regions and countries
1594+
self.region1 = RegionFactory.create(name=2, label="Asia Pacific")
1595+
self.region2 = RegionFactory.create(name=1, label="Americas")
1596+
1597+
self.country1 = CountryFactory.create(name="Nepal", iso3="NPL", region=self.region1)
1598+
self.country2 = CountryFactory.create(name="Philippines", iso3="PHL", region=self.region1)
1599+
self.country3 = CountryFactory.create(name="Brazil", iso3="BRA", region=self.region2)
1600+
1601+
# Types
1602+
self.type_health = LocalUnitType.objects.create(code=2, name="Health")
1603+
self.type_admin = LocalUnitType.objects.create(code=1, name="Administrative")
1604+
1605+
# Lookups for HealthData
1606+
self.aff = Affiliation.objects.create(code=11, name="Public")
1607+
self.func = Functionality.objects.create(code=21, name="Functional")
1608+
self.ftype = FacilityType.objects.create(code=31, name="Clinic")
1609+
self.phcc = PrimaryHCC.objects.create(code=41, name="Primary")
1610+
self.htype = HospitalType.objects.create(code=51, name="District Hospital")
1611+
1612+
# Included: public, not deprecated, type=2 with health
1613+
self.hd1 = HealthDataFactory.create(
1614+
affiliation=self.aff,
1615+
functionality=self.func,
1616+
health_facility_type=self.ftype,
1617+
primary_health_care_center=self.phcc,
1618+
hospital_type=self.htype,
1619+
)
1620+
self.lu1 = LocalUnitFactory.create(
1621+
country=self.country1,
1622+
type=self.type_health,
1623+
health=self.hd1,
1624+
visibility=VisibilityChoices.PUBLIC,
1625+
is_deprecated=False,
1626+
status=LocalUnit.Status.VALIDATED,
1627+
subtype="District Clinic A",
1628+
)
1629+
1630+
self.hd2 = HealthDataFactory.create(
1631+
affiliation=self.aff,
1632+
functionality=self.func,
1633+
health_facility_type=self.ftype,
1634+
)
1635+
self.lu2 = LocalUnitFactory.create(
1636+
country=self.country2,
1637+
type=self.type_health,
1638+
health=self.hd2,
1639+
visibility=VisibilityChoices.PUBLIC,
1640+
is_deprecated=False,
1641+
status=LocalUnit.Status.UNVALIDATED,
1642+
subtype="Mobile Clinic",
1643+
)
1644+
1645+
# Exclusions
1646+
# - private visibility
1647+
LocalUnitFactory.create(
1648+
country=self.country1,
1649+
type=self.type_health,
1650+
health=HealthDataFactory.create(affiliation=self.aff, functionality=self.func, health_facility_type=self.ftype),
1651+
visibility=VisibilityChoices.MEMBERSHIP,
1652+
is_deprecated=False,
1653+
status=LocalUnit.Status.VALIDATED,
1654+
)
1655+
# - deprecated
1656+
LocalUnitFactory.create(
1657+
country=self.country1,
1658+
type=self.type_health,
1659+
health=HealthDataFactory.create(affiliation=self.aff, functionality=self.func, health_facility_type=self.ftype),
1660+
visibility=VisibilityChoices.PUBLIC,
1661+
is_deprecated=True,
1662+
status=LocalUnit.Status.VALIDATED,
1663+
)
1664+
# - wrong type (admin)
1665+
LocalUnitFactory.create(
1666+
country=self.country1,
1667+
type=self.type_admin,
1668+
health=None,
1669+
visibility=VisibilityChoices.PUBLIC,
1670+
is_deprecated=False,
1671+
status=LocalUnit.Status.VALIDATED,
1672+
)
1673+
# - no health
1674+
LocalUnitFactory.create(
1675+
country=self.country3,
1676+
type=self.type_health,
1677+
health=None,
1678+
visibility=VisibilityChoices.PUBLIC,
1679+
is_deprecated=False,
1680+
status=LocalUnit.Status.VALIDATED,
1681+
)
1682+
1683+
def test_list_public_health_local_units(self):
1684+
resp = self.client.get("/api/v2/health-local-units/")
1685+
self.assertEqual(resp.status_code, 200)
1686+
self.assertEqual(resp.data["count"], 2)
1687+
# Check a few flattened fields exist
1688+
first = resp.data["results"][0]
1689+
self.assertIn("country_name", first)
1690+
self.assertIn("country_iso3", first)
1691+
self.assertEqual(first["type_code"], 2)
1692+
self.assertIn("location", first)
1693+
self.assertIn("affiliation", first)
1694+
self.assertIn("functionality", first)
1695+
self.assertIn("health_facility_type", first)
1696+
# health_facility_type may include image_url; assert at least name exists when present
1697+
if first["health_facility_type"] is not None:
1698+
self.assertIn("name", first["health_facility_type"])
1699+
1700+
def test_filters_region_country_iso3_validated_subtype(self):
1701+
# region -> both country1 and country2 are in region1
1702+
resp = self.client.get(f"/api/v2/health-local-units/?region={self.region1.id}")
1703+
self.assertEqual(resp.status_code, 200)
1704+
self.assertEqual(resp.data["count"], 2)
1705+
1706+
# country
1707+
resp = self.client.get(f"/api/v2/health-local-units/?country={self.country1.id}")
1708+
self.assertEqual(resp.status_code, 200)
1709+
self.assertEqual(resp.data["count"], 1)
1710+
self.assertEqual(resp.data["results"][0]["country_iso3"], "NPL")
1711+
1712+
# iso3
1713+
resp = self.client.get("/api/v2/health-local-units/?iso3=PHL")
1714+
self.assertEqual(resp.status_code, 200)
1715+
self.assertEqual(resp.data["count"], 1)
1716+
self.assertEqual(resp.data["results"][0]["country_iso3"], "PHL")
1717+
1718+
# validated true
1719+
resp = self.client.get("/api/v2/health-local-units/?validated=true")
1720+
self.assertEqual(resp.status_code, 200)
1721+
self.assertEqual(resp.data["count"], 1)
1722+
self.assertEqual(resp.data["results"][0]["status"], LocalUnit.Status.VALIDATED)
1723+
1724+
# validated false
1725+
resp = self.client.get("/api/v2/health-local-units/?validated=false")
1726+
self.assertEqual(resp.status_code, 200)
1727+
self.assertEqual(resp.data["count"], 1)
1728+
self.assertEqual(resp.data["results"][0]["status"], LocalUnit.Status.UNVALIDATED)
1729+
1730+
# subtype icontains
1731+
resp = self.client.get("/api/v2/health-local-units/?subtype=mobile")
1732+
self.assertEqual(resp.status_code, 200)
1733+
self.assertEqual(resp.data["count"], 1)
1734+
self.assertEqual(resp.data["results"][0]["subtype"].lower(), "mobile clinic".lower())
1735+
1736+
# End of relevant assertions for this test.

local_units/views.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from local_units.filterset import (
1414
DelegationOfficeFilters,
1515
ExternallyManagedLocalUnitFilters,
16+
HealthLocalUnitFilters,
1617
LocalUnitBulkUploadFilters,
1718
LocalUnitFilters,
1819
)
@@ -363,6 +364,7 @@ class HealthLocalUnitViewSet(viewsets.ReadOnlyModelViewSet):
363364

364365
serializer_class = HealthLocalUnitFlatSerializer
365366
http_method_names = ["get", "head", "options"]
367+
filterset_class = HealthLocalUnitFilters
366368

367369
queryset = (
368370
LocalUnit.objects.select_related(

0 commit comments

Comments
 (0)