EvaluateRouteView

Bases: LoggingMixin, ResponseMixin, APIView

Source code in polarrouteserver/route_api/views.py
class EvaluateRouteView(LoggingMixin, ResponseMixin, APIView):
    serializer_class = None

    @extend_schema(
        operation_id="api_route_evaluation",
        request=inline_serializer(
            name="RouteEvaluationRequest",
            fields={
                "route": serializers.JSONField(help_text="The route JSON to evaluate."),
                "custom_mesh_id": serializers.UUIDField(
                    required=False,
                    allow_null=True,
                    help_text="Optional: Custom mesh ID to use for evaluation.",
                ),
            },
        ),
        responses={
            200: routeEvaluationResponseSchema,
            404: notFoundResponseSchema,
        },
    )
    def post(self, request):
        "POST Endpoint to evaluate traveltime and fuel usage on a given route."
        data = request.data
        route_json = data.get("route", None)
        custom_mesh_id = data.get("custom_mesh_id", None)

        if custom_mesh_id:
            try:
                mesh = Mesh.objects.get(id=custom_mesh_id)
                meshes = [mesh]
            except Mesh.DoesNotExist:
                return self.not_found_response("No mesh available.")
        else:
            meshes = select_mesh_for_route_evaluation(route_json)

            if meshes is None:
                return self.not_found_response("No mesh available.")

        response_data = {"polarrouteserver-version": polarrouteserver_version}

        result_dict = evaluate_route(route_json, meshes[0])

        response_data.update(result_dict)

        return self.success_response(response_data)

post(request)

POST Endpoint to evaluate traveltime and fuel usage on a given route.

Source code in polarrouteserver/route_api/views.py
@extend_schema(
    operation_id="api_route_evaluation",
    request=inline_serializer(
        name="RouteEvaluationRequest",
        fields={
            "route": serializers.JSONField(help_text="The route JSON to evaluate."),
            "custom_mesh_id": serializers.UUIDField(
                required=False,
                allow_null=True,
                help_text="Optional: Custom mesh ID to use for evaluation.",
            ),
        },
    ),
    responses={
        200: routeEvaluationResponseSchema,
        404: notFoundResponseSchema,
    },
)
def post(self, request):
    "POST Endpoint to evaluate traveltime and fuel usage on a given route."
    data = request.data
    route_json = data.get("route", None)
    custom_mesh_id = data.get("custom_mesh_id", None)

    if custom_mesh_id:
        try:
            mesh = Mesh.objects.get(id=custom_mesh_id)
            meshes = [mesh]
        except Mesh.DoesNotExist:
            return self.not_found_response("No mesh available.")
    else:
        meshes = select_mesh_for_route_evaluation(route_json)

        if meshes is None:
            return self.not_found_response("No mesh available.")

    response_data = {"polarrouteserver-version": polarrouteserver_version}

    result_dict = evaluate_route(route_json, meshes[0])

    response_data.update(result_dict)

    return self.success_response(response_data)

JobView

Bases: LoggingMixin, ResponseMixin, GenericAPIView

View for handling job status requests

Source code in polarrouteserver/route_api/views.py
class JobView(LoggingMixin, ResponseMixin, GenericAPIView):
    """
    View for handling job status requests
    """

    serializer_class = JobStatusSerializer

    @extend_schema(
        operation_id="api_job_retrieve_status",
        responses={
            200: jobStatusResponseSchema,
            404: notFoundResponseSchema,
        },
    )
    def get(self, request, id):
        """Return status of job and route URL if complete."""

        logger.info(
            f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}"
        )

        try:
            job = Job.objects.get(id=id)
        except Job.DoesNotExist:
            return self.not_found_response(f"Job with id {id} not found.")

        serializer = JobStatusSerializer(job, context={"request": request})

        return self.success_response(serializer.data)

    @extend_schema(
        operation_id="api_job_cancel",
        responses={
            202: acceptedResponseSchema,
            404: notFoundResponseSchema,
        },
    )
    def delete(self, request, id):
        """Cancel job"""

        logger.info(
            f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}"
        )

        try:
            job = Job.objects.get(id=id)
        except Job.DoesNotExist:
            return self.not_found_response(f"Job with id {id} not found.")

        # Store route ID for response before deletion
        route_id = job.route.id

        # Cancel the Celery task
        result = AsyncResult(id=str(id), app=app)
        result.revoke()

        # Delete the corresponding route (this will also delete the job due to CASCADE)
        job.route.delete()

        return self.accepted_response(
            {
                "message": f"Job {id} cancellation requested and route {route_id} deleted.",
                "job_id": str(job.id),
                "route_id": route_id,
            }
        )

