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/import ski trail maintenance history #387

Open
wants to merge 69 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
4eeb501
Copied street maintenance model
juuso-j Sep 17, 2024
6b8cf19
Add maintenance to installed apps
juuso-j Sep 17, 2024
8bf546b
Delete as move to maintenace application
juuso-j Sep 17, 2024
7818d81
Fix import
juuso-j Sep 17, 2024
14e1c53
Delete, moved to maintenance app
juuso-j Sep 17, 2024
2a8ae44
Copy tests from street_maintenance app
juuso-j Sep 17, 2024
f61b526
Delete, moved to maintenance applicaiton
juuso-j Sep 18, 2024
1534e76
Delete, moved to maintenance app
juuso-j Sep 18, 2024
f10ca1c
Change street_maintenance to maintenance in DOC_ENDPOINTS
juuso-j Sep 18, 2024
1ff9f2a
Add tests from street_maintenance application
juuso-j Sep 18, 2024
8a6f9e2
Add __init__.py
juuso-j Sep 18, 2024
bd1d664
Add __init__.py
juuso-j Sep 18, 2024
9e4f796
Add tasks.py from street_maintenance application
juuso-j Sep 18, 2024
d52b36e
Add maintenance AppConfig
juuso-j Sep 18, 2024
1f34e1d
Delete all street_maintenance application tables
juuso-j Sep 18, 2024
27ba44b
Add constants.py from street_maintenance
juuso-j Sep 18, 2024
6111a72
Add models from street_maintenance application
juuso-j Sep 18, 2024
fec6ada
Add API from street_maintenace application
juuso-j Sep 18, 2024
0e261ba
Add admin from street_maintenance
juuso-j Sep 18, 2024
e59ba1b
Add street_maintenance urls for backward compatibility
juuso-j Sep 18, 2024
136d5fd
Add maintenance urls
juuso-j Sep 18, 2024
658c34a
Replace street_maintenance logger settings with maintenance
juuso-j Sep 18, 2024
875ba9e
Add README from street_maintenance
juuso-j Sep 18, 2024
76eb781
Add utils.py from street_maintenance applicaiton
juuso-j Sep 18, 2024
177ad0e
Add management commands from street_maintenance application
juuso-j Sep 18, 2024
c728fec
Add specifications
juuso-j Sep 18, 2024
b6d615b
Delete, as moved to maintenance application
juuso-j Sep 18, 2024
a75d5ba
Fix maintenance urls prefix
juuso-j Sep 19, 2024
23670b8
Merge branch 'feature/replace-street-maintenance-app-with-maintenance…
juuso-j Sep 19, 2024
121e21d
Rename file
juuso-j Sep 24, 2024
1993b3a
Add function get_data_layer
juuso-j Sep 25, 2024
5d2f976
Add importer for ski trails
juuso-j Sep 25, 2024
1b707db
Add task to import ski trails
juuso-j Sep 25, 2024
49dd07b
Add fixture data for testing importing of ski trails
juuso-j Sep 25, 2024
f9f39ac
Add ski trails importer tests
juuso-j Sep 25, 2024
145d5cb
Add function to get fixture GDAL data layer
juuso-j Sep 26, 2024
c77fcaf
Add UnitMaintenanceGeometry fixture
juuso-j Sep 26, 2024
1782b2d
Add models for storing Unit maintenance history
juuso-j Sep 26, 2024
d433930
Add Unit fixture
juuso-j Sep 26, 2024
702ea7e
Add ski trails maintenance history importer
juuso-j Sep 26, 2024
039e6ce
Add ski_trails_maintenance_history_mock_data
juuso-j Sep 26, 2024
8bda877
Add ski trail maintenance history importer tests
juuso-j Sep 26, 2024
f6a1287
Add import_ski_trails_maintenance_history task
juuso-j Sep 26, 2024
73b4e98
Add view for UnitMaintenance model
juuso-j Sep 27, 2024
39df1c9
Add fixture now
juuso-j Sep 27, 2024
249da09
Add units fixture
juuso-j Sep 27, 2024
91345a5
Add unit_maintenance_geometries fixture
juuso-j Sep 27, 2024
a2d2af5
Add SKI_TRAILS_DATE_FIELD_FORMAT constants
juuso-j Sep 27, 2024
3cd24f5
Add UnitMaintenance fixtures
juuso-j Sep 27, 2024
c1017a2
Add viewset for UnitMaintenanceGeometry
juuso-j Sep 27, 2024
12277a4
Add serializers for UnitMaintenance and UnitMaintenanceGeometry
juuso-j Sep 27, 2024
676d8c4
Add tests for UnitMaintenanceViewSet
juuso-j Sep 27, 2024
c905115
Add unit_maintenance/ to DOC_ENDPOINTS
juuso-j Sep 27, 2024
e8bc406
Add UnitMaintenance and UnitMaintenanceGeometry
juuso-j Sep 27, 2024
4d79211
Test UnitMaintenanceGeometryViewSet
juuso-j Sep 27, 2024
7436e3a
Add information about ski trails
juuso-j Sep 27, 2024
cabb95f
Conditionally serialize maintenance information to units
juuso-j Sep 27, 2024
e2ab6e0
Serialize maintance information by default
juuso-j Sep 27, 2024
f3d5d2f
Fix SKITRAIL_TO_UNIT_ID_MAPPINGS
juuso-j Sep 30, 2024
6b15e4a
Change STREET_MAINTENANCE_LOG_LEVEL to MAINTENANCE_LOG_LEVEL
juuso-j Oct 1, 2024
acfc917
Merge branch 'feature/replace-street-maintenance-app-with-maintenance…
juuso-j Oct 1, 2024
9ace48f
Fix comment
juuso-j Oct 1, 2024
b56a5c3
Merge branch 'feature/replace-street-maintenance-app-with-maintenance…
juuso-j Oct 1, 2024
b7558aa
Add SKI_TRAILS_URL environment variable
juuso-j Oct 2, 2024
889932a
Add SKI_TRAILS_MAINTENANCE_HISTORY_URL environment variable
juuso-j Oct 2, 2024
21aeab0
Test ski trails geometry change
juuso-j Oct 2, 2024
028788b
Set unit_maintenance deletion to CASCADE
juuso-j Oct 2, 2024
a6c612f
Remove ueless line of code
juuso-j Oct 2, 2024
161b322
Handle geometry change
juuso-j Oct 2, 2024
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
8 changes: 6 additions & 2 deletions config_dev.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ ECO_COUNTER_LOG_LEVEL=
MOBILITY_DATA_LOG_LEVEL=
# Bicycle networks APP, default INFO
BICYCLE_NETWORK_LOG_LEVEL=
# Street maintenance, default INFO
STREET_MAINTENANCE_LOG_LEVEL=
# Maintenance
MAINTENANCE_LOG_LEVEL=INFO

