Skip to content

Commit

Permalink
Merge pull request #23 from City-of-Turku/feature/check-parking-check…
Browse files Browse the repository at this point in the history
…-eventarea

Feature/check parking check eventarea
  • Loading branch information
juuso-j authored Oct 17, 2024
2 parents bd1093b + 416cf9f commit cc7a8d1
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 48 deletions.
1 change: 0 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
---
version: '3'
services:
runserver:
user: bew
Expand Down
9 changes: 8 additions & 1 deletion docs/api/enforcement.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ components:
time_start: "2020-04-15T11:10:05Z"
time_end: "2020-04-15T14:00:00Z"
operator: "63f8374e-1eed-44fa-9224-4cd0c64ddf35"
operator_name: "Foobar Eent Parkings"
operator_name: "Foobar Event Parkings"
event_area: "cdb91cd2-88b0-4869-9c93-4df4fad28a33"
properties:
id:
Expand Down Expand Up @@ -445,6 +445,13 @@ paths:
type: string
maxLength: 10
nullable: true
event_area:
description: >-
Event Area found by the provided location,
or null if the provided location is not in any
event area in the system.
type: string
nullable: true
time:
description: >-
Time used in the check. It will either be equal
Expand Down
30 changes: 18 additions & 12 deletions parkings/api/enforcement/check_parking.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from rest_framework.response import Response

from ...models import (
EventParking, Parking, ParkingCheck, PaymentZone, PermitArea,
EventArea, EventParking, Parking, ParkingCheck, PaymentZone, PermitArea,
PermitLookupItem)
from ...models.constants import GK25FIN_SRID, WGS84_SRID
from .permissions import IsEnforcer
Expand Down Expand Up @@ -60,10 +60,10 @@ def post(self, request):

zone = get_payment_zone(gk25_location, domain)
area = get_permit_area(gk25_location, domain)
event_area = get_event_area(gk25_location, domain)

(allowed_by, parking, end_time) = check_parking(
registration_number, zone, area, time, domain)

registration_number, zone, area, time, domain, event_area)
allowed = bool(allowed_by)

if not allowed:
Expand All @@ -72,14 +72,15 @@ def post(self, request):
# ago (where "a few minutes" is the grace duration)
past_time = time - get_grace_duration()
(_allowed_by, parking, end_time) = check_parking(
registration_number, zone, area, past_time, domain)
registration_number, zone, area, past_time, domain, event_area)

result = {
"allowed": allowed,
"end_time": end_time,
"location": {
"payment_zone": zone,
"permit_area": area.identifier if area else None,
"event_area": event_area.id if event_area else None,
},
"time": time,
}
Expand Down Expand Up @@ -132,15 +133,16 @@ def get_permit_area(location, domain):
area = PermitArea.objects.filter(geom__contains=location, domain=domain).first()
return area if area else None

# def get_event_area(location, domain):
# if location is None:
# return None
# now = timezone.now()
# area = EventArea.objects.filter(geom__contains=location, domain=domain, time_end__gte=now).first()
# return area if area else None

def get_event_area(location, domain):
if location is None:
return None
now = timezone.now()
area = EventArea.objects.filter(geom__contains=location, domain=domain, time_end__gte=now).first()
return area if area else None


def check_parking(registration_number, zone, area, time, domain):
def check_parking(registration_number, zone, area, time, domain, event_area):
"""
Check parking allowance from the database.
Expand Down Expand Up @@ -184,7 +186,11 @@ def check_parking(registration_number, zone, area, time, domain):
.filter(domain=domain)).first()

if active_event_parking:
return ("event parking", active_event_parking, active_event_parking.time_end)
if active_event_parking.event_area == event_area:
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)

return (None, None, None)

Expand Down
10 changes: 10 additions & 0 deletions parkings/api/enforcement/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@

from django.conf import settings

from .check_parking import get_event_area


def get_grace_duration(default=datetime.timedelta(minutes=15)):
value = getattr(settings, 'PARKKIHUBI_TIME_OLD_PARKINGS_VISIBLE', None)
assert value is None or isinstance(value, datetime.timedelta)
return value if value is not None else default


def get_event_parkings_in_assigned_event_areas(queryset):
in_assigned_event_area = []
for event_parking in queryset.all():
if get_event_area(event_parking.location_gk25fin, domain=event_parking.domain) == event_parking.event_area:
in_assigned_event_area.append(event_parking.id)
return queryset.filter(id__in=in_assigned_event_area)
10 changes: 7 additions & 3 deletions parkings/api/enforcement/valid_parking.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

from ...models import Parking
from .permissions import IsEnforcer
from .utils import get_grace_duration
from .utils import (
get_event_parkings_in_assigned_event_areas, get_grace_duration)


class ValidSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -123,6 +124,7 @@ class Meta:

class ValidViewSet(viewsets.ReadOnlyModelViewSet):
permission_classes = [IsEnforcer]
model = None

class Meta:
abstract = True
Expand All @@ -140,7 +142,6 @@ def filter_queryset(self, queryset):
with the PARKKIHUBI_TIME_OLD_PARKINGS_VISIBLE setting.
"""
filtered_queryset = super().filter_queryset(queryset)

if filtered_queryset:
return filtered_queryset

Expand Down Expand Up @@ -173,7 +174,10 @@ def _get_filterset(self, queryset):
return filter_backend.get_filterset(self.request, queryset, self)

