Skip to content

Commit af58ffa

Browse files
authored
Merge pull request #212 from praekeltfoundation/add-clinic-lookup
Add clinic lookup
2 parents 786a253 + ef63cef commit af58ffa

File tree

15 files changed

+227
-1
lines changed

15 files changed

+227
-1
lines changed

config/settings/base.py

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"randomisation",
4949
"msisdn_utils",
5050
"turn_alerts",
51+
"momconnect",
5152
]
5253

5354
MIDDLEWARE = [

config/urls.py

+1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@
1111
path("yal/", include("rp_yal.urls"), name="rp_yal"),
1212
path("msisdn_utils/", include("msisdn_utils.urls")),
1313
path("turnalerts/", include("turn_alerts.urls"), name="turn_alerts"),
14+
path("momconnect/", include("momconnect.urls"), name="momconnect"),
1415
]

docs/apps/momconnect.md

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# MomConnect App
2+
3+
The `momconnect` app is responsible for all momconnect specific functionality, including:
4+
- **Clinic-related operations and integrations within the project**: It provides APIs and utilities to validate clinic codes and retrieve clinic details.
5+
6+
## Key Features
7+
8+
- **Clinic Management**: Handles the storage and retrieval of clinic information.
9+
- **Validation**: Includes utilities to validate clinic codes.
10+
- **API Endpoints**: Exposes endpoints for interacting with clinic data.
11+
12+
## Key Components
13+
14+
### Models
15+
- **Clinic**: Represents a clinic with attributes such as `value` (clinic code) and `name`.
16+
17+
### Views
18+
- **ClinicDetailsView**: API view to retrieve clinic details based on a provided clinic code.
19+
20+
### Utilities
21+
- **validate_clinic_code**: Utility function to validate the format of a clinic code.
22+
23+
## API Endpoints
24+
25+
### Clinic Details
26+
- **Endpoint**: `/momconnect/clinic-check/`
27+
- **Method**: `GET`
28+
- **Parameters**:
29+
- `clinic_code` (required): The code of the clinic to retrieve.
30+
- **Responses**:
31+
- `200 OK`: Returns the clinic details.
32+
- `400 Bad Request`: Missing or invalid clinic code.
33+
- `404 Not Found`: Clinic not found.
34+
35+
## Testing
36+
37+
The `momconnect` app includes comprehensive test coverage for its views and utilities. Tests are located in `momconnect/tests/`.

momconnect/__init__.py

Whitespace-only changes.

momconnect/apps.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.apps import AppConfig
2+
3+
4+
class MomconnectConfig(AppConfig):
5+
default_auto_field = "django.db.models.BigAutoField"
6+
name = "momconnect"

momconnect/management/commands/import_clinics.py

Whitespace-only changes.

momconnect/migrations/0001_initial.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Generated by Django 4.2.20 on 2025-03-25 08:31
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
initial = True
9+
10+
dependencies = [
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name='Clinic',
16+
fields=[
17+
('code', models.CharField(max_length=255)),
18+
('value', models.CharField(max_length=255)),
19+
('uid', models.CharField(max_length=255, primary_key=True, serialize=False)),
20+
('name', models.CharField(max_length=255)),
21+
('province', models.CharField(blank=True, default=None, max_length=6, null=True)),
22+
('location', models.CharField(blank=True, default=None, max_length=255, null=True)),
23+
('area_type', models.CharField(blank=True, default=None, max_length=50, null=True)),
24+
('unit_type', models.CharField(blank=True, default=None, max_length=50, null=True)),
25+
('district', models.CharField(blank=True, default=None, max_length=50, null=True)),
26+
('municipality', models.CharField(blank=True, default=None, max_length=50, null=True)),
27+
],
28+
options={
29+
'indexes': [models.Index(fields=['value'], name='momconnect__value_9a1252_idx')],
30+
},
31+
),
32+
]

momconnect/migrations/__init__.py

Whitespace-only changes.

momconnect/models.py

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from django.db import models
2+
3+
4+
class Clinic(models.Model):
5+
code = models.CharField(max_length=255)
6+
value = models.CharField(max_length=255)
7+
uid = models.CharField(max_length=255, primary_key=True)
8+
name = models.CharField(max_length=255)
9+
province = models.CharField(max_length=6, blank=True, null=True, default=None)
10+
location = models.CharField(max_length=255, blank=True, null=True, default=None)
11+
area_type = models.CharField(max_length=50, blank=True, null=True, default=None)
12+
unit_type = models.CharField(max_length=50, blank=True, null=True, default=None)
13+
district = models.CharField(max_length=50, blank=True, null=True, default=None)
14+
municipality = models.CharField(max_length=50, blank=True, null=True, default=None)
15+
16+
class Meta:
17+
indexes = [models.Index(fields=["value"])]