delete(request, id)

Cancel job

Source code in polarrouteserver/route_api/views.py
@extend_schema(
    operation_id="api_job_cancel",
    responses={
        202: acceptedResponseSchema,
        404: notFoundResponseSchema,
    },
)
def delete(self, request, id):
    """Cancel job"""

    logger.info(
        f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}"
    )

    try:
        job = Job.objects.get(id=id)
    except Job.DoesNotExist:
        return self.not_found_response(f"Job with id {id} not found.")

    # Store route ID for response before deletion
    route_id = job.route.id

    # Cancel the Celery task
    result = AsyncResult(id=str(id), app=app)
    result.revoke()

    # Delete the corresponding route (this will also delete the job due to CASCADE)
    job.route.delete()

    return self.accepted_response(
        {
            "message": f"Job {id} cancellation requested and route {route_id} deleted.",
            "job_id": str(job.id),
            "route_id": route_id,
        }
    )

get(request, id)

Return status of job and route URL if complete.

Source code in polarrouteserver/route_api/views.py
@extend_schema(
    operation_id="api_job_retrieve_status",
    responses={
        200: jobStatusResponseSchema,
        404: notFoundResponseSchema,
    },
)
def get(self, request, id):
    """Return status of job and route URL if complete."""

    logger.info(
        f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}"
    )

    try:
        job = Job.objects.get(id=id)
    except Job.DoesNotExist:
        return self.not_found_response(f"Job with id {id} not found.")

    serializer = JobStatusSerializer(job, context={"request": request})

    return self.success_response(serializer.data)

LoggingMixin

Provides full logging of requests and responses