# Settings needed for enabling Turku area:
#ADDITIONAL_INSTALLED_APPS=smbackend_turku,ptv
Expand Down Expand Up @@ -202,3 +202,7 @@ KUNTEC_KEY=
# Telraam API token, required when fetching Telraam data to csv (import_telraam_to_csv.py)
# https://telraam.helpspace-docs.io/article/27/you-wish-more-data-and-statistics-telraam-api
TELRAAM_TOKEN=
# Optionally, define the urls, from where the ski trails and maintenance histories are retrieved from.
# The default values are in the examples.
# SKI_TRAILS_URL=https://api.paikannuspalvelu.fi/v1/public/track/?data_key=cftqHZ8mjwf3uYpXz9HAUH3nXY6IjrvrvYmRMnbZ&author=?&format=geojson
# SKI_TRAILS_MAINTENANCE_HISTORY_URL=https://api.paikannuspalvelu.fi/v1/public/location/lastvisit/?data_key=cftqHZ8mjwf3uYpXz9HAUH3nXY6IjrvrvYmRMnbZ&author=?&format=geojson&max_distance=50
25 changes: 19 additions & 6 deletions street_maintenance/README.md → maintenance/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Street Maintenance history
# Maintenance history

Django app for importing and serving street maintenance data.
Django app for importing, processing and serving maintenance data.

## Importer
## Street maintenance history
### Importer
Name:
import_street_maintenance_history

