Skip to content

Commit

Permalink
Merge pull request #58 from rcpch:testing
Browse files Browse the repository at this point in the history
Testing
  • Loading branch information
eatyourpeas authored Dec 21, 2024
2 parents cb7bab6 + 8631f88 commit 0ae8921
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 13 deletions.
2 changes: 0 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: "3.11"

x-global-environment: &global
env_file:
- envs/.env # env file - NOT committed to Git
Expand Down
3 changes: 3 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[pytest]
DJANGO_SETTINGS_MODULE = rcpch_nhs_organisations.settings
python_files = tests.py test_*.py *_tests.py
1 change: 1 addition & 0 deletions rcpch_nhs_organisations/hospitals/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
)
from .local_authority_district import (
LocalAuthorityDistrictSerializer,
LocalAuthorityDistrictGeoJSONSerializer,
)
from .local_health_board import (
LocalHealthBoardSerializer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
)
]
)
class LocalAuthorityDistrictSerializer(GeoFeatureModelSerializer):
class LocalAuthorityDistrictGeoJSONSerializer(GeoFeatureModelSerializer):
class Meta:
model = LocalAuthorityDistrict
geo_field = "geom"
Expand All @@ -41,3 +41,26 @@ class Meta:
"globalid",
"geom",
]


@extend_schema_serializer(
examples=[
OpenApiExample(
"/local_authority_district/2024/1/extended",
value={
"lad24cd": "",
"lad24nm": "",
"lad24nmw": "",
"bng_e": "",
"bng_n": "",
"long": "",
"lat": "",
},
response_only=True,
)
]
)
class LocalAuthorityDistrictSerializer(serializers.ModelSerializer):
class Meta:
model = LocalAuthorityDistrict
fields = ["lad24cd", "lad24nm", "lad24nmw", "bng_e", "bng_n", "long", "lat"]
108 changes: 108 additions & 0 deletions rcpch_nhs_organisations/hospitals/tests/test_viewsets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import pytest
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from django.contrib.gis.geos import Point, MultiPolygon, Polygon
from django.apps import apps


@pytest.fixture
def api_client():
return APIClient()


LocalAuthorityDistrict = apps.get_model("hospitals", "LocalAuthorityDistrict")


@pytest.fixture
def local_authority_districts():
LocalAuthorityDistrict.objects.all().delete() # Clear the table
lad1 = LocalAuthorityDistrict.objects.create(
lad24cd="LAD001",
lad24nm="Test District 1",
lad24nmw="Test District 1 Welsh",
bng_e=350000,
bng_n=400000,
long=-3.0,
lat=53.0,
globalid="globalid1",
geom=MultiPolygon(
Polygon(
(
(349900, 400100),
(349900, 399900),
(350100, 399900),
(350100, 400100),
(349900, 400100),
)
)
),
)
lad2 = LocalAuthorityDistrict.objects.create(
lad24cd="LAD002",
lad24nm="Test District 2",
lad24nmw="Test District 2 Welsh",
bng_e=350100,
bng_n=400100,
long=-3.0,
lat=53.0,
globalid="globalid2",
geom=MultiPolygon(
Polygon(
(
(350000, 400200),
(350000, 400000),
(350200, 400000),
(350200, 400200),
(350000, 400200),
)
)
),
)
lad3 = LocalAuthorityDistrict.objects.create(
lad24cd="LAD003",
lad24nm="Test District 3",
lad24nmw="Test District 3 Welsh",
bng_e=350200,
bng_n=400200,
long=-3.0,
lat=53.0,
globalid="globalid3",
geom=MultiPolygon(
Polygon(
(
(350100, 400300),
(350100, 400100),
(350300, 400100),
(350300, 400300),
(350100, 400300),
)
)
),
)
return [lad1, lad2, lad3]


@pytest.mark.django_db
def test_list_local_authority_districts(api_client, local_authority_districts):
url = reverse("local_authority_district-list")
response = api_client.get(url)

assert response.status_code == status.HTTP_200_OK
assert len(response.data["features"]) == 3


@pytest.mark.django_db
def test_within_radius(api_client, local_authority_districts):
url = reverse("local_authority_district-within-radius")
response = api_client.get(
url, {"lat": 53.0, "long": -3.0, "radius": 500000}
) # within 500km
assert response.status_code == status.HTTP_200_OK
assert len(response.data["features"]) == 3

