Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/count service unit by division #294

Merged
merged 5 commits into from
Aug 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 52 additions & 25 deletions services/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,32 @@ def root_service_nodes(services):
)


def resolve_divisions(divisions):
div_list = []
for division_path in divisions:
if division_path.startswith("ocd-division"):
muni_ocd_id = division_path
else:
ocd_id_base = r"[\w0-9~_.-]+"
match_re = r"(%s)/([\w_-]+):(%s)" % (ocd_id_base, ocd_id_base)
m = re.match(match_re, division_path, re.U)
if not m:
raise ParseError("'division' must be of form 'muni/type:id'")

arr = division_path.split("/")
muni_ocd_id = make_muni_ocd_id(arr.pop(0), "/".join(arr))
try:
div = AdministrativeDivision.objects.select_related("geometry").get(
ocd_id=muni_ocd_id
)
except AdministrativeDivision.DoesNotExist:
raise ParseError(
"administrative division with OCD ID '%s' not found" % muni_ocd_id
)
div_list.append(div)
return div_list


class JSONAPISerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super(JSONAPISerializer, self).__init__(*args, **kwargs)
Expand Down Expand Up @@ -300,7 +326,13 @@ def root_service_nodes(self, obj):

class Meta:
model = ServiceNode
fields = "__all__"
exclude = (
"search_column_fi",
"search_column_sv",
"search_column_en",
"syllables_fi",
"service_reference",
)


class ServiceSerializer(TranslatedModelSerializer, JSONAPISerializer):
Expand All @@ -315,6 +347,16 @@ def to_representation(self, obj):
total += unit_count.count
ret["unit_count"]["municipality"][div_name] = unit_count.count
ret["unit_count"]["total"] = total

divisions = self.context.get("divisions", [])
include_fields = self.context.get("include", [])
if "unit_count_per_division" in include_fields and divisions:
ret["unit_count_per_division"] = {}
div_list = resolve_divisions(divisions)
for div in div_list:
ret["unit_count_per_division"][div.name] = Unit.objects.filter(
services=obj.pk, location__within=div.geometry.boundary
).count()
return ret

class Meta:
Expand Down Expand Up @@ -530,6 +572,13 @@ class ServiceViewSet(JSONAPIViewSet, viewsets.ReadOnlyModelViewSet):
queryset = Service.objects.all()
serializer_class = ServiceSerializer

def get_serializer_context(self):
ret = super(ServiceViewSet, self).get_serializer_context()
query_params = self.request.query_params
division = query_params.get("division", "")
ret["divisions"] = [x.strip() for x in division.split(",") if x]
return ret

def get_queryset(self):
queryset = (
super(ServiceViewSet, self)
Expand Down Expand Up @@ -720,6 +769,7 @@ class Meta:
"accessibility_property_hash",
"identifier_hash",
"public",
"syllables_fi",
"search_column_fi",
"search_column_sv",
"search_column_en",
Expand Down Expand Up @@ -916,30 +966,7 @@ def validate_service_node_ids(service_node_ids):
# Divisions can be specified with form:
# division=helsinki/kaupunginosa:kallio,vantaa/äänestysalue:5
d_list = filters["division"].lower().split(",")
div_list = []
for division_path in d_list:
if division_path.startswith("ocd-division"):
muni_ocd_id = division_path
else:
ocd_id_base = r"[\w0-9~_.-]+"
match_re = r"(%s)/([\w_-]+):(%s)" % (ocd_id_base, ocd_id_base)
m = re.match(match_re, division_path, re.U)
if not m:
raise ParseError("'division' must be of form 'muni/type:id'")

arr = division_path.split("/")
muni_ocd_id = make_muni_ocd_id(arr.pop(0), "/".join(arr))
try:
div = AdministrativeDivision.objects.select_related("geometry").get(
ocd_id=muni_ocd_id
)
except AdministrativeDivision.DoesNotExist:
raise ParseError(
"administrative division with OCD ID '%s' not found"
% muni_ocd_id
)
div_list.append(div)

div_list = resolve_divisions(d_list)
if div_list:
mp = div_list.pop(0).geometry.boundary
for div in div_list:
Expand Down
23 changes: 21 additions & 2 deletions specification.swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,25 @@ paths:
schema:
type: integer
example: 811
- $ref: "#/components/parameters/include_param"
- name: include
in: query
style: form
explode: false
description: "Enable count service by division with: "include=unit_count_per_division""
type: string
- name: division
in: query
style: form
explode: false
description: A comma-separated list of administrative divisions to be used when unit
counting by service and division. Use either full division ids or shorthands of the form
muni/type\:id
required: false
schema:
type: array
items:
type: string
example: ocd-division/country:fi/kunta:raisio
- $ref: "#/components/parameters/only_param"
- $ref: "#/components/parameters/geometry_param"
/service/:
Expand All @@ -313,7 +331,7 @@ paths:
- service
parameters:
- $ref: "#/components/parameters/page_param"
- $ref: "#/components/parameters/pagesize_param"
- $ref: "#/components/parameters/pagesize_param"
- name: id
in: query
style: form
Expand All @@ -324,6 +342,7 @@ paths:
items:
type: integer
example: 811,663

responses:
"200":
description: List of services, paginated
Expand Down