From 6e0901ed02c6e00e5c90cc3b4eea787809e503dc Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Fri, 18 Oct 2024 11:46:32 +0300 Subject: [PATCH 01/11] Add details parameter to check_parking Note, does not return permits in permissions --- parkings/api/enforcement/check_parking.py | 40 ++++++- .../api/enforcement/test_check_parking.py | 111 +++++++++++++++++- 2 files changed, 146 insertions(+), 5 deletions(-) diff --git a/parkings/api/enforcement/check_parking.py b/parkings/api/enforcement/check_parking.py index 12499717..2a10b67a 100644 --- a/parkings/api/enforcement/check_parking.py +++ b/parkings/api/enforcement/check_parking.py @@ -35,6 +35,7 @@ class CheckParkingSerializer(serializers.Serializer): registration_number = serializers.CharField(max_length=20) location = LocationSerializer() time = AwareDateTimeField(required=False) + details = serializers.ListSerializer(child=serializers.CharField(), required=False) class CheckParking(generics.GenericAPIView): @@ -84,6 +85,23 @@ def post(self, request): }, "time": time, } + + operator_detail, time_start_detail, permissions_detail = get_details(params) + + if operator_detail: + result["operator"] = parking.operator.name if parking and parking.operator else None + + if time_start_detail: + result["time_start"] = parking.time_start if parking and parking.time_start else None + + if permissions_detail: + if isinstance(parking, EventParking): + permissions = {"event_area": parking.event_area.origin_id if parking and parking.event_area else None, + "zone": None} + else: + permissions = {"event_area": None, "zone": parking.zone.number if parking and parking.zone else None} + result["permissions"] = permissions + filter = { "performer": request.user, "time": time, @@ -102,6 +120,22 @@ def post(self, request): return Response(result) +def get_details(params): + details = params.get("details", []) + operator = False + time_start = False + permissions = False + + for detail in [d.lower() for d in details]: + if detail == "operator": + operator = True + if detail == "time_start": + time_start = True + if detail == "permissions": + permissions = True + return operator, time_start, permissions + + def get_location(params): longitude = params["location"]["longitude"] latitude = params["location"]["latitude"] @@ -178,6 +212,10 @@ def check_parking(registration_number, zone, area, time, domain, event_area): if permit_end_time: return ("permit", None, permit_end_time) + for parking in active_parkings: + if parking.zone.number > zone: + return (None, parking, parking.time_end) + active_event_parking = ( EventParking.objects .registration_number_like(registration_number) @@ -187,7 +225,7 @@ def check_parking(registration_number, zone, area, time, domain, event_area): if active_event_parking: if active_event_parking.event_area == event_area: - return ("event parking", active_event_parking, active_event_parking.time_end) + return ("event_parking", active_event_parking, active_event_parking.time_end) else: # Event parking not parked in the assigned event area, i.e., not allowed. return (None, active_event_parking, active_event_parking.time_end) diff --git a/parkings/tests/api/enforcement/test_check_parking.py b/parkings/tests/api/enforcement/test_check_parking.py index f6a2ea8b..b57b1c42 100644 --- a/parkings/tests/api/enforcement/test_check_parking.py +++ b/parkings/tests/api/enforcement/test_check_parking.py @@ -2,6 +2,7 @@ import datetime import json +from copy import deepcopy from datetime import timedelta import pytest @@ -109,20 +110,46 @@ def test_check_parking_allowed_event_parking(enforcer_api_client, event_parking_ registration_number=event_parking.registration_number).first().result["allowed"] is True -def test_check_parking_not_allowed_event_parking( +def test_check_parking_allowed_event_parking_details( + enforcer_api_client, event_parking_factory, enforcer, event_area_factory): + event_area = event_area_factory.create(geom=create_area_geom(), domain=enforcer.enforced_domain) + event_parking = event_parking_factory(registration_number="ABC-123", + domain=enforcer.enforced_domain, event_area=event_area) + data = deepcopy(PARKING_DATA) + data["details"] = ["operator", "time_start", "permissions"] + response = enforcer_api_client.post(list_url, data=data) + + assert response.status_code == HTTP_200_OK + assert response.data["allowed"] is True + assert response.data["end_time"] == event_parking.time_end + assert response.data["permissions"]["event_area"] == event_area.origin_id + assert response.data["operator"] == event_parking.operator.name + assert response.data["time_start"] == event_parking.time_start + assert ParkingCheck.objects.filter( + registration_number=event_parking.registration_number).first().result["allowed"] is True + + +def test_check_parking_not_allowed_event_parking_details( enforcer_api_client, event_parking_factory, enforcer, event_area_factory): event_area = event_area_factory.create(geom=create_area_geom(), domain=enforcer.enforced_domain) event_parking_factory(registration_number="CBA-123", domain=enforcer.enforced_domain, event_area=event_area) - response = enforcer_api_client.post(list_url, data=PARKING_DATA) + data = deepcopy(PARKING_DATA) + data["details"] = ["operator", "time_start", "permissions"] + + response = enforcer_api_client.post(list_url, data=data) assert response.status_code == HTTP_200_OK assert response.data["allowed"] is False assert response.data["end_time"] is None + assert response.data["permissions"]["event_area"] is None + assert response.data["permissions"]["zone"] is None + assert response.data["operator"] is None + assert response.data["time_start"] is None assert ParkingCheck.objects.filter( registration_number=PARKING_DATA["registration_number"]).first().result["allowed"] is False -def test_check_event_parking_parked_in_wrong_event_area( +def test_check_event_parking_parked_in_wrong_event_area_include_details( enforcer_api_client, event_parking_factory, enforcer, event_area_factory): event_area_1 = event_area_factory.create(geom=create_area_geom(), domain=enforcer.enforced_domain) event_area_2 = event_area_factory.create(geom=create_area_geom(geom=GEOM_2), domain=enforcer.enforced_domain) @@ -130,11 +157,18 @@ def test_check_event_parking_parked_in_wrong_event_area( location = Point(PARKING_DATA_2["location"]["longitude"], PARKING_DATA_2["location"]["latitude"], srid=WGS84_SRID) event_parking = event_parking_factory(registration_number="ABC-123", domain=enforcer.enforced_domain, event_area=event_area_1, location=location) - response = enforcer_api_client.post(list_url, data=PARKING_DATA_2) + data = deepcopy(PARKING_DATA_2) + data["details"] = ["operator", "time_start", "permissions"] + response = enforcer_api_client.post(list_url, data=data) + assert response.status_code == HTTP_200_OK assert response.data["location"]["event_area"] == event_area_2.id assert response.data["allowed"] is False assert response.data["end_time"] == event_parking.time_end + assert response.data["permissions"]["event_area"] == event_area_1.origin_id + assert response.data["permissions"]["zone"] is None + assert response.data["operator"] == event_parking.operator.name + assert response.data["time_start"] == event_parking.time_start assert ParkingCheck.objects.filter( registration_number=PARKING_DATA["registration_number"]).first().result["allowed"] is False @@ -188,6 +222,75 @@ def test_check_parking_valid_parking(operator, enforcer, enforcer_api_client, pa assert response.data["end_time"] == parking.time_end +def test_check_parking_details_operator_parameter(operator, enforcer, enforcer_api_client, parking_factory): + zone = create_payment_zone(domain=enforcer.enforced_domain) + parking = parking_factory(registration_number="ABC-123", operator=operator, zone=zone, domain=zone.domain) + data = deepcopy(PARKING_DATA) + data["details"] = ["operator"] + response = enforcer_api_client.post(list_url, data=data) + + assert response.status_code == HTTP_200_OK + assert response.data["operator"] == operator.name + assert response.data["allowed"] is True + assert response.data["end_time"] == parking.time_end + + +def test_check_parking_details_time_start_parameter(operator, enforcer, enforcer_api_client, parking_factory): + zone = create_payment_zone(domain=enforcer.enforced_domain) + parking = parking_factory(registration_number="ABC-123", operator=operator, zone=zone, domain=zone.domain) + data = deepcopy(PARKING_DATA) + data["details"] = ["time_start"] + response = enforcer_api_client.post(list_url, data=data) + + assert response.status_code == HTTP_200_OK + assert response.data["time_start"] == parking.time_start + assert response.data["allowed"] is True + assert response.data["end_time"] == parking.time_end + + +def test_check_parking_details_permissions_parameter(operator, enforcer, enforcer_api_client, parking_factory): + zone = create_payment_zone(domain=enforcer.enforced_domain) + parking = parking_factory(registration_number="ABC-123", operator=operator, zone=zone, domain=zone.domain) + data = deepcopy(PARKING_DATA) + data["details"] = ["permissions"] + response = enforcer_api_client.post(list_url, data=data) + + assert response.status_code == HTTP_200_OK + assert response.data["permissions"]["zone"] == parking.zone.number + assert response.data["permissions"]["event_area"] is None + assert response.data["allowed"] is True + assert response.data["end_time"] == parking.time_end + + +def test_check_parking_details_parking_not_allowed(operator, enforcer, enforcer_api_client, parking_factory): + data = deepcopy(PARKING_DATA) + data["details"] = ["operator", "time_start", "permissions"] + response = enforcer_api_client.post(list_url, data=data) + + assert response.status_code == HTTP_200_OK + assert response.data["allowed"] is False + assert response.data["operator"] is None + assert response.data["time_start"] is None + assert response.data["permissions"]["zone"] is None + + +def test_check_parking_details_invalid_zone(operator, enforcer, enforcer_api_client, parking_factory): + create_payment_zone(domain=enforcer.enforced_domain) + zone = create_payment_zone(geom=create_area_geom(geom=GEOM_2), number=2, code="2", domain=enforcer.enforced_domain) + parking = parking_factory(registration_number="ABC-123", operator=operator, zone=zone, domain=zone.domain) + data = deepcopy(PARKING_DATA) + data["details"] = ["operator", "time_start", "permissions"] + + response = enforcer_api_client.post(list_url, data=data) + + assert response.status_code == HTTP_200_OK + assert response.data["allowed"] is False + assert response.data["end_time"] == parking.time_end + assert response.data["permissions"]["zone"] == parking.zone.number + assert response.data["operator"] == operator.name + assert response.data["time_start"] == parking.time_start + + def test_check_parking_invalid_time_parking(operator, enforcer, enforcer_api_client, history_parking_factory): zone = create_payment_zone(domain=enforcer.enforced_domain) history_parking_factory(registration_number="ABC-123", operator=operator, zone=zone, domain=zone.domain) From 6fda3f05021e44ad04cffa8bc22f774e70fcab4c Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:08:10 +0300 Subject: [PATCH 02/11] Add active permits to details permissions --- parkings/api/enforcement/check_parking.py | 56 +++++++++-------- .../api/enforcement/test_check_parking.py | 61 ++++++++++++++++--- 2 files changed, 83 insertions(+), 34 deletions(-) diff --git a/parkings/api/enforcement/check_parking.py b/parkings/api/enforcement/check_parking.py index 2a10b67a..09de030f 100644 --- a/parkings/api/enforcement/check_parking.py +++ b/parkings/api/enforcement/check_parking.py @@ -3,16 +3,24 @@ from django.conf import settings from django.contrib.gis.gdal.error import GDALException from django.contrib.gis.geos import Point + from django.utils import timezone from rest_framework import generics, serializers from rest_framework.response import Response - from ...models import ( EventArea, EventParking, Parking, ParkingCheck, PaymentZone, PermitArea, - PermitLookupItem) + PermitLookupItem, Permit) from ...models.constants import GK25FIN_SRID, WGS84_SRID from .permissions import IsEnforcer +class PermitPermissionsSerializer(serializers.ModelSerializer): + class Meta: + model = Permit + fields = [ + "external_id", + "subjects", + "areas" + ] class AwareDateTimeField(serializers.DateTimeField): default_error_messages = dict( @@ -63,7 +71,7 @@ def post(self, request): area = get_permit_area(gk25_location, domain) event_area = get_event_area(gk25_location, domain) - (allowed_by, parking, end_time) = check_parking( + (allowed_by, parking, end_time, permit_lookup_items) = check_parking( registration_number, zone, area, time, domain, event_area) allowed = bool(allowed_by) @@ -72,7 +80,7 @@ def post(self, request): # one that has just expired, i.e. was valid a few minutes # ago (where "a few minutes" is the grace duration) past_time = time - get_grace_duration() - (_allowed_by, parking, end_time) = check_parking( + (_allowed_by, parking, end_time, permit_lookup_items) = check_parking( registration_number, zone, area, past_time, domain, event_area) result = { @@ -96,10 +104,10 @@ def post(self, request): if permissions_detail: if isinstance(parking, EventParking): - permissions = {"event_area": parking.event_area.origin_id if parking and parking.event_area else None, - "zone": None} + permissions = {"zone": None, "permits": None, "event_area": parking.event_area.origin_id if parking and parking.event_area else None} else: - permissions = {"event_area": None, "zone": parking.zone.number if parking and parking.zone else None} + permissions = {"zone": parking.zone.number if parking and parking.zone else None,"permits": [PermitPermissionsSerializer(item.permit).data for item in permit_lookup_items],"event_area": None} + result["permissions"] = permissions filter = { @@ -111,6 +119,7 @@ def post(self, request): "result": result, "allowed": allowed } + if isinstance(parking, Parking): filter["found_parking"] = parking if isinstance(parking, EventParking): @@ -185,8 +194,15 @@ def check_parking(registration_number, zone, area, time, domain, event_area): :type area: str|None :type domain: parkings.models.EnforcementDomain :type time: datetime.datetime - :rtype: (str, Parking|None, datetime.datetime) + :rtype: (str, Parking|None, datetime.datetime, PermitLookupItemQuerySet) """ + permit_lookup_item_qs = ( + PermitLookupItem.objects + .active() + .by_time(time) + .by_subject(registration_number) + .filter(permit__domain=domain)) + active_parkings = ( Parking.objects .registration_number_like(registration_number) @@ -196,25 +212,17 @@ def check_parking(registration_number, zone, area, time, domain, event_area): for parking in active_parkings: if zone is None or parking.zone.number <= zone: - return ("parking", parking, parking.time_end) + return ("parking", parking, parking.time_end, permit_lookup_item_qs) if area: - permit_end_time = ( - PermitLookupItem.objects - .active() - .by_time(time) - .by_subject(registration_number) - .by_area(area) - .values_list("end_time", flat=True) - .filter(permit__domain=domain) - .first()) - + permit_end_time = permit_lookup_item_qs.by_area(area).values_list("end_time", flat=True).first() if permit_end_time: - return ("permit", None, permit_end_time) + return ("permit", None, permit_end_time, permit_lookup_item_qs) + for parking in active_parkings: if parking.zone.number > zone: - return (None, parking, parking.time_end) + return (None, parking, parking.time_end, permit_lookup_item_qs) active_event_parking = ( EventParking.objects @@ -225,12 +233,12 @@ def check_parking(registration_number, zone, area, time, domain, event_area): if active_event_parking: if active_event_parking.event_area == event_area: - return ("event_parking", active_event_parking, active_event_parking.time_end) + return ("event_parking", active_event_parking, active_event_parking.time_end, None) else: # Event parking not parked in the assigned event area, i.e., not allowed. - return (None, active_event_parking, active_event_parking.time_end) + return (None, active_event_parking, active_event_parking.time_end, None) - return (None, None, None) + return (None, None, None, permit_lookup_item_qs) def get_grace_duration(default=datetime.timedelta(minutes=15)): diff --git a/parkings/tests/api/enforcement/test_check_parking.py b/parkings/tests/api/enforcement/test_check_parking.py index b57b1c42..277fa9a8 100644 --- a/parkings/tests/api/enforcement/test_check_parking.py +++ b/parkings/tests/api/enforcement/test_check_parking.py @@ -55,13 +55,15 @@ ] -def create_permit_area(client=None, domain=None, allowed_user=None): +def create_permit_area(client=None, domain=None, allowed_user=None, identifier="A", name="Kamppi", geom=None): assert client or (domain and allowed_user) + if geom is None: + geom = create_area_geom() area = PermitArea.objects.create( domain=(domain or client.enforcer.enforced_domain), - identifier="A", - name="Kamppi", - geom=create_area_geom()) + identifier=identifier, + name=name, + geom=geom) area.allowed_users.add(allowed_user or client.auth_user) return area @@ -78,21 +80,22 @@ def create_area_geom(geom=GEOM_1): return MultiPolygon(polygons) -def create_permit(domain, permit_series=None, end_time=None): +def create_permit(domain, permit_series=None, end_time=None, registration_number="ABC-123", area="A", start_time=None): end_time = end_time or timezone.now() + datetime.timedelta(days=1) - start_time = timezone.now() + if not start_time: + start_time = timezone.now() series = permit_series or create_permit_series(active=True) subjects = [ { "end_time": str(end_time), "start_time": str(start_time), - "registration_number": "ABC-123", + "registration_number": registration_number, } ] - areas = [{"area": "A", "end_time": str(end_time), "start_time": str(start_time)}] + areas = [{"area": area, "end_time": str(end_time), "start_time": str(start_time)}] - Permit.objects.create( + return Permit.objects.create( domain=domain, series=series, external_id=12345, subjects=subjects, areas=areas ) @@ -144,7 +147,6 @@ def test_check_parking_not_allowed_event_parking_details( assert response.data["permissions"]["zone"] is None assert response.data["operator"] is None assert response.data["time_start"] is None - assert ParkingCheck.objects.filter( registration_number=PARKING_DATA["registration_number"]).first().result["allowed"] is False @@ -322,6 +324,45 @@ def test_check_parking_valid_permit(enforcer_api_client, staff_user): assert response.data["allowed"] is True +def test_check_parking_valid_permit_details(enforcer_api_client, staff_user): + create_permit_area(enforcer_api_client) + permit = create_permit(domain=enforcer_api_client.enforcer.enforced_domain) + data = deepcopy(PARKING_DATA) + data["details"] = ["operator", "time_start", "permissions"] + response = enforcer_api_client.post(list_url, data=data) + + assert response.status_code == HTTP_200_OK + assert response.data["allowed"] is True + assert response.data["operator"] is None + assert response.data["time_start"] is None + assert response.data["permissions"]["zone"] is None + assert response.data["permissions"]["event_area"] is None + assert len(response.data["permissions"]["permits"]) == 1 + assert response.data["permissions"]["permits"][0]["subjects"] == permit.subjects + assert response.data["permissions"]["permits"][0]["areas"] == permit.areas + + +def test_check_parking_invalid_multiple_permit_details(enforcer_api_client, staff_user): + create_permit_area(enforcer_api_client) + create_permit_area(enforcer_api_client, identifier="B", name="Kauppatori", geom=create_area_geom(geom=GEOM_2)) + + permit_1 = create_permit(domain=enforcer_api_client.enforcer.enforced_domain, + start_time=timezone.now() - datetime.timedelta(days=1)) + permit_2 = create_permit(domain=enforcer_api_client.enforcer.enforced_domain, area="B", + start_time=timezone.now() - datetime.timedelta(days=1)) + + data = deepcopy(INVALID_PARKING_DATA) + data["details"] = ["time_start", "operator", "permissions"] + response = enforcer_api_client.post(list_url, data=data) + assert response.status_code == HTTP_200_OK + assert response.data["allowed"] is False + assert len(response.data["permissions"]["permits"]) == 2 + assert response.data["permissions"]["permits"][0]["subjects"] == permit_1.subjects + assert response.data["permissions"]["permits"][0]["areas"] == permit_1.areas + assert response.data["permissions"]["permits"][1]["subjects"] == permit_2.subjects + assert response.data["permissions"]["permits"][1]["areas"] == permit_2.areas + + def test_check_parking_invalid_time_permit(enforcer_api_client, staff_user): create_permit_area(enforcer_api_client) end_time = timezone.now() - datetime.timedelta(days=1) From 61b4cfacce085d17141919f23690f5dbb92b25a9 Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:24:20 +0300 Subject: [PATCH 03/11] Return end_time=None for not allowed event parkings --- parkings/api/enforcement/check_parking.py | 17 ++++++++++------- .../tests/api/enforcement/test_check_parking.py | 8 ++++---- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/parkings/api/enforcement/check_parking.py b/parkings/api/enforcement/check_parking.py index 09de030f..54164a80 100644 --- a/parkings/api/enforcement/check_parking.py +++ b/parkings/api/enforcement/check_parking.py @@ -3,16 +3,17 @@ from django.conf import settings from django.contrib.gis.gdal.error import GDALException from django.contrib.gis.geos import Point - from django.utils import timezone from rest_framework import generics, serializers from rest_framework.response import Response + from ...models import ( - EventArea, EventParking, Parking, ParkingCheck, PaymentZone, PermitArea, - PermitLookupItem, Permit) + EventArea, EventParking, Parking, ParkingCheck, PaymentZone, Permit, + PermitArea, PermitLookupItem) from ...models.constants import GK25FIN_SRID, WGS84_SRID from .permissions import IsEnforcer + class PermitPermissionsSerializer(serializers.ModelSerializer): class Meta: model = Permit @@ -22,6 +23,7 @@ class Meta: "areas" ] + class AwareDateTimeField(serializers.DateTimeField): default_error_messages = dict( serializers.DateField.default_error_messages, @@ -104,9 +106,11 @@ def post(self, request): if permissions_detail: if isinstance(parking, EventParking): - permissions = {"zone": None, "permits": None, "event_area": parking.event_area.origin_id if parking and parking.event_area else None} + permissions = {"zone": None, "permits": None, + "event_area": parking.event_area.origin_id if parking and parking.event_area else None} else: - permissions = {"zone": parking.zone.number if parking and parking.zone else None,"permits": [PermitPermissionsSerializer(item.permit).data for item in permit_lookup_items],"event_area": None} + permissions = {"zone": parking.zone.number if parking and parking.zone else None, "permits": [ + PermitPermissionsSerializer(item.permit).data for item in permit_lookup_items], "event_area": None} result["permissions"] = permissions @@ -219,7 +223,6 @@ def check_parking(registration_number, zone, area, time, domain, event_area): if permit_end_time: return ("permit", None, permit_end_time, permit_lookup_item_qs) - for parking in active_parkings: if parking.zone.number > zone: return (None, parking, parking.time_end, permit_lookup_item_qs) @@ -236,7 +239,7 @@ def check_parking(registration_number, zone, area, time, domain, event_area): return ("event_parking", active_event_parking, active_event_parking.time_end, None) else: # Event parking not parked in the assigned event area, i.e., not allowed. - return (None, active_event_parking, active_event_parking.time_end, None) + return (None, active_event_parking, None, None) return (None, None, None, permit_lookup_item_qs) diff --git a/parkings/tests/api/enforcement/test_check_parking.py b/parkings/tests/api/enforcement/test_check_parking.py index 277fa9a8..c80a410a 100644 --- a/parkings/tests/api/enforcement/test_check_parking.py +++ b/parkings/tests/api/enforcement/test_check_parking.py @@ -166,7 +166,7 @@ def test_check_event_parking_parked_in_wrong_event_area_include_details( assert response.status_code == HTTP_200_OK assert response.data["location"]["event_area"] == event_area_2.id assert response.data["allowed"] is False - assert response.data["end_time"] == event_parking.time_end + assert response.data["end_time"] is None assert response.data["permissions"]["event_area"] == event_area_1.origin_id assert response.data["permissions"]["zone"] is None assert response.data["operator"] == event_parking.operator.name @@ -179,13 +179,13 @@ def test_check_event_parking_outside_event_area( enforcer_api_client, event_parking_factory, enforcer, event_area_factory): event_area = event_area_factory.create(geom=create_area_geom(), domain=enforcer.enforced_domain) location = Point(PARKING_DATA_2["location"]["longitude"], PARKING_DATA_2["location"]["latitude"], srid=WGS84_SRID) - event_parking = event_parking_factory(registration_number="ABC-123", - domain=enforcer.enforced_domain, event_area=event_area, location=location) + event_parking_factory(registration_number="ABC-123", + domain=enforcer.enforced_domain, event_area=event_area, location=location) response = enforcer_api_client.post(list_url, data=PARKING_DATA_2) assert response.status_code == HTTP_200_OK assert response.data["location"]["event_area"] is None assert response.data["allowed"] is False - assert response.data["end_time"] == event_parking.time_end + assert response.data["end_time"] is None assert ParkingCheck.objects.filter( registration_number=PARKING_DATA["registration_number"]).first().result["allowed"] is False From f9677b4708a272d9272d7045c423276786005d75 Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Mon, 21 Oct 2024 13:41:17 +0300 Subject: [PATCH 04/11] Add test_check_parking_vaid_parking_permit_details --- .../api/enforcement/test_check_parking.py | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/parkings/tests/api/enforcement/test_check_parking.py b/parkings/tests/api/enforcement/test_check_parking.py index c80a410a..7f52d892 100644 --- a/parkings/tests/api/enforcement/test_check_parking.py +++ b/parkings/tests/api/enforcement/test_check_parking.py @@ -324,7 +324,7 @@ def test_check_parking_valid_permit(enforcer_api_client, staff_user): assert response.data["allowed"] is True -def test_check_parking_valid_permit_details(enforcer_api_client, staff_user): +def test_check_parking_valid_parking_permit_details(enforcer_api_client, staff_user): create_permit_area(enforcer_api_client) permit = create_permit(domain=enforcer_api_client.enforcer.enforced_domain) data = deepcopy(PARKING_DATA) @@ -342,7 +342,7 @@ def test_check_parking_valid_permit_details(enforcer_api_client, staff_user): assert response.data["permissions"]["permits"][0]["areas"] == permit.areas -def test_check_parking_invalid_multiple_permit_details(enforcer_api_client, staff_user): +def test_check_parking_invalid_location_multiple_permit_details(enforcer_api_client, staff_user): create_permit_area(enforcer_api_client) create_permit_area(enforcer_api_client, identifier="B", name="Kauppatori", geom=create_area_geom(geom=GEOM_2)) @@ -352,7 +352,7 @@ def test_check_parking_invalid_multiple_permit_details(enforcer_api_client, staf start_time=timezone.now() - datetime.timedelta(days=1)) data = deepcopy(INVALID_PARKING_DATA) - data["details"] = ["time_start", "operator", "permissions"] + data["details"] = ["permissions"] response = enforcer_api_client.post(list_url, data=data) assert response.status_code == HTTP_200_OK assert response.data["allowed"] is False @@ -363,6 +363,26 @@ def test_check_parking_invalid_multiple_permit_details(enforcer_api_client, staf assert response.data["permissions"]["permits"][1]["areas"] == permit_2.areas +def test_check_parking_valid_parking_with_permit_details(enforcer_api_client, parking_factory, operator): + create_permit_area(enforcer_api_client, identifier="B", name="Kauppatori", geom=create_area_geom(geom=GEOM_2)) + + permit_1 = create_permit(domain=enforcer_api_client.enforcer.enforced_domain, + start_time=timezone.now() - datetime.timedelta(days=1), area="B") + + zone = create_payment_zone(domain=enforcer_api_client.enforcer.enforced_domain) + parking = parking_factory(registration_number="ABC-123", operator=operator, zone=zone, domain=zone.domain) + data = deepcopy(PARKING_DATA) + data["details"] = ["time_start", "operator", "permissions"] + response = enforcer_api_client.post(list_url, data=data) + assert response.status_code == HTTP_200_OK + assert response.data["allowed"] is True + assert len(response.data["permissions"]["permits"]) == 1 + assert response.data["permissions"]["permits"][0]["subjects"] == permit_1.subjects + assert response.data["permissions"]["permits"][0]["areas"] == permit_1.areas + assert response.data["time_start"] == parking.time_start + assert response.data["operator"] == parking.operator.name + + def test_check_parking_invalid_time_permit(enforcer_api_client, staff_user): create_permit_area(enforcer_api_client) end_time = timezone.now() - datetime.timedelta(days=1) From 1f224e53e13775ba2784eb5d7f803d00d72c2b6a Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Tue, 22 Oct 2024 07:52:58 +0300 Subject: [PATCH 05/11] Set time_start and operator to None if parking not allowed --- parkings/api/enforcement/check_parking.py | 7 ++++--- .../tests/api/enforcement/test_check_parking.py | 15 ++++++++------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/parkings/api/enforcement/check_parking.py b/parkings/api/enforcement/check_parking.py index 54164a80..d068bd7b 100644 --- a/parkings/api/enforcement/check_parking.py +++ b/parkings/api/enforcement/check_parking.py @@ -99,10 +99,10 @@ def post(self, request): operator_detail, time_start_detail, permissions_detail = get_details(params) if operator_detail: - result["operator"] = parking.operator.name if parking and parking.operator else None + result["operator"] = parking.operator.name if allowed and parking and parking.operator else None if time_start_detail: - result["time_start"] = parking.time_start if parking and parking.time_start else None + result["time_start"] = parking.time_start if allowed and parking and parking.time_start else None if permissions_detail: if isinstance(parking, EventParking): @@ -225,8 +225,9 @@ def check_parking(registration_number, zone, area, time, domain, event_area): for parking in active_parkings: if parking.zone.number > zone: - return (None, parking, parking.time_end, permit_lookup_item_qs) + return (None, parking, None, permit_lookup_item_qs) + # move up active_event_parking = ( EventParking.objects .registration_number_like(registration_number) diff --git a/parkings/tests/api/enforcement/test_check_parking.py b/parkings/tests/api/enforcement/test_check_parking.py index 7f52d892..a523bff3 100644 --- a/parkings/tests/api/enforcement/test_check_parking.py +++ b/parkings/tests/api/enforcement/test_check_parking.py @@ -157,8 +157,8 @@ def test_check_event_parking_parked_in_wrong_event_area_include_details( event_area_2 = event_area_factory.create(geom=create_area_geom(geom=GEOM_2), domain=enforcer.enforced_domain) # Parked inside event_area_2, i.e., inside GEOM_2, but event_area_1 is assigned location = Point(PARKING_DATA_2["location"]["longitude"], PARKING_DATA_2["location"]["latitude"], srid=WGS84_SRID) - event_parking = event_parking_factory(registration_number="ABC-123", - domain=enforcer.enforced_domain, event_area=event_area_1, location=location) + event_parking_factory(registration_number="ABC-123", domain=enforcer.enforced_domain, + event_area=event_area_1, location=location) data = deepcopy(PARKING_DATA_2) data["details"] = ["operator", "time_start", "permissions"] response = enforcer_api_client.post(list_url, data=data) @@ -169,8 +169,8 @@ def test_check_event_parking_parked_in_wrong_event_area_include_details( assert response.data["end_time"] is None assert response.data["permissions"]["event_area"] == event_area_1.origin_id assert response.data["permissions"]["zone"] is None - assert response.data["operator"] == event_parking.operator.name - assert response.data["time_start"] == event_parking.time_start + assert response.data["operator"] is None + assert response.data["time_start"] is None assert ParkingCheck.objects.filter( registration_number=PARKING_DATA["registration_number"]).first().result["allowed"] is False @@ -287,10 +287,10 @@ def test_check_parking_details_invalid_zone(operator, enforcer, enforcer_api_cli assert response.status_code == HTTP_200_OK assert response.data["allowed"] is False - assert response.data["end_time"] == parking.time_end + assert response.data["end_time"] is None assert response.data["permissions"]["zone"] == parking.zone.number - assert response.data["operator"] == operator.name - assert response.data["time_start"] == parking.time_start + assert response.data["operator"] is None + assert response.data["time_start"] is None def test_check_parking_invalid_time_parking(operator, enforcer, enforcer_api_client, history_parking_factory): @@ -379,6 +379,7 @@ def test_check_parking_valid_parking_with_permit_details(enforcer_api_client, pa assert len(response.data["permissions"]["permits"]) == 1 assert response.data["permissions"]["permits"][0]["subjects"] == permit_1.subjects assert response.data["permissions"]["permits"][0]["areas"] == permit_1.areas + assert response.data["permissions"]["zone"] == parking.zone.number assert response.data["time_start"] == parking.time_start assert response.data["operator"] == parking.operator.name From cde9470675f0ed2a3628617726ed44fdbfdefb81 Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:13:14 +0300 Subject: [PATCH 06/11] Return all active event areas for given registration number --- parkings/api/enforcement/check_parking.py | 53 ++++++++++--------- .../api/enforcement/test_check_parking.py | 11 ++-- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/parkings/api/enforcement/check_parking.py b/parkings/api/enforcement/check_parking.py index d068bd7b..065397e5 100644 --- a/parkings/api/enforcement/check_parking.py +++ b/parkings/api/enforcement/check_parking.py @@ -73,7 +73,7 @@ def post(self, request): area = get_permit_area(gk25_location, domain) event_area = get_event_area(gk25_location, domain) - (allowed_by, parking, end_time, permit_lookup_items) = check_parking( + (allowed_by, parking, end_time, permit_lookup_items, active_event_parkings) = check_parking( registration_number, zone, area, time, domain, event_area) allowed = bool(allowed_by) @@ -82,7 +82,7 @@ def post(self, request): # one that has just expired, i.e. was valid a few minutes # ago (where "a few minutes" is the grace duration) past_time = time - get_grace_duration() - (_allowed_by, parking, end_time, permit_lookup_items) = check_parking( + (_allowed_by, parking, end_time, permit_lookup_items, active_event_parkings) = check_parking( registration_number, zone, area, past_time, domain, event_area) result = { @@ -107,10 +107,11 @@ def post(self, request): if permissions_detail: if isinstance(parking, EventParking): permissions = {"zone": None, "permits": None, - "event_area": parking.event_area.origin_id if parking and parking.event_area else None} + "event_areas": [e_p.event_area.origin_id for e_p in active_event_parkings]} else: permissions = {"zone": parking.zone.number if parking and parking.zone else None, "permits": [ - PermitPermissionsSerializer(item.permit).data for item in permit_lookup_items], "event_area": None} + PermitPermissionsSerializer(item.permit).data for item in permit_lookup_items], + "event_areas": [e_p.event_area.origin_id for e_p in active_event_parkings]} result["permissions"] = permissions @@ -198,9 +199,9 @@ def check_parking(registration_number, zone, area, time, domain, event_area): :type area: str|None :type domain: parkings.models.EnforcementDomain :type time: datetime.datetime - :rtype: (str, Parking|None, datetime.datetime, PermitLookupItemQuerySet) + :rtype: (str, Parking|None, datetime.datetime, PermitLookupItemQuerySet, EventParkingQuerySet) """ - permit_lookup_item_qs = ( + permit_lookup_items = ( PermitLookupItem.objects .active() .by_time(time) @@ -214,35 +215,39 @@ def check_parking(registration_number, zone, area, time, domain, event_area): .only("id", "zone", "time_end") .filter(domain=domain)) + active_event_parkings = ( + EventParking.objects + .registration_number_like(registration_number) + .valid_at(time) + .only("id", "time_end") + .filter(domain=domain)) + for parking in active_parkings: if zone is None or parking.zone.number <= zone: - return ("parking", parking, parking.time_end, permit_lookup_item_qs) + return ("parking", + parking, + parking.time_end, + permit_lookup_items, + active_event_parkings) if area: - permit_end_time = permit_lookup_item_qs.by_area(area).values_list("end_time", flat=True).first() + permit_end_time = permit_lookup_items.by_area(area).values_list("end_time", flat=True).first() if permit_end_time: - return ("permit", None, permit_end_time, permit_lookup_item_qs) + return ("permit", None, permit_end_time, permit_lookup_items, active_event_parkings) for parking in active_parkings: if parking.zone.number > zone: - return (None, parking, None, permit_lookup_item_qs) - - # move up - active_event_parking = ( - EventParking.objects - .registration_number_like(registration_number) - .valid_at(time) - .only("id", "time_end") - .filter(domain=domain)).first() + return (None, parking, None, permit_lookup_items, active_event_parkings) - if active_event_parking: + for active_event_parking in active_event_parkings: if active_event_parking.event_area == event_area: - return ("event_parking", active_event_parking, active_event_parking.time_end, None) - else: - # Event parking not parked in the assigned event area, i.e., not allowed. - return (None, active_event_parking, None, None) + return ("event_parking", + active_event_parking, + active_event_parking.time_end, + permit_lookup_items, + active_event_parkings) - return (None, None, None, permit_lookup_item_qs) + return (None, None, None, permit_lookup_items, active_event_parkings) def get_grace_duration(default=datetime.timedelta(minutes=15)): diff --git a/parkings/tests/api/enforcement/test_check_parking.py b/parkings/tests/api/enforcement/test_check_parking.py index a523bff3..259355a3 100644 --- a/parkings/tests/api/enforcement/test_check_parking.py +++ b/parkings/tests/api/enforcement/test_check_parking.py @@ -125,7 +125,7 @@ def test_check_parking_allowed_event_parking_details( assert response.status_code == HTTP_200_OK assert response.data["allowed"] is True assert response.data["end_time"] == event_parking.time_end - assert response.data["permissions"]["event_area"] == event_area.origin_id + assert response.data["permissions"]["event_areas"][0] == event_area.origin_id assert response.data["operator"] == event_parking.operator.name assert response.data["time_start"] == event_parking.time_start assert ParkingCheck.objects.filter( @@ -143,7 +143,7 @@ def test_check_parking_not_allowed_event_parking_details( assert response.status_code == HTTP_200_OK assert response.data["allowed"] is False assert response.data["end_time"] is None - assert response.data["permissions"]["event_area"] is None + assert response.data["permissions"]["event_areas"] == [] assert response.data["permissions"]["zone"] is None assert response.data["operator"] is None assert response.data["time_start"] is None @@ -167,7 +167,8 @@ def test_check_event_parking_parked_in_wrong_event_area_include_details( assert response.data["location"]["event_area"] == event_area_2.id assert response.data["allowed"] is False assert response.data["end_time"] is None - assert response.data["permissions"]["event_area"] == event_area_1.origin_id + assert len(response.data["permissions"]["event_areas"]) == 1 + assert event_area_1.origin_id in response.data["permissions"]["event_areas"] assert response.data["permissions"]["zone"] is None assert response.data["operator"] is None assert response.data["time_start"] is None @@ -259,7 +260,7 @@ def test_check_parking_details_permissions_parameter(operator, enforcer, enforce assert response.status_code == HTTP_200_OK assert response.data["permissions"]["zone"] == parking.zone.number - assert response.data["permissions"]["event_area"] is None + assert response.data["permissions"]["event_areas"] == [] assert response.data["allowed"] is True assert response.data["end_time"] == parking.time_end @@ -336,7 +337,7 @@ def test_check_parking_valid_parking_permit_details(enforcer_api_client, staff_u assert response.data["operator"] is None assert response.data["time_start"] is None assert response.data["permissions"]["zone"] is None - assert response.data["permissions"]["event_area"] is None + assert response.data["permissions"]["event_areas"] == [] assert len(response.data["permissions"]["permits"]) == 1 assert response.data["permissions"]["permits"][0]["subjects"] == permit.subjects assert response.data["permissions"]["permits"][0]["areas"] == permit.areas From 650d15e3eeb5d0e9671c22bf7c9d76cfc39a7a95 Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:07:22 +0300 Subject: [PATCH 07/11] Return all active zones for given registration number --- parkings/api/enforcement/check_parking.py | 50 ++++++++++++------- .../api/enforcement/test_check_parking.py | 36 ++++++++++--- 2 files changed, 60 insertions(+), 26 deletions(-) diff --git a/parkings/api/enforcement/check_parking.py b/parkings/api/enforcement/check_parking.py index 065397e5..71d3a029 100644 --- a/parkings/api/enforcement/check_parking.py +++ b/parkings/api/enforcement/check_parking.py @@ -73,7 +73,7 @@ def post(self, request): area = get_permit_area(gk25_location, domain) event_area = get_event_area(gk25_location, domain) - (allowed_by, parking, end_time, permit_lookup_items, active_event_parkings) = check_parking( + (allowed_by, parking, end_time, active_parkings, permit_lookup_items, active_event_parkings) = check_parking( registration_number, zone, area, time, domain, event_area) allowed = bool(allowed_by) @@ -82,8 +82,20 @@ def post(self, request): # one that has just expired, i.e. was valid a few minutes # ago (where "a few minutes" is the grace duration) past_time = time - get_grace_duration() - (_allowed_by, parking, end_time, permit_lookup_items, active_event_parkings) = check_parking( - registration_number, zone, area, past_time, domain, event_area) + ( + _allowed_by, + parking, + end_time, + active_parkings, + permit_lookup_items, + active_event_parkings + ) = check_parking( + registration_number, + zone, area, + past_time, + domain, + event_area + ) result = { "allowed": allowed, @@ -105,15 +117,17 @@ def post(self, request): result["time_start"] = parking.time_start if allowed and parking and parking.time_start else None if permissions_detail: - if isinstance(parking, EventParking): - permissions = {"zone": None, "permits": None, - "event_areas": [e_p.event_area.origin_id for e_p in active_event_parkings]} - else: - permissions = {"zone": parking.zone.number if parking and parking.zone else None, "permits": [ - PermitPermissionsSerializer(item.permit).data for item in permit_lookup_items], - "event_areas": [e_p.event_area.origin_id for e_p in active_event_parkings]} - - result["permissions"] = permissions + result["permissions"] = { + "zones": [ + p.zone.number for p in active_parkings + ], + "permits": [ + PermitPermissionsSerializer(item.permit).data for item in permit_lookup_items + ], + "event_areas": [ + e_p.event_area.origin_id for e_p in active_event_parkings + ] + } filter = { "performer": request.user, @@ -199,7 +213,7 @@ def check_parking(registration_number, zone, area, time, domain, event_area): :type area: str|None :type domain: parkings.models.EnforcementDomain :type time: datetime.datetime - :rtype: (str, Parking|None, datetime.datetime, PermitLookupItemQuerySet, EventParkingQuerySet) + :rtype: (str, Parking|None, datetime.datetime, ParkingQuerySet, PermitLookupItemQuerySet, EventParkingQuerySet) """ permit_lookup_items = ( PermitLookupItem.objects @@ -227,27 +241,25 @@ def check_parking(registration_number, zone, area, time, domain, event_area): return ("parking", parking, parking.time_end, + active_parkings, permit_lookup_items, active_event_parkings) if area: permit_end_time = permit_lookup_items.by_area(area).values_list("end_time", flat=True).first() if permit_end_time: - return ("permit", None, permit_end_time, permit_lookup_items, active_event_parkings) - - for parking in active_parkings: - if parking.zone.number > zone: - return (None, parking, None, permit_lookup_items, active_event_parkings) + return ("permit", None, permit_end_time, active_parkings, permit_lookup_items, active_event_parkings) for active_event_parking in active_event_parkings: if active_event_parking.event_area == event_area: return ("event_parking", active_event_parking, active_event_parking.time_end, + active_parkings, permit_lookup_items, active_event_parkings) - return (None, None, None, permit_lookup_items, active_event_parkings) + return (None, None, None, active_parkings, permit_lookup_items, active_event_parkings) def get_grace_duration(default=datetime.timedelta(minutes=15)): diff --git a/parkings/tests/api/enforcement/test_check_parking.py b/parkings/tests/api/enforcement/test_check_parking.py index 259355a3..7f07af7b 100644 --- a/parkings/tests/api/enforcement/test_check_parking.py +++ b/parkings/tests/api/enforcement/test_check_parking.py @@ -144,7 +144,7 @@ def test_check_parking_not_allowed_event_parking_details( assert response.data["allowed"] is False assert response.data["end_time"] is None assert response.data["permissions"]["event_areas"] == [] - assert response.data["permissions"]["zone"] is None + assert response.data["permissions"]["zones"] == [] assert response.data["operator"] is None assert response.data["time_start"] is None assert ParkingCheck.objects.filter( @@ -169,13 +169,35 @@ def test_check_event_parking_parked_in_wrong_event_area_include_details( assert response.data["end_time"] is None assert len(response.data["permissions"]["event_areas"]) == 1 assert event_area_1.origin_id in response.data["permissions"]["event_areas"] - assert response.data["permissions"]["zone"] is None + assert response.data["permissions"]["zones"] == [] assert response.data["operator"] is None assert response.data["time_start"] is None assert ParkingCheck.objects.filter( registration_number=PARKING_DATA["registration_number"]).first().result["allowed"] is False +def test_check_parking_not_allowed_parking_and_event_parking_permission_details( + enforcer_api_client, event_parking_factory, operator, enforcer, event_area_factory, parking_factory): + # In this theoretical case, the registration number has an active parking and active event parking, + # but it is parked outside the active zone and event area. + event_area = event_area_factory.create(geom=create_area_geom(geom=GEOM_2), domain=enforcer.enforced_domain) + event_parking_factory(registration_number="ABC-123", + domain=enforcer.enforced_domain, event_area=event_area) + create_payment_zone(domain=enforcer.enforced_domain) + zone = create_payment_zone(geom=create_area_geom(geom=GEOM_2), number=2, code="2", domain=enforcer.enforced_domain) + parking = parking_factory(registration_number="ABC-123", operator=operator, zone=zone, domain=zone.domain) + data = deepcopy(PARKING_DATA) + data["details"] = ["time_start", "operator", "permissions"] + response = enforcer_api_client.post(list_url, data=data) + + assert response.status_code == HTTP_200_OK + assert response.data["allowed"] is False + assert response.data["permissions"]["event_areas"] == [event_area.origin_id] + assert response.data["permissions"]["zones"] == [parking.zone.number] + assert response.data["operator"] is None + assert response.data["time_start"] is None + + def test_check_event_parking_outside_event_area( enforcer_api_client, event_parking_factory, enforcer, event_area_factory): event_area = event_area_factory.create(geom=create_area_geom(), domain=enforcer.enforced_domain) @@ -259,7 +281,7 @@ def test_check_parking_details_permissions_parameter(operator, enforcer, enforce response = enforcer_api_client.post(list_url, data=data) assert response.status_code == HTTP_200_OK - assert response.data["permissions"]["zone"] == parking.zone.number + assert response.data["permissions"]["zones"] == [parking.zone.number] assert response.data["permissions"]["event_areas"] == [] assert response.data["allowed"] is True assert response.data["end_time"] == parking.time_end @@ -274,7 +296,7 @@ def test_check_parking_details_parking_not_allowed(operator, enforcer, enforcer_ assert response.data["allowed"] is False assert response.data["operator"] is None assert response.data["time_start"] is None - assert response.data["permissions"]["zone"] is None + assert response.data["permissions"]["zones"] == [] def test_check_parking_details_invalid_zone(operator, enforcer, enforcer_api_client, parking_factory): @@ -289,7 +311,7 @@ def test_check_parking_details_invalid_zone(operator, enforcer, enforcer_api_cli assert response.status_code == HTTP_200_OK assert response.data["allowed"] is False assert response.data["end_time"] is None - assert response.data["permissions"]["zone"] == parking.zone.number + assert response.data["permissions"]["zones"] == [parking.zone.number] assert response.data["operator"] is None assert response.data["time_start"] is None @@ -336,7 +358,7 @@ def test_check_parking_valid_parking_permit_details(enforcer_api_client, staff_u assert response.data["allowed"] is True assert response.data["operator"] is None assert response.data["time_start"] is None - assert response.data["permissions"]["zone"] is None + assert response.data["permissions"]["zones"] == [] assert response.data["permissions"]["event_areas"] == [] assert len(response.data["permissions"]["permits"]) == 1 assert response.data["permissions"]["permits"][0]["subjects"] == permit.subjects @@ -380,7 +402,7 @@ def test_check_parking_valid_parking_with_permit_details(enforcer_api_client, pa assert len(response.data["permissions"]["permits"]) == 1 assert response.data["permissions"]["permits"][0]["subjects"] == permit_1.subjects assert response.data["permissions"]["permits"][0]["areas"] == permit_1.areas - assert response.data["permissions"]["zone"] == parking.zone.number + assert response.data["permissions"]["zones"] == [parking.zone.number] assert response.data["time_start"] == parking.time_start assert response.data["operator"] == parking.operator.name From a018f4ed213e91365a5620ef9ca96f886822431b Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:31:37 +0300 Subject: [PATCH 08/11] Test permissions with multiple parkings, event_parkings and permits --- .../api/enforcement/test_check_parking.py | 65 ++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/parkings/tests/api/enforcement/test_check_parking.py b/parkings/tests/api/enforcement/test_check_parking.py index 7f07af7b..2bba5352 100644 --- a/parkings/tests/api/enforcement/test_check_parking.py +++ b/parkings/tests/api/enforcement/test_check_parking.py @@ -54,6 +54,13 @@ (26.8, 64.1), # South West corner ] +GEOM_3 = [ + (23.8, 64.3), # North West corner + (24.0, 64.3), # North East corner + (24.0, 64.1), # South East corner + (23.8, 64.1), # South West corner +] + def create_permit_area(client=None, domain=None, allowed_user=None, identifier="A", name="Kamppi", geom=None): assert client or (domain and allowed_user) @@ -178,8 +185,8 @@ def test_check_event_parking_parked_in_wrong_event_area_include_details( def test_check_parking_not_allowed_parking_and_event_parking_permission_details( enforcer_api_client, event_parking_factory, operator, enforcer, event_area_factory, parking_factory): - # In this theoretical case, the registration number has an active parking and active event parking, - # but it is parked outside the active zone and event area. + # In this very improbable scenario, the registration number has an active parking and active event parking, + # but the vehicle is parked outside the active zone and event area. event_area = event_area_factory.create(geom=create_area_geom(geom=GEOM_2), domain=enforcer.enforced_domain) event_parking_factory(registration_number="ABC-123", domain=enforcer.enforced_domain, event_area=event_area) @@ -198,6 +205,60 @@ def test_check_parking_not_allowed_parking_and_event_parking_permission_details( assert response.data["time_start"] is None +def test_check_parking_not_allowed_multiple_active_parkings_and_permits_permissions_details( + enforcer_api_client, + event_parking_factory, + operator, enforcer, + event_area_factory, + parking_factory +): + # In this very improbable scenario, the registration number has two active event parkings, active parkings + # and permits, but the vehicle is parked outside its active parkings and permits. + event_area_1 = event_area_factory.create(geom=create_area_geom(geom=GEOM_3), domain=enforcer.enforced_domain) + event_parking_factory(registration_number="ABC-123", + domain=enforcer.enforced_domain, event_area=event_area_1) + event_area_2 = event_area_factory.create(geom=create_area_geom(geom=GEOM_2), domain=enforcer.enforced_domain) + event_parking_factory(registration_number="ABC-123", + domain=enforcer.enforced_domain, event_area=event_area_2) + + zone_1 = create_payment_zone(domain=enforcer.enforced_domain) + zone_2 = create_payment_zone(geom=create_area_geom(geom=GEOM_2), number=2, + code="2", domain=enforcer.enforced_domain) + zone_3 = create_payment_zone(geom=create_area_geom(geom=GEOM_3), number=3, + code="3", domain=enforcer.enforced_domain) + parking_factory(registration_number="ABC-123", operator=operator, zone=zone_2, domain=zone_1.domain) + parking_factory(registration_number="ABC-123", operator=operator, zone=zone_3, domain=zone_2.domain) + + create_permit_area(enforcer_api_client, geom=create_area_geom(geom=GEOM_2)) + create_permit_area(enforcer_api_client, identifier="B", name="Kauppatori", geom=create_area_geom(geom=GEOM_3)) + + permit_1 = create_permit(domain=enforcer_api_client.enforcer.enforced_domain, + start_time=timezone.now() - datetime.timedelta(days=1)) + permit_2 = create_permit(domain=enforcer_api_client.enforcer.enforced_domain, area="B", + start_time=timezone.now() - datetime.timedelta(days=1)) + + data = deepcopy(PARKING_DATA) + data["details"] = ["time_start", "operator", "permissions"] + response = enforcer_api_client.post(list_url, data=data) + + assert response.status_code == HTTP_200_OK + assert response.data["allowed"] is False + assert response.data["location"]["payment_zone"] == zone_1.number + assert len(response.data["permissions"]["event_areas"]) == 2 + assert event_area_1.origin_id in response.data["permissions"]["event_areas"] + assert event_area_2.origin_id in response.data["permissions"]["event_areas"] + assert len(response.data["permissions"]["zones"]) == 2 + assert zone_2.number in response.data["permissions"]["zones"] + assert zone_3.number in response.data["permissions"]["zones"] + assert len(response.data["permissions"]["permits"]) == 2 + assert response.data["permissions"]["permits"][0]["subjects"] == permit_1.subjects + assert response.data["permissions"]["permits"][0]["areas"] == permit_1.areas + assert response.data["permissions"]["permits"][1]["subjects"] == permit_2.subjects + assert response.data["permissions"]["permits"][1]["areas"] == permit_2.areas + assert response.data["operator"] is None + assert response.data["time_start"] is None + + def test_check_event_parking_outside_event_area( enforcer_api_client, event_parking_factory, enforcer, event_area_factory): event_area = event_area_factory.create(geom=create_area_geom(), domain=enforcer.enforced_domain) From 05615fc3e670f04655786427e20dddc665f28228 Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:36:10 +0300 Subject: [PATCH 09/11] Return only unique zones and event_areas in permissions --- parkings/api/enforcement/check_parking.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/parkings/api/enforcement/check_parking.py b/parkings/api/enforcement/check_parking.py index 71d3a029..fd193743 100644 --- a/parkings/api/enforcement/check_parking.py +++ b/parkings/api/enforcement/check_parking.py @@ -118,15 +118,11 @@ def post(self, request): if permissions_detail: result["permissions"] = { - "zones": [ - p.zone.number for p in active_parkings - ], + "zones": list({p.zone.number for p in active_parkings}), "permits": [ PermitPermissionsSerializer(item.permit).data for item in permit_lookup_items ], - "event_areas": [ - e_p.event_area.origin_id for e_p in active_event_parkings - ] + "event_areas": list({e_p.event_area.origin_id for e_p in active_event_parkings}) } filter = { From 9269e4a81dd4cde761149b0295d0ad7aa402d258 Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Thu, 24 Oct 2024 10:01:41 +0300 Subject: [PATCH 10/11] Return event area IDs in permissions 'event_areas' field --- parkings/api/enforcement/check_parking.py | 2 +- .../api/enforcement/test_check_parking.py | 26 +++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/parkings/api/enforcement/check_parking.py b/parkings/api/enforcement/check_parking.py index fd193743..da3e6c7b 100644 --- a/parkings/api/enforcement/check_parking.py +++ b/parkings/api/enforcement/check_parking.py @@ -122,7 +122,7 @@ def post(self, request): "permits": [ PermitPermissionsSerializer(item.permit).data for item in permit_lookup_items ], - "event_areas": list({e_p.event_area.origin_id for e_p in active_event_parkings}) + "event_areas": list({e_p.event_area.id for e_p in active_event_parkings}) } filter = { diff --git a/parkings/tests/api/enforcement/test_check_parking.py b/parkings/tests/api/enforcement/test_check_parking.py index 2bba5352..1fe26a1c 100644 --- a/parkings/tests/api/enforcement/test_check_parking.py +++ b/parkings/tests/api/enforcement/test_check_parking.py @@ -132,7 +132,7 @@ def test_check_parking_allowed_event_parking_details( assert response.status_code == HTTP_200_OK assert response.data["allowed"] is True assert response.data["end_time"] == event_parking.time_end - assert response.data["permissions"]["event_areas"][0] == event_area.origin_id + assert response.data["permissions"]["event_areas"][0] == event_area.id assert response.data["operator"] == event_parking.operator.name assert response.data["time_start"] == event_parking.time_start assert ParkingCheck.objects.filter( @@ -175,7 +175,7 @@ def test_check_event_parking_parked_in_wrong_event_area_include_details( assert response.data["allowed"] is False assert response.data["end_time"] is None assert len(response.data["permissions"]["event_areas"]) == 1 - assert event_area_1.origin_id in response.data["permissions"]["event_areas"] + assert event_area_1.id in response.data["permissions"]["event_areas"] assert response.data["permissions"]["zones"] == [] assert response.data["operator"] is None assert response.data["time_start"] is None @@ -199,10 +199,12 @@ def test_check_parking_not_allowed_parking_and_event_parking_permission_details( assert response.status_code == HTTP_200_OK assert response.data["allowed"] is False - assert response.data["permissions"]["event_areas"] == [event_area.origin_id] + assert response.data["permissions"]["event_areas"] == [event_area.id] assert response.data["permissions"]["zones"] == [parking.zone.number] assert response.data["operator"] is None assert response.data["time_start"] is None + assert ParkingCheck.objects.filter( + registration_number=PARKING_DATA["registration_number"]).first().result["allowed"] is False def test_check_parking_not_allowed_multiple_active_parkings_and_permits_permissions_details( @@ -245,8 +247,8 @@ def test_check_parking_not_allowed_multiple_active_parkings_and_permits_permissi assert response.data["allowed"] is False assert response.data["location"]["payment_zone"] == zone_1.number assert len(response.data["permissions"]["event_areas"]) == 2 - assert event_area_1.origin_id in response.data["permissions"]["event_areas"] - assert event_area_2.origin_id in response.data["permissions"]["event_areas"] + assert event_area_1.id in response.data["permissions"]["event_areas"] + assert event_area_2.id in response.data["permissions"]["event_areas"] assert len(response.data["permissions"]["zones"]) == 2 assert zone_2.number in response.data["permissions"]["zones"] assert zone_3.number in response.data["permissions"]["zones"] @@ -257,6 +259,8 @@ def test_check_parking_not_allowed_multiple_active_parkings_and_permits_permissi assert response.data["permissions"]["permits"][1]["areas"] == permit_2.areas assert response.data["operator"] is None assert response.data["time_start"] is None + assert ParkingCheck.objects.filter( + registration_number=PARKING_DATA["registration_number"]).first().result["allowed"] is False def test_check_event_parking_outside_event_area( @@ -319,6 +323,8 @@ def test_check_parking_details_operator_parameter(operator, enforcer, enforcer_a assert response.data["operator"] == operator.name assert response.data["allowed"] is True assert response.data["end_time"] == parking.time_end + assert ParkingCheck.objects.filter( + registration_number=PARKING_DATA["registration_number"]).first().result["allowed"] is True def test_check_parking_details_time_start_parameter(operator, enforcer, enforcer_api_client, parking_factory): @@ -332,6 +338,8 @@ def test_check_parking_details_time_start_parameter(operator, enforcer, enforcer assert response.data["time_start"] == parking.time_start assert response.data["allowed"] is True assert response.data["end_time"] == parking.time_end + assert ParkingCheck.objects.filter( + registration_number=PARKING_DATA["registration_number"]).first().result["allowed"] is True def test_check_parking_details_permissions_parameter(operator, enforcer, enforcer_api_client, parking_factory): @@ -358,6 +366,8 @@ def test_check_parking_details_parking_not_allowed(operator, enforcer, enforcer_ assert response.data["operator"] is None assert response.data["time_start"] is None assert response.data["permissions"]["zones"] == [] + assert ParkingCheck.objects.filter( + registration_number=PARKING_DATA["registration_number"]).first().result["allowed"] is False def test_check_parking_details_invalid_zone(operator, enforcer, enforcer_api_client, parking_factory): @@ -424,6 +434,8 @@ def test_check_parking_valid_parking_permit_details(enforcer_api_client, staff_u assert len(response.data["permissions"]["permits"]) == 1 assert response.data["permissions"]["permits"][0]["subjects"] == permit.subjects assert response.data["permissions"]["permits"][0]["areas"] == permit.areas + assert ParkingCheck.objects.filter( + registration_number=PARKING_DATA["registration_number"]).first().result["allowed"] is True def test_check_parking_invalid_location_multiple_permit_details(enforcer_api_client, staff_user): @@ -445,6 +457,8 @@ def test_check_parking_invalid_location_multiple_permit_details(enforcer_api_cli assert response.data["permissions"]["permits"][0]["areas"] == permit_1.areas assert response.data["permissions"]["permits"][1]["subjects"] == permit_2.subjects assert response.data["permissions"]["permits"][1]["areas"] == permit_2.areas + assert ParkingCheck.objects.filter( + registration_number=PARKING_DATA["registration_number"]).first().result["allowed"] is False def test_check_parking_valid_parking_with_permit_details(enforcer_api_client, parking_factory, operator): @@ -466,6 +480,8 @@ def test_check_parking_valid_parking_with_permit_details(enforcer_api_client, pa assert response.data["permissions"]["zones"] == [parking.zone.number] assert response.data["time_start"] == parking.time_start assert response.data["operator"] == parking.operator.name + assert ParkingCheck.objects.filter( + registration_number=PARKING_DATA["registration_number"]).first().result["allowed"] is True def test_check_parking_invalid_time_permit(enforcer_api_client, staff_user): From 0b983a08be072a37bc5d2bb77ac66e618579610d Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Thu, 24 Oct 2024 10:39:00 +0300 Subject: [PATCH 11/11] Add information about details parameter --- docs/api/enforcement.yaml | 54 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/docs/api/enforcement.yaml b/docs/api/enforcement.yaml index ee23d1fa..e7e0af7b 100644 --- a/docs/api/enforcement.yaml +++ b/docs/api/enforcement.yaml @@ -386,6 +386,16 @@ paths: given, defaults to current time. type: string format: date-time + details: + description: >- + Optional additional details. Takes as argument a list of preferred details. + The details are: operator, time_start and permissions. + type: array + items: + type: string + default: [] + example: ["operator", "time_start", "permissions"] + responses: '200': description: OK. Validity check succeeded. @@ -454,11 +464,53 @@ paths: nullable: true time: description: >- - Time used in the check. It will either be equal + Time used in the check. It will either be equal to the time value provided in the request or the current time if none was provided. type: string format: date-time + operator: + description: >- + If 'operator' in details, returns the operator + of the allowed parking. + type: string + nullable: true + time_start: + description: >- + If 'time_start' in details, returns the time_start + of the allowed parking. + type: string + format: date-time + nullable: true + permissions: + description: >- + If 'permissions' in details, returns the active permissions + for the registration number. + type: object + properties: + event_areas: + description: >- + List of event area IDs to which the registration number has permissions. + type: array + items: + type: string + zones: + description: List of zones to which the registration number has permissions. + type: array + items: + type: integer + permits: + description: List of active permits for the registration number. + type: array + items: + type: object + properties: + external_id: + type: string + subjects: &permitSubjects + $ref: '#/components/schemas/PermitSubjects' + targets: &permitAreas + $ref: '#/components/schemas/PermitAreas' '400': $ref: '#/components/responses/BadRequest' '401':