response = api_client.get(
url, {"lat": 53.0, "long": -3.0, "radius": 5}
) # within 5 km
assert response.status_code == status.HTTP_200_OK
assert len(response.data["features"]) == 0
90 changes: 80 additions & 10 deletions rcpch_nhs_organisations/hospitals/views/local_authority_district.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from rest_framework.response import Response
from django.contrib.gis.geos import Point
from django.contrib.gis.db.models.functions import Distance
from django.contrib.gis.db.models.functions import Distance, Transform
from django.contrib.gis.measure import D
from rest_framework.decorators import action
from rest_framework import (
viewsets,
Expand All @@ -19,7 +20,10 @@
from drf_spectacular.types import OpenApiTypes

from ..models import LocalAuthorityDistrict
from ..serializers import LocalAuthorityDistrictSerializer
from ..serializers import (
LocalAuthorityDistrictSerializer,
LocalAuthorityDistrictGeoJSONSerializer,
)


@extend_schema(
Expand All @@ -42,19 +46,32 @@
"long": -1.270225,
"lat": 54.676159,
"globalid": "{F1D3D2A4-1D4D-4D3D-8D3D-3D1D4D3D1D4D}",
"geom": [],
"geom": {
"type": "MultiPolygon",
"coordinates": [
[
[
[-1.270225, 54.676159],
[-1.270225, 54.676159],
[-1.270225, 54.676159],
[-1.270225, 54.676159],
[-1.270225, 54.676159],
]
]
],
},
}
],
response_only=True,
),
],
),
},
summary="This endpoint returns a list of Local Authority Districts with their boundaries, or an individual authority districts.",
summary="This endpoint returns a list of Local Authority Districts with their boundaries, or an individual local authority district.",
)
class LocalAuthorityDistrictViewSet(viewsets.ReadOnlyModelViewSet):
"""
This endpoint returns a list of Local Health Boards (Wales) with their boundaries, or an individual local health authority by LAD24CD.
This endpoint returns a list of Local Authority Districts (2024 publication) with their boundaries, or an individual local health authority by LAD24CD.
Filter Parameters:
Expand All @@ -66,14 +83,13 @@ class LocalAuthorityDistrictViewSet(viewsets.ReadOnlyModelViewSet):
`long`
`lat`
`globalid`
`geom`
If none are passed, a list is returned.
"""

queryset = LocalAuthorityDistrict.objects.all().order_by("-lad24nm")
serializer_class = LocalAuthorityDistrictSerializer
serializer_class = LocalAuthorityDistrictGeoJSONSerializer
lookup_field = "lad24cd"
filterset_fields = [
"lad24cd",
Expand All @@ -87,6 +103,52 @@ class LocalAuthorityDistrictViewSet(viewsets.ReadOnlyModelViewSet):
]
filter_backends = (DjangoFilterBackend,)

def get_serializer_class(self):
if self.action == "within_radius":
return LocalAuthorityDistrictSerializer
return super().get_serializer_class()

@extend_schema(
parameters=[
OpenApiParameter(
name="lat",
type=OpenApiTypes.NUMBER,
description="Latitude of the center point",
),
OpenApiParameter(
name="long",
type=OpenApiTypes.NUMBER,
description="Longitude of the center point",
),
OpenApiParameter(
name="radius", type=OpenApiTypes.NUMBER, description="Radius in meters"
),
],
responses={200: LocalAuthorityDistrictGeoJSONSerializer(many=True)},
summary="Get Local Authority Districtsand boundaries within a radius",
description="This endpoint returns a list of Local Authority Districts within a specified radius from a given latitude and longitude. It also returns geojson boundaries for each district.",
)
@action(detail=False, methods=["get"])
def within_radius_with_geography(self, request):
try:
lat = float(request.query_params.get("lat"))
long = float(request.query_params.get("long"))
radius = float(request.query_params.get("radius"))
except (TypeError, ValueError):
return Response({"error": "Invalid parameters"}, status=400)

user_location = Point(long, lat, srid=4326)
# the reference system of the geom field is 27700 - transform to 4326 before calculating distance

queryset = (
self.queryset.annotate(geom_4326=Transform("geom", 4326))
.annotate(distance=Distance("geom_4326", user_location))
.filter(distance__lte=D(m=radius)) # distance is in meters
)

serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)

@extend_schema(
parameters=[
OpenApiParameter(
Expand Down Expand Up @@ -117,8 +179,16 @@ def within_radius(self, request):
return Response({"error": "Invalid parameters"}, status=400)

user_location = Point(long, lat, srid=4326)
queryset = self.queryset.annotate(
distance=Distance("geom", user_location)
).filter(distance__lte=radius)
# the reference system of the geom field is 27700 - transform to 4326 before calculating distance
log_queryset = self.queryset.annotate(
geom_4326=Transform("geom", 4326)
).annotate(distance=Distance("geom_4326", user_location))

queryset = (
self.queryset.annotate(geom_4326=Transform("geom", 4326))
.annotate(distance=Distance("geom_4326", user_location))
.filter(distance__lte=D(m=radius)) # distance is in meters
)

serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
1 change: 1 addition & 0 deletions requirements/development-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
autopep8
coverage
black
pytest
pytest-django

# third party imports
Expand Down

0 comments on commit 0ae8921

Please sign in to comment.