momconnect/tests/test_utils.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from django.test import TestCase
2+
3+
from momconnect.utils import clean_clinic_code, validate_clinic_code
4+
5+
6+
class TestUtils(TestCase):
7+
def test_clean_clinic_code(self):
8+
"""
9+
Test that the clean_clinic_code function removes spaces from the input string.
10+
"""
11+
self.assertEqual(clean_clinic_code("123 456"), "123456")
12+
self.assertEqual(clean_clinic_code(" 789 "), "789")
13+
self.assertEqual(clean_clinic_code("abc def"), "abcdef")
14+
self.assertEqual(clean_clinic_code(""), "")
15+
16+
def test_validate_clinic_code(self):
17+
"""
18+
Test that the validate_clinic_code function checks if the clinic code is valid.
19+
A valid clinic code must be exactly 6 digits long and contain only numbers.
20+
"""
21+
self.assertTrue(validate_clinic_code("123456"))
22+
self.assertTrue(validate_clinic_code(" 123 456 "))
23+
self.assertFalse(validate_clinic_code("12345"))
24+
self.assertFalse(validate_clinic_code("1234567"))
25+
self.assertFalse(validate_clinic_code("12345a"))
26+
self.assertFalse(validate_clinic_code(""))
27+
self.assertFalse(validate_clinic_code(" "))

momconnect/tests/test_views.py

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from django.contrib.auth.models import User
2+
from django.urls import reverse
3+
from rest_framework import status
4+
from rest_framework.authtoken.models import Token
5+
from rest_framework.test import APIClient, APITestCase
6+
7+
from momconnect.models import Clinic
8+
9+
10+
class ClinicDetailsViewTests(APITestCase):
11+
def setUp(self):
12+
self.api_client = APIClient()
13+
14+
self.user = User.objects.create_user(
15+
"username", "[email protected]", "password"
16+
)
17+
token = Token.objects.get(user=self.user)
18+
self.api_client.credentials(HTTP_AUTHORIZATION="Token " + token.key)
19+
20+
self.clinic = Clinic.objects.create(value="123456", name="Test Clinic")
21+
self.url = reverse("clinic-check")
22+
23+
def test_unauthenticated_client(self):
24+
"""
25+
Test that an unauthenticated client receives a 401 Unauthorized response
26+
when attempting to access the clinic details endpoint.
27+
"""
28+
response = self.client.get(self.url, {"clinic_code": self.clinic.value})
29+
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
30+
31+
def test_missing_clinic_code(self):
32+
"""
33+
Test that the endpoint returns a 400 Bad Request response
34+
when the clinic_code parameter is missing.
35+
"""
36+
response = self.api_client.get(self.url)
37+
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
38+
self.assertEqual(response.data, {"error": "missing clinic code"})
39+
40+
def test_invalid_clinic_code(self):
41+
"""
42+
Test that the endpoint returns a 400 Bad Request response
43+
when the clinic_code parameter is invalid.
44+
"""
45+
response = self.api_client.get(self.url, {"clinic_code": "invalid"})
46+
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
47+
self.assertEqual(response.data, {"error": "invalid"})
48+
49+
def test_clinic_not_found(self):
50+
"""
51+
Test that the endpoint returns a 404 Not Found response
52+
when the clinic with the provided clinic_code does not exist.
53+
"""
54+
response = self.api_client.get(self.url, {"clinic_code": "999999"})
55+
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
56+
self.assertEqual(response.data, {"error": "clinic not found"})
57+
58+
def test_successful_clinic_retrieval(self):
59+
"""
60+
Test that the endpoint successfully retrieves clinic details
61+
when a valid clinic_code is provided.
62+
"""
63+
response = self.api_client.get(self.url, {"clinic_code": self.clinic.value})
64+
self.assertEqual(response.status_code, status.HTTP_200_OK)
65+
self.assertEqual(response.data["code"], self.clinic.value)
66+
self.assertEqual(response.data["name"], self.clinic.name)

momconnect/urls.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from django.urls import path
2+
3+
from .views import ClinicDetailsView
4+
5+
urlpatterns = [
6+
path("clinic-check", ClinicDetailsView.as_view(), name="clinic-check"),
7+
]

momconnect/utils.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
def clean_clinic_code(clinic_code):
2+
return str(clinic_code).replace(" ", "")
3+
4+
5+
def validate_clinic_code(clinic_code):
6+
clinic_code = clean_clinic_code(clinic_code)
7+
return clinic_code.isdigit() and len(clinic_code) == 6

momconnect/views.py

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from rest_framework import generics, status
2+
from rest_framework.response import Response
3+
4+
from .models import Clinic
5+
from .utils import validate_clinic_code
6+
7+
8+
class ClinicDetailsView(generics.RetrieveAPIView):
9+
def get(self, request):
10+
try:
11+
clinic_code = request.query_params["clinic_code"]
12+
except KeyError:
13+
return Response(
14+
{"error": "missing clinic code"}, status.HTTP_400_BAD_REQUEST
15+
)
16+
17+
if not validate_clinic_code(clinic_code):
18+
return Response({"error": "invalid"}, status.HTTP_400_BAD_REQUEST)
19+
20+
try:
21+
clinic = Clinic.objects.get(value=clinic_code)
22+
except Clinic.DoesNotExist:
23+
return Response({"error": "clinic not found"}, status.HTTP_404_NOT_FOUND)
24+
25+
return Response({"code": clinic.value, "name": clinic.name})

uv.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)