def get_queryset(self):
return super().get_queryset().filter(domain=self.request.user.enforcer.enforced_domain)
queryset = super().get_queryset().filter(domain=self.request.user.enforcer.enforced_domain)
if self.__class__.__name__ == "ValidEventParkingViewSet":
queryset = queryset.filter(id__in=get_event_parkings_in_assigned_event_areas(self.queryset))
return queryset


class ValidParkingViewSet(ValidViewSet):
Expand Down
6 changes: 5 additions & 1 deletion parkings/api/monitoring/valid_parking.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from ...models import Parking
from ..common import WGS84InBBoxFilter
from ..enforcement.utils import get_event_parkings_in_assigned_event_areas
from .permissions import IsMonitor
from .serializers import ParkingSerializer

Expand Down Expand Up @@ -42,7 +43,10 @@ class ValidViewSet(viewsets.ReadOnlyModelViewSet):
bbox_filter_include_overlapping = True

def get_queryset(self):
return super().get_queryset().filter(domain=self.request.user.monitor.domain)
queryset = super().get_queryset().filter(domain=self.request.user.monitor.domain)
if self.__class__.__name__ == "ValidEventParkingViewSet":
queryset = queryset.filter(id__in=get_event_parkings_in_assigned_event_areas(self.queryset))
return queryset

class Meta:
abstract = True
Expand Down
64 changes: 53 additions & 11 deletions parkings/tests/api/enforcement/test_check_parking.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
"location": {"longitude": 24.9, "latitude": 60.2},
}

PARKING_DATA_2 = {
"registration_number": "ABC-123",
"location": {"longitude": 26.4, "latitude": 64.2},
}


INVALID_PARKING_DATA = {
"registration_number": "ABC-123",
"location": {"longitude": 24.9, "latitude": 60.4},
Expand Down Expand Up @@ -91,21 +97,23 @@ def create_permit(domain, permit_series=None, end_time=None):
)


def test_check_parking_allowed_event_parking(enforcer_api_client, event_parking_factory, enforcer):
event_parking = event_parking_factory(registration_number="ABC-123", domain=enforcer.enforced_domain)
def test_check_parking_allowed_event_parking(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)
response = enforcer_api_client.post(list_url, data=PARKING_DATA)
assert response.status_code == HTTP_200_OK
assert response.data["allowed"] is True
assert response.data["end_time"] == event_parking.time_end

assert ParkingCheck.objects.filter(
registration_number=event_parking.registration_number).first().result["allowed"] is True


def test_check_parking_not_allowed_event_parking(enforcer_api_client, event_parking_factory, enforcer):
event_parking_factory(registration_number="CBA-123", domain=enforcer.enforced_domain)
def test_check_parking_not_allowed_event_parking(
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)

assert response.status_code == HTTP_200_OK
assert response.data["allowed"] is False
assert response.data["end_time"] is None
Expand All @@ -114,18 +122,52 @@ def test_check_parking_not_allowed_event_parking(enforcer_api_client, event_park
registration_number=PARKING_DATA["registration_number"]).first().result["allowed"] is False


def test_check_event_parking_with_time_end_null(enforcer_api_client, event_parking_factory, enforcer):
def test_check_event_parking_parked_in_wrong_event_area(
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)
# 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)
response = enforcer_api_client.post(list_url, data=PARKING_DATA_2)
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 ParkingCheck.objects.filter(
registration_number=PARKING_DATA["registration_number"]).first().result["allowed"] is False


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)
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 ParkingCheck.objects.filter(
registration_number=PARKING_DATA["registration_number"]).first().result["allowed"] is False


def test_check_event_parking_with_time_end_null(
enforcer_api_client, event_parking_factory, enforcer, event_area_factory):
time_end = None
now = timezone.now()
time_start = now - timedelta(hours=1)
event_area = event_area_factory.create(geom=create_area_geom(), domain=enforcer.enforced_domain)
event_parking_factory(registration_number="ABC-123", domain=enforcer.enforced_domain,
time_start=time_start, time_end=time_end)
time_start=time_start, time_end=time_end, event_area=event_area)

response = enforcer_api_client.post(list_url, data=PARKING_DATA)
assert response.status_code == HTTP_200_OK
assert response.data["allowed"] is True
assert response.data["end_time"] is None

assert response.data["location"]["event_area"] == event_area.id
assert ParkingCheck.objects.filter(
registration_number=PARKING_DATA["registration_number"]).first().result["allowed"] is True

Expand Down Expand Up @@ -195,7 +237,7 @@ def test_check_parking_invalid_location(enforcer_api_client, staff_user):

assert response.status_code == HTTP_200_OK
assert response.data["location"] == {
'payment_zone': None, 'permit_area': None}
'event_area': None, 'payment_zone': None, 'permit_area': None}
assert response.data["allowed"] is False


Expand All @@ -208,7 +250,7 @@ def test_returned_data_has_correct_schema(enforcer_api_client):
assert isinstance(data["allowed"], bool)
assert data["end_time"] is None
assert isinstance(data["location"], dict)
assert sorted(data["location"].keys()) == ["payment_zone", "permit_area"]
assert sorted(data["location"].keys()) == ["event_area", "payment_zone", "permit_area"]
assert isinstance(data["time"], datetime.datetime)


Expand Down
Loading

0 comments on commit cc7a8d1

Please sign in to comment.