Source code in polarrouteserver/route_api/views.py
class LoggingMixin:
    """
    Provides full logging of requests and responses
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.logger = logging.getLogger("django.request")

    def initial(self, request, *args, **kwargs):
        try:
            self.logger.debug(
                {
                    "request": request.data,
                    "method": request.method,
                    "endpoint": request.path,
                    "user": request.user.username,
                    "ip_address": request.META.get("REMOTE_ADDR"),
                    "user_agent": request.META.get("HTTP_USER_AGENT"),
                }
            )
        except Exception:
            self.logger.exception("Error logging request data")

        super().initial(request, *args, **kwargs)

    def finalize_response(self, request, response, *args, **kwargs):
        try:
            self.logger.debug(
                {
                    "response": response.data,
                    "status_code": response.status_code,
                    "user": request.user.username,
                    "ip_address": request.META.get("REMOTE_ADDR"),
                    "user_agent": request.META.get("HTTP_USER_AGENT"),
                }
            )
        except Exception:
            self.logger.exception("Error logging response data")

        return super().finalize_response(request, response, *args, **kwargs)

MeshView

Bases: LoggingMixin, ResponseMixin, APIView

Source code in polarrouteserver/route_api/views.py
class MeshView(LoggingMixin, ResponseMixin, APIView):
    serializer_class = None

    @extend_schema(
        operation_id="api_mesh_get",
        responses={
            200: meshDetailResponseSchema,
            404: notFoundResponseSchema,
        },
    )
    def get(self, request, id):
        "GET Meshes by id"

        logger.info(
            f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}"
        )

        data = {"polarrouteserver-version": polarrouteserver_version}

        try:
            mesh = Mesh.objects.get(id=id)
            data.update(
                dict(
                    id=mesh.id,
                    json=mesh.json,
                    geojson=EnvironmentMesh.load_from_json(mesh.json).to_geojson(),
                )
            )

            return self.success_response(data)

        except Mesh.DoesNotExist:
            return self.no_content_response(data)

get(request, id)

GET Meshes by id

Source code in polarrouteserver/route_api/views.py
@extend_schema(
    operation_id="api_mesh_get",
    responses={
        200: meshDetailResponseSchema,
        404: notFoundResponseSchema,
    },
)
def get(self, request, id):
    "GET Meshes by id"

    logger.info(
        f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}"
    )

    data = {"polarrouteserver-version": polarrouteserver_version}

    try:
        mesh = Mesh.objects.get(id=id)
        data.update(
            dict(
                id=mesh.id,
                json=mesh.json,
                geojson=EnvironmentMesh.load_from_json(mesh.json).to_geojson(),
            )
        )

        return self.success_response(data)

    except Mesh.DoesNotExist:
        return self.no_content_response(data)

RecentRoutesView

Bases: LoggingMixin, ResponseMixin, GenericAPIView

Source code in polarrouteserver/route_api/views.py
class RecentRoutesView(LoggingMixin, ResponseMixin, GenericAPIView):
    serializer_class = None  # No serializer needed - using manual response building

    def _get_celery_task_status(self, job_id, calculated_timestamp, route_info):
        """
        Get Celery task status. Uses database state to avoid Celery broker calls.
        """
        if calculated_timestamp:
            return "SUCCESS"

        if route_info and "error" in str(route_info).lower():
            return "FAILURE"

        # Handle missing job scenarios
        if not job_id:
            return "PENDING"

        # Job exists but no calculation yet - also PENDING
        return "PENDING"

    @extend_schema(
        operation_id="api_recent_routes_list",
        responses={
            200: recentRoutesResponseSchema,
            204: noContentResponseSchema,
        },
    )
    def get(self, request):
        """Get recent routes"""

        logger.info(
            f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}"
        )

        # Only get today's routes, just essential fields
        routes_recent = (
            Route.objects.filter(requested__date=datetime.now().date())
            .select_related("job")
            .values(
                "id",
                "start_lat",
                "start_lon",
                "end_lat",
                "end_lon",
                "start_name",
                "end_name",
                "polar_route_version",
                "requested",
                "calculated",
                "info",
                "mesh_id",
                "mesh__name",
                "job__id",
            )
            .order_by("-requested")
        )

        logger.debug(f"Found {len(routes_recent)} routes calculated today.")

        if not routes_recent:
            return self.no_content_response(
                data={"polarrouteserver-version": polarrouteserver_version},
                message="No recent routes found for today.",
            )

        routes_data = []
        for route in routes_recent:
            status = self._get_celery_task_status(
                route["job__id"], route["calculated"], route["info"]
            )

            # Build lightweight route data
            route_data = {
                "id": route["id"],
                "start_lat": route["start_lat"],
                "start_lon": route["start_lon"],
                "end_lat": route["end_lat"],
                "end_lon": route["end_lon"],
                "start_name": route["start_name"],
                "end_name": route["end_name"],
                "polar_route_version": route["polar_route_version"],
                "requested": route["requested"].isoformat()
                if route["requested"]
                else None,
                "calculated": route["calculated"].isoformat()
                if route["calculated"]
                else None,
                "status": status,
                "route_url": reverse(
                    "route_detail", args=[route["id"]], request=request
                ),
            }

            if route["job__id"]:
                route_data["job_id"] = route["job__id"]
                route_data["job_status_url"] = reverse(
                    "job_detail", args=[route["job__id"]], request=request
                )

            # Add minimal mesh info without loading the heavy JSON
            if route["mesh_id"]:
                route_data["mesh"] = {
                    "id": route["mesh_id"],
                    "name": route["mesh__name"],
                }

            routes_data.append(route_data)

        response_data = {
            "routes": routes_data,
            "polarrouteserver-version": polarrouteserver_version,
        }

        return self.success_response(response_data)

get(request)

Get recent routes

Source code in polarrouteserver/route_api/views.py
@extend_schema(
    operation_id="api_recent_routes_list",
    responses={
        200: recentRoutesResponseSchema,
        204: noContentResponseSchema,
    },
)
def get(self, request):
    """Get recent routes"""

    logger.info(
        f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}"
    )

    # Only get today's routes, just essential fields
    routes_recent = (
        Route.objects.filter(requested__date=datetime.now().date())
        .select_related("job")
        .values(
            "id",
            "start_lat",
            "start_lon",
            "end_lat",
            "end_lon",
            "start_name",
            "end_name",
            "polar_route_version",
            "requested",
            "calculated",
            "info",
            "mesh_id",
            "mesh__name",
            "job__id",
        )
        .order_by("-requested")
    )

    logger.debug(f"Found {len(routes_recent)} routes calculated today.")

    if not routes_recent:
        return self.no_content_response(
            data={"polarrouteserver-version": polarrouteserver_version},
            message="No recent routes found for today.",
        )

    routes_data = []
    for route in routes_recent:
        status = self._get_celery_task_status(
            route["job__id"], route["calculated"], route["info"]
        )

        # Build lightweight route data
        route_data = {
            "id": route["id"],
            "start_lat": route["start_lat"],
            "start_lon": route["start_lon"],
            "end_lat": route["end_lat"],
            "end_lon": route["end_lon"],
            "start_name": route["start_name"],
            "end_name": route["end_name"],
            "polar_route_version": route["polar_route_version"],
            "requested": route["requested"].isoformat()
            if route["requested"]
            else None,
            "calculated": route["calculated"].isoformat()
            if route["calculated"]
            else None,
            "status": status,
            "route_url": reverse(
                "route_detail", args=[route["id"]], request=request
            ),
        }

        if route["job__id"]:
            route_data["job_id"] = route["job__id"]
            route_data["job_status_url"] = reverse(
                "job_detail", args=[route["job__id"]], request=request
            )

        # Add minimal mesh info without loading the heavy JSON
        if route["mesh_id"]:
            route_data["mesh"] = {
                "id": route["mesh_id"],
                "name": route["mesh__name"],
            }

        routes_data.append(route_data)

    response_data = {
        "routes": routes_data,
        "polarrouteserver-version": polarrouteserver_version,
    }

    return self.success_response(response_data)

RouteDetailView

Bases: LoggingMixin, ResponseMixin, GenericAPIView

Source code in polarrouteserver/route_api/views.py
class RouteDetailView(LoggingMixin, ResponseMixin, GenericAPIView):
    serializer_class = RouteSerializer

    @extend_schema(
        operation_id="api_route_retrieve_by_id",
        description="Retrieve route details by ID. Returns the route data.",
        responses={
            200: routeSchema,
            404: notFoundResponseSchema,
        },
    )
    def get(self, request, id):
        """Return route data by route ID."""

        logger.info(
            f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}"
        )

        try:
            route = Route.objects.get(id=id)
        except Route.DoesNotExist:
            return self.not_found_response(f"Route with id {id} not found.")

        data = RouteSerializer(route).data

        return self.success_response(data)

get(request, id)

Return route data by route ID.

Source code in polarrouteserver/route_api/views.py
@extend_schema(
    operation_id="api_route_retrieve_by_id",
    description="Retrieve route details by ID. Returns the route data.",
    responses={
        200: routeSchema,
        404: notFoundResponseSchema,
    },
)
def get(self, request, id):
    """Return route data by route ID."""

    logger.info(
        f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}"
    )

    try:
        route = Route.objects.get(id=id)
    except Route.DoesNotExist:
        return self.not_found_response(f"Route with id {id} not found.")

    data = RouteSerializer(route).data

    return self.success_response(data)

RouteRequestView

Bases: LoggingMixin, ResponseMixin, GenericAPIView

Source code in polarrouteserver/route_api/views.py
class RouteRequestView(LoggingMixin, ResponseMixin, GenericAPIView):
    serializer_class = RouteSerializer

    @extend_schema(
        operation_id="api_route_create_request",
        request=inline_serializer(
            name="RouteCreationRequest",
            # This should be updated along with the json validation below
            fields={
                "start_lat": serializers.FloatField(
                    help_text="Starting latitude of the route."
                ),
                "start_lon": serializers.FloatField(
                    help_text="Starting longitude of the route."
                ),
                "end_lat": serializers.FloatField(
                    help_text="Ending latitude of the route."
                ),
                "end_lon": serializers.FloatField(
                    help_text="Ending longitude of the route."
                ),
                "start_name": serializers.CharField(
                    required=False,
                    allow_null=True,
                    help_text="Name of the start point.",
                ),
                "end_name": serializers.CharField(
                    required=False, allow_null=True, help_text="Name of the end point."
                ),
                "mesh_id": serializers.UUIDField(
                    required=False,
                    allow_null=True,
                    help_text="Optional: Custom mesh ID to use for route calculation.",
                ),
                "force_new_route": serializers.BooleanField(
                    required=False,
                    default=False,
                    help_text="If true, forces recalculation even if an existing route is found.",
                ),
            },
        ),
        responses={
            202: routeAcceptedResponseSchema,
            400: badRequestResponseSchema,
            404: notFoundResponseSchema,
        },
    )
    def post(self, request):
        """Entry point for route requests"""

        logger.info(
            f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}: {request.data}"
        )

        data = request.data

        # TODO validate request JSON
        try:
            start_lat = float(data["start_lat"])
            start_lon = float(data["start_lon"])
            end_lat = float(data["end_lat"])
            end_lon = float(data["end_lon"])
        except (ValueError, TypeError, KeyError) as e:
            msg = f"Invalid coordinate values provided: {e}"
            logger.error(msg)
            return self.bad_request_response(msg)

        start_name = data.get("start_name", None)
        end_name = data.get("end_name", None)
        custom_mesh_id = data.get("mesh_id", None)
        force_new_route = data.get("force_new_route", False)

        if custom_mesh_id:
            try:
                logger.info(f"Got custom mesh id {custom_mesh_id} in request.")
                meshes = [Mesh.objects.get(id=custom_mesh_id)]
            except Mesh.DoesNotExist:
                msg = f"Mesh id {custom_mesh_id} requested. Does not exist."
                logger.info(msg)
                return self.not_found_response(msg)
        else:
            meshes = select_mesh(start_lat, start_lon, end_lat, end_lon)

        if meshes is None:
            return self.not_found_response("No mesh available.")

        logger.debug(f"Using meshes: {[mesh.id for mesh in meshes]}")
        # TODO Future: calculate an up to date mesh if none available

        existing_route = route_exists(meshes, start_lat, start_lon, end_lat, end_lon)

        if existing_route is not None:
            if not force_new_route:
                logger.info(f"Existing route found: {existing_route}")

                # Check if there's an existing job for this route
                existing_job = existing_route.job_set.latest("datetime")

                response_data = {
                    "id": str(existing_job.id),
                    "status-url": reverse(
                        "job_detail", args=[existing_job.id], request=request
                    ),
                    "polarrouteserver-version": polarrouteserver_version,
                    "info": {
                        "message": "Pre-existing route found. Job already exists. To force new calculation, include 'force_new_route': true in POST request."
                    },
                }

                return self.accepted_response(response_data)
            else:
                logger.info(
                    f"Found existing route(s) but got force_new_route={force_new_route}, beginning recalculation."
                )

        logger.debug(
            f"Using mesh {meshes[0].id} as primary mesh with {[mesh.id for mesh in meshes[1:]]} as backup."
        )

        # Create route in database
        route = Route.objects.create(
            start_lat=start_lat,
            start_lon=start_lon,
            end_lat=end_lat,
            end_lon=end_lon,
            mesh=meshes[0],
            start_name=start_name,
            end_name=end_name,
        )

        # Start the task calculation
        task = optimise_route.delay(
            route.id, backup_mesh_ids=[mesh.id for mesh in meshes[1:]]
        )

        # Create database record representing the calculation job
        job = Job.objects.create(
            id=task.id,
            route=route,
        )

        # Prepare response data
        data = {
            "id": job.id,
            "status-url": reverse("job_detail", args=[job.id], request=request),
            "polarrouteserver-version": polarrouteserver_version,
        }

        return self.accepted_response(data)

post(request)

Entry point for route requests

Source code in polarrouteserver/route_api/views.py
@extend_schema(
    operation_id="api_route_create_request",
    request=inline_serializer(
        name="RouteCreationRequest",
        # This should be updated along with the json validation below
        fields={
            "start_lat": serializers.FloatField(
                help_text="Starting latitude of the route."
            ),
            "start_lon": serializers.FloatField(
                help_text="Starting longitude of the route."
            ),
            "end_lat": serializers.FloatField(
                help_text="Ending latitude of the route."
            ),
            "end_lon": serializers.FloatField(
                help_text="Ending longitude of the route."
            ),
            "start_name": serializers.CharField(
                required=False,
                allow_null=True,
                help_text="Name of the start point.",
            ),
            "end_name": serializers.CharField(
                required=False, allow_null=True, help_text="Name of the end point."
            ),
            "mesh_id": serializers.UUIDField(
                required=False,
                allow_null=True,
                help_text="Optional: Custom mesh ID to use for route calculation.",
            ),
            "force_new_route": serializers.BooleanField(
                required=False,
                default=False,
                help_text="If true, forces recalculation even if an existing route is found.",
            ),
        },
    ),
    responses={
        202: routeAcceptedResponseSchema,
        400: badRequestResponseSchema,
        404: notFoundResponseSchema,
    },
)
def post(self, request):
    """Entry point for route requests"""

    logger.info(
        f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}: {request.data}"
    )

    data = request.data

    # TODO validate request JSON
    try:
        start_lat = float(data["start_lat"])
        start_lon = float(data["start_lon"])
        end_lat = float(data["end_lat"])
        end_lon = float(data["end_lon"])
    except (ValueError, TypeError, KeyError) as e:
        msg = f"Invalid coordinate values provided: {e}"
        logger.error(msg)
        return self.bad_request_response(msg)

    start_name = data.get("start_name", None)
    end_name = data.get("end_name", None)
    custom_mesh_id = data.get("mesh_id", None)
    force_new_route = data.get("force_new_route", False)

    if custom_mesh_id:
        try:
            logger.info(f"Got custom mesh id {custom_mesh_id} in request.")
            meshes = [Mesh.objects.get(id=custom_mesh_id)]
        except Mesh.DoesNotExist:
            msg = f"Mesh id {custom_mesh_id} requested. Does not exist."
            logger.info(msg)
            return self.not_found_response(msg)
    else:
        meshes = select_mesh(start_lat, start_lon, end_lat, end_lon)

    if meshes is None:
        return self.not_found_response("No mesh available.")

    logger.debug(f"Using meshes: {[mesh.id for mesh in meshes]}")
    # TODO Future: calculate an up to date mesh if none available

    existing_route = route_exists(meshes, start_lat, start_lon, end_lat, end_lon)

    if existing_route is not None:
        if not force_new_route:
            logger.info(f"Existing route found: {existing_route}")

            # Check if there's an existing job for this route
            existing_job = existing_route.job_set.latest("datetime")

            response_data = {
                "id": str(existing_job.id),
                "status-url": reverse(
                    "job_detail", args=[existing_job.id], request=request
                ),
                "polarrouteserver-version": polarrouteserver_version,
                "info": {
                    "message": "Pre-existing route found. Job already exists. To force new calculation, include 'force_new_route': true in POST request."
                },
            }

            return self.accepted_response(response_data)
        else:
            logger.info(
                f"Found existing route(s) but got force_new_route={force_new_route}, beginning recalculation."
            )

    logger.debug(
        f"Using mesh {meshes[0].id} as primary mesh with {[mesh.id for mesh in meshes[1:]]} as backup."
    )

    # Create route in database
    route = Route.objects.create(
        start_lat=start_lat,
        start_lon=start_lon,
        end_lat=end_lat,
        end_lon=end_lon,
        mesh=meshes[0],
        start_name=start_name,
        end_name=end_name,
    )

    # Start the task calculation
    task = optimise_route.delay(
        route.id, backup_mesh_ids=[mesh.id for mesh in meshes[1:]]
    )

    # Create database record representing the calculation job
    job = Job.objects.create(
        id=task.id,
        route=route,
    )

    # Prepare response data
    data = {
        "id": job.id,
        "status-url": reverse("job_detail", args=[job.id], request=request),
        "polarrouteserver-version": polarrouteserver_version,
    }

    return self.accepted_response(data)

VehicleDetailView

Bases: LoggingMixin, ResponseMixin, GenericAPIView

Source code in polarrouteserver/route_api/views.py
class VehicleDetailView(LoggingMixin, ResponseMixin, GenericAPIView):
    serializer_class = VehicleSerializer

    @extend_schema(
        operation_id="api_vehicle_retrieve_by_type",
        responses={
            200: successResponseSchema,
            404: notFoundResponseSchema,
        },
    )
    def get(self, request, vessel_type):
        """Retrieve vehicle by vessel_type"""

        logger.info(
            f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}"
        )

        logger.info(f"Fetching vehicle(s) with vessel_type={vessel_type}")
        vehicles = Vehicle.objects.filter(vessel_type=vessel_type)

        serializer = self.serializer_class(vehicles, many=True)

        return self.success_response(serializer.data)

    @extend_schema(
        operation_id="api_vehicle_delete_by_type",
        responses={
            204: noContentResponseSchema,
            404: notFoundResponseSchema,
        },
    )
    def delete(self, request, vessel_type):
        """Delete vehicle by vessel_type"""

        logger.info(
            f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}"
        )

        try:
            vehicle = Vehicle.objects.get(vessel_type=vessel_type)
            vehicle.delete()
            logger.info(f"Deleted vehicle with vessel_type={vessel_type}")
            return self.no_content_response(
                data={"message": f"Vehicle '{vessel_type}' deleted successfully."}
            )
        except Vehicle.DoesNotExist:
            logger.error(
                f"Vehicle with vessel_type={vessel_type} not found for deletion."
            )
            return self.not_found_response(
                f"Vehicle with vessel_type '{vessel_type}' not found."
            )

delete(request, vessel_type)

Delete vehicle by vessel_type

Source code in polarrouteserver/route_api/views.py
@extend_schema(
    operation_id="api_vehicle_delete_by_type",
    responses={
        204: noContentResponseSchema,
        404: notFoundResponseSchema,
    },
)
def delete(self, request, vessel_type):
    """Delete vehicle by vessel_type"""

    logger.info(
        f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}"
    )

    try:
        vehicle = Vehicle.objects.get(vessel_type=vessel_type)
        vehicle.delete()
        logger.info(f"Deleted vehicle with vessel_type={vessel_type}")
        return self.no_content_response(
            data={"message": f"Vehicle '{vessel_type}' deleted successfully."}
        )
    except Vehicle.DoesNotExist:
        logger.error(
            f"Vehicle with vessel_type={vessel_type} not found for deletion."
        )
        return self.not_found_response(
            f"Vehicle with vessel_type '{vessel_type}' not found."
        )

get(request, vessel_type)

Retrieve vehicle by vessel_type

Source code in polarrouteserver/route_api/views.py
@extend_schema(
    operation_id="api_vehicle_retrieve_by_type",
    responses={
        200: successResponseSchema,
        404: notFoundResponseSchema,
    },
)
def get(self, request, vessel_type):
    """Retrieve vehicle by vessel_type"""

    logger.info(
        f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}"
    )

    logger.info(f"Fetching vehicle(s) with vessel_type={vessel_type}")
    vehicles = Vehicle.objects.filter(vessel_type=vessel_type)

    serializer = self.serializer_class(vehicles, many=True)

    return self.success_response(serializer.data)

VehicleRequestView

Bases: LoggingMixin, ResponseMixin, GenericAPIView

Source code in polarrouteserver/route_api/views.py
class VehicleRequestView(LoggingMixin, ResponseMixin, GenericAPIView):
    serializer_class = VehicleSerializer

    @extend_schema(
        operation_id="api_vehicle_create_request",
        request=VehicleSerializer,
        responses={
            200: successResponseSchema,
            400: badRequestResponseSchema,
            406: notAcceptableResponseSchema,
        },
    )
    def post(self, request):
        """Entry point to create vehicles"""

        logger.info(
            f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}: {request.data}"
        )

        data = request.data

        # Using Polarroute's built in validation to validate vessel config supplied
        try:
            validate_vessel_config(data)
            logging.info("Vessel config is valid.")
        except Exception as e:
            if isinstance(e, ValidationError):
                error_message = f"Validation error: {e.message}"
            else:
                error_message = f"{e}"

            logging.error(error_message)

            return self.bad_request_response(error_message)

        # Separate out vessel_type and force_properties for checking logic below
        force_properties = data.get("force_properties", None)
        vessel_type = data["vessel_type"]

        # Check if vehicle exists already
        vehicle_queryset = Vehicle.objects.filter(vessel_type=vessel_type)

        # If the vehicle exists, obtain it and return an error if user has not specified force_properties
        if vehicle_queryset.exists():
            logger.info(f"Existing vehicle found: {vessel_type}")

            if not force_properties:
                return self.not_acceptable_response(
                    "Pre-existing vehicle was found. "
                    "To force new properties on an existing vehicle, "
                    "include 'force_properties': true in POST request."
                )

            # If a user has specified force_properties, update that vessel_type's properties
            # The vessel_type and force_properties fields need to be removed to allow updating
            vehicle_properties = data.copy()
            for key in ["vessel_type", "force_properties"]:
                vehicle_properties.pop(key, None)

            vehicle_queryset.update(**vehicle_properties)
            logger.info(f"Updating properties for existing vehicle: {vessel_type}")

            response_data = {"vessel_type": vessel_type}

        else:
            logger.info("Creating new vehicle:")

            # Create vehicle in database
            vehicle = Vehicle.objects.create(**data)

            # Prepare response data
            response_data = {"vessel_type": vehicle.vessel_type}

        return self.success_response(response_data)

    @extend_schema(
        operation_id="api_vehicle_list_retrieve",
        responses={
            200: successResponseSchema,
            204: noContentResponseSchema,
        },
    )
    def get(self, request):
        """Retrieve all vehicles"""

        logger.info(
            f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}"
        )

        logger.info("Fetching all vehicles")
        vehicles = Vehicle.objects.all()

        serializer = self.serializer_class(vehicles, many=True)

        return self.success_response(serializer.data)

get(request)

Retrieve all vehicles

Source code in polarrouteserver/route_api/views.py
@extend_schema(
    operation_id="api_vehicle_list_retrieve",
    responses={
        200: successResponseSchema,
        204: noContentResponseSchema,
    },
)
def get(self, request):
    """Retrieve all vehicles"""

    logger.info(
        f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}"
    )

    logger.info("Fetching all vehicles")
    vehicles = Vehicle.objects.all()

    serializer = self.serializer_class(vehicles, many=True)

    return self.success_response(serializer.data)

post(request)

Entry point to create vehicles

Source code in polarrouteserver/route_api/views.py
@extend_schema(
    operation_id="api_vehicle_create_request",
    request=VehicleSerializer,
    responses={
        200: successResponseSchema,
        400: badRequestResponseSchema,
        406: notAcceptableResponseSchema,
    },
)
def post(self, request):
    """Entry point to create vehicles"""

    logger.info(
        f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}: {request.data}"
    )

    data = request.data

    # Using Polarroute's built in validation to validate vessel config supplied
    try:
        validate_vessel_config(data)
        logging.info("Vessel config is valid.")
    except Exception as e:
        if isinstance(e, ValidationError):
            error_message = f"Validation error: {e.message}"
        else:
            error_message = f"{e}"

        logging.error(error_message)

        return self.bad_request_response(error_message)

    # Separate out vessel_type and force_properties for checking logic below
    force_properties = data.get("force_properties", None)
    vessel_type = data["vessel_type"]

    # Check if vehicle exists already
    vehicle_queryset = Vehicle.objects.filter(vessel_type=vessel_type)

    # If the vehicle exists, obtain it and return an error if user has not specified force_properties
    if vehicle_queryset.exists():
        logger.info(f"Existing vehicle found: {vessel_type}")

        if not force_properties:
            return self.not_acceptable_response(
                "Pre-existing vehicle was found. "
                "To force new properties on an existing vehicle, "
                "include 'force_properties': true in POST request."
            )

        # If a user has specified force_properties, update that vessel_type's properties
        # The vessel_type and force_properties fields need to be removed to allow updating
        vehicle_properties = data.copy()
        for key in ["vessel_type", "force_properties"]:
            vehicle_properties.pop(key, None)

        vehicle_queryset.update(**vehicle_properties)
        logger.info(f"Updating properties for existing vehicle: {vessel_type}")

        response_data = {"vessel_type": vessel_type}

    else:
        logger.info("Creating new vehicle:")

        # Create vehicle in database
        vehicle = Vehicle.objects.create(**data)

        # Prepare response data
        response_data = {"vessel_type": vehicle.vessel_type}

    return self.success_response(response_data)

VehicleTypeListView

Bases: LoggingMixin, ResponseMixin, GenericAPIView

Endpoint to list all distinct vessel_types available.

Source code in polarrouteserver/route_api/views.py
class VehicleTypeListView(LoggingMixin, ResponseMixin, GenericAPIView):
    """
    Endpoint to list all distinct vessel_types available.
    """

    serializer_class = VesselTypeSerializer

    @extend_schema(
        operation_id="api_vehicle_available_list",
        responses={
            200: vehicleTypeListResponseSchema,
            204: noContentResponseSchema,
        },
    )
    def get(self, request):
        logger.info(
            f"{request.method} {request.path} from {request.META.get('REMOTE_ADDR')}"
        )

        vessel_types = Vehicle.objects.values_list("vessel_type", flat=True).distinct()
        vessel_types_list = list(vessel_types)

        if not vessel_types_list:
            logger.warning("No available vessel_types found in the database.")
            return self.no_content_response(
                data={"vessel_types": []}, message="No available vessel types found."
            )

        logger.info(f"Returning {len(vessel_types_list)} distinct vessel_types")

        return self.success_response({"vessel_types": vessel_types_list})