Expand Down Expand Up @@ -32,12 +33,24 @@ Note, only the MaintenanceWorks and MaintenanceUnits for the given provider from
To periodically import data use Celery, for more information [see](https://github.com/City-of-Turku/smbackend/wiki/Celery-Tasks#street-maintenance-history-street_maintenancetasksimport_street_maintenance_history).


## Deleting street maintenance history for a provider
### Deleting street maintenance history for a provider
It is possible to delete street maintenance history for a provider.
e.g., to delete all street maintenance history for provider 'destia':
```
./manage.py delete_street_maintenance_history destia
```

## API
See: specificatin.swagger.yaml
## Ski trails
To periodically import ski trails maintenance history use Celery, for more information [see](https://github.com/City-of-Turku/smbackend/wiki/Celery-Tasks#unit-maintenance).

### Ski trails
Before importing the ski trails maintenance history the ski trails must be imported. To import type:
```
./manage.py import_ski_trails
```

### Ski trails maintenance history
To import the maintenance history, type:
```
./manage.py import_ski_trails_maintenance_history
```
File renamed without changes.
13 changes: 13 additions & 0 deletions maintenance/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.contrib import admin

from maintenance.models import (
MaintenanceUnit,
MaintenanceWork,
UnitMaintenance,
UnitMaintenanceGeometry,
)

admin.site.register(MaintenanceWork)
admin.site.register(MaintenanceUnit)
admin.site.register(UnitMaintenance)
admin.site.register(UnitMaintenanceGeometry)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,46 @@
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers

from street_maintenance.models import GeometryHistory, MaintenanceUnit, MaintenanceWork
from maintenance.models import (
GeometryHistory,
MaintenanceUnit,
MaintenanceWork,
UnitMaintenance,
UnitMaintenanceGeometry,
)


class UnitMaintenanceGeometrySerializer(serializers.ModelSerializer):

class Meta:
model = UnitMaintenanceGeometry
fields = ["id", "geometry", "geometry_id", "unit_maintenance"]

def to_representation(self, instance):
ret = super().to_representation(instance)
# If nested in UnitMaintenanceSerializer
if (
self.context.get("request", False)
and "/maintenance/unit_maintenance/" in self.context.get("request", "").path
):
ret.pop("unit_maintenance", None)
return ret


class UnitMaintenanceSerializer(serializers.ModelSerializer):
geometries = UnitMaintenanceGeometrySerializer(many=True, read_only=True)

class Meta:
model = UnitMaintenance
fields = [
"id",
"unit",
"target",
"condition",
"maintained_at",
"last_imported_time",
"geometries",
]


class GeometryHistorySerializer(serializers.ModelSerializer):
Expand Down
30 changes: 30 additions & 0 deletions maintenance/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from django.urls import include, path
from rest_framework import routers

from . import views

app_name = "maintenance"

router = routers.DefaultRouter()
router.register("active_events", views.ActiveEventsViewSet, basename="active_events")

router.register(
"maintenance_works", views.MaintenanceWorkViewSet, basename="maintenance_works"
)
router.register(
"maintenance_units", views.MaintenanceUnitViewSet, basename="maintenance_units"
)
router.register(
"geometry_history", views.GeometryHitoryViewSet, basename="geometry_history"
)
router.register(
"unit_maintenance", views.UnitMaintenanceViewSet, basename="unit_maintenance"
)
router.register(
"unit_maintenance_geometry",
views.UnitMaintenanceGeometryViewSet,
basename="unit_maintenance_geometry",
)
urlpatterns = [
path("", include(router.urls), name="maintenance"),
]
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
"""
Keep backward compatibility to street_maintenance API.
"""

from django.urls import include, path
from rest_framework import routers

Expand All @@ -20,6 +24,5 @@
)

urlpatterns = [
# re_path("^street_maintenance/active_events", )
path("", include(router.urls), name="street_maintenance"),
]
49 changes: 45 additions & 4 deletions street_maintenance/api/views.py → maintenance/api/views.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
from datetime import datetime

import django_filters
from django.utils.decorators import method_decorator
from django.utils.timezone import make_aware
from django.views.decorators.cache import cache_page
from django_filters.rest_framework import DjangoFilterBackend
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter
from rest_framework import mixins, viewsets
from rest_framework.exceptions import ParseError
from rest_framework.pagination import PageNumberPagination

from street_maintenance.api.serializers import (
from maintenance.api.serializers import (
ActiveEventSerializer,
GeometryHistorySerializer,
MaintenanceUnitSerializer,
MaintenanceWorkSerializer,
UnitMaintenanceGeometrySerializer,
UnitMaintenanceSerializer,
)
from street_maintenance.management.commands.constants import (
from maintenance.management.commands.constants import (
EVENT_CHOICES,
PROVIDERS,
START_DATE_TIME_FORMAT,
)
from street_maintenance.models import GeometryHistory, MaintenanceUnit, MaintenanceWork
from maintenance.models import (
GeometryHistory,
MaintenanceUnit,
MaintenanceWork,
UnitMaintenance,
UnitMaintenanceGeometry,
)

EXAMPLE_TIME_FORMAT = "YYYY-MM-DD HH:MM:SS"
EXAMPLE_TIME = "2022-09-18 10:00:00"
Expand Down Expand Up @@ -64,6 +74,37 @@ class LargeResultsSetPagination(PageNumberPagination):
max_page_size = 200_000


class UnitMaintenaceFilterSet(django_filters.FilterSet):
maintained_at__gte = django_filters.DateTimeFilter(
method="filter_maintained_at__gte"
)
maintained_at__lte = django_filters.DateTimeFilter(
method="filter_maintained_at__lte"
)

class Meta:
model = UnitMaintenance
fields = {"target": ["iexact"], "unit": ["exact"]}

def filter_maintained_at__gte(self, queryset, fields, maintained_at):
return queryset.filter(maintained_at__gte=maintained_at)

def filter_maintained_at__lte(self, queryset, fields, maintained_at):
return queryset.filter(maintained_at__lte=maintained_at)


class UnitMaintenanceViewSet(viewsets.ReadOnlyModelViewSet):
queryset = UnitMaintenance.objects.all()
serializer_class = UnitMaintenanceSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = UnitMaintenaceFilterSet


class UnitMaintenanceGeometryViewSet(viewsets.ReadOnlyModelViewSet):
queryset = UnitMaintenanceGeometry.objects.all()
serializer_class = UnitMaintenanceGeometrySerializer


class ActiveEventsViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
queryset = MaintenanceWork.objects.order_by().distinct("events")
serializer_class = ActiveEventSerializer
Expand Down Expand Up @@ -133,7 +174,7 @@ def list(self, request):
list=extend_schema(
description="MaintananceUnit objets are the entities that creates the MaintenanceWorks. Every MaintenanceWork "
"has a relation to a MaintenanceUnit. The type of the MaintenanceUnit can vary depending on the provider. It "
"can be a machine or a event",
"can be a machine or an event.",
),
)
class MaintenanceUnitViewSet(viewsets.ReadOnlyModelViewSet):
Expand Down
6 changes: 6 additions & 0 deletions maintenance/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class MaintenanceConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "maintenance"
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,5 @@
KUNTEC: {HISTORY_SIZE: KUNTEC_DEFAULT_WORKS_HISTORY_SIZE},
YIT: {HISTORY_SIZE: YIT_DEFAULT_WORKS_HISTORY_SIZE},
}

SKI_TRAILS_DATE_FIELD_FORMAT = "%Y-%m-%d %H:%M"
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

from django.core.management import BaseCommand

from street_maintenance.models import GeometryHistory, MaintenanceUnit
from maintenance.models import GeometryHistory, MaintenanceUnit

from .constants import PROVIDERS

logger = logging.getLogger("mobility_data")
logger = logging.getLogger("maintenance")

# Add deprecated provider name 'AUTORI'
PROVIDERS.append("AUTORI")
Expand Down
74 changes: 74 additions & 0 deletions maintenance/management/commands/import_ski_trails.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import logging

from django.conf import settings
from django.contrib.gis.geos import GEOSGeometry
from django.contrib.gis.geos.error import GEOSException
from django.core.management.base import BaseCommand
from django.db.utils import IntegrityError

from maintenance.models import UnitMaintenance, UnitMaintenanceGeometry

from .utils import get_data_layer

logger = logging.getLogger(__name__)


def save_trails(layer):
num_created = 0
num_updated = 0
for feature in layer:
is_created = False
is_updated = False
try:
geometry = None

try:
geometry = GEOSGeometry(feature.geom.wkt, srid=feature.geom.srid)
except GEOSException:
logger.error(f"Invalid geometry {feature.geom.wkt}, skipping...")
continue

geometry_id = feature["construction_point_id"].as_int()
filter = {"geometry_id": geometry_id}
queryset = UnitMaintenanceGeometry.objects.filter(**filter)

if queryset.count() == 0:
unit_maintenance_geometry = UnitMaintenanceGeometry(**filter)
unit_maintenance_geometry.geometry = geometry
is_created = True
else:
unit_maintenance_geometry = queryset.first()
if not unit_maintenance_geometry.geometry.equals(geometry):
unit_maintenance_geometry.geometry = geometry
is_updated = True

try:
unit_maintenance_geometry.save()
except IntegrityError:
logger.error(f"geometry id {geometry.geometry_id} exists, skipping...")

except Exception as exp:
logger.error(f"Could not save ski trail {feature}, reason {exp}")
num_created = num_created + 1 if is_created else num_created
num_updated = num_updated + 1 if is_updated else num_updated
return num_created, num_updated


class Command(BaseCommand):

def add_arguments(self, parser):
parser.add_argument(
"--delete",
action="store_true",
help="Delete all ski trails that have a unit maintenance relationship, before importing.",
)

def handle(self, *args, **options):
if options.get("delete", False):
UnitMaintenanceGeometry.objects.filter(
unit_maintenance__target=UnitMaintenance.SKI_TRAIL
).delete()

layer = get_data_layer(settings.SKI_TRAILS_URL)
num_created, num_updated = save_trails(layer)
logger.info(f"Created {num_created}, updated {num_updated} ski trails.")
Loading
Loading