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)

RecentRoutesView

Bases: LoggingMixin, GenericAPIView

Source code in polarrouteserver/route_api/views.py
class RecentRoutesView(LoggingMixin, GenericAPIView):
    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
        routes_today = Route.objects.filter(requested__date=datetime.now().date())
        response_data = []
        logger.debug(f"Found {len(routes_today)} routes today.")
        for route in routes_today:
            logger.debug(f"{route.id}")
            try:
                job = route.job_set.latest("datetime")
            except Job.DoesNotExist:
                logger.debug(f"Job does not exist for route {route.id}")
                continue

            result = AsyncResult(id=str(job.id), app=app)
            status = result.state

            data = {
                "id": str(job.id),
                "status": status,
                "polarrouteserver-version": polarrouteserver_version,
            }

            data.update(RouteSerializer(route).data)

            if status == "FAILURE":
                data.update({"error": route.info})

            response_data.append(data)

        return Response(
            response_data,
            headers={"Content-Type": "application/json"},
            status=rest_framework.status.HTTP_200_OK,
        )

get(request)

Get recent routes

Source code in polarrouteserver/route_api/views.py
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
    routes_today = Route.objects.filter(requested__date=datetime.now().date())
    response_data = []
    logger.debug(f"Found {len(routes_today)} routes today.")
    for route in routes_today:
        logger.debug(f"{route.id}")
        try:
            job = route.job_set.latest("datetime")
        except Job.DoesNotExist:
            logger.debug(f"Job does not exist for route {route.id}")
            continue

        result = AsyncResult(id=str(job.id), app=app)
        status = result.state

        data = {
            "id": str(job.id),
            "status": status,
            "polarrouteserver-version": polarrouteserver_version,
        }

        data.update(RouteSerializer(route).data)

        if status == "FAILURE":
            data.update({"error": route.info})

        response_data.append(data)

    return Response(
        response_data,
        headers={"Content-Type": "application/json"},
        status=rest_framework.status.HTTP_200_OK,
    )

RouteView

Bases: LoggingMixin, GenericAPIView

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

    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
        start_lat = data["start_lat"]
        start_lon = data["start_lon"]
        end_lat = data["end_lat"]
        end_lon = data["end_lon"]
        start_name = data.get("start_name", None)
        end_name = data.get("end_name", None)
        custom_mesh_id = data.get("mesh_id", None)
        force_recalculate = data.get("force_recalculate", 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 Response(
                    data={
                        "info": {"error": msg},
                        "status": "FAILURE",
                    },
                    headers={"Content-Type": "application/json"},
                    status=rest_framework.status.HTTP_202_ACCEPTED,
                )
        else:
            meshes = select_mesh(start_lat, start_lon, end_lat, end_lon)

        if meshes is None:
            return Response(
                data={
                    "info": {"error": "No suitable mesh available."},
                    "status": "FAILURE",
                },
                headers={"Content-Type": "application/json"},
                status=rest_framework.status.HTTP_200_OK,
            )

        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_recalculate:
                logger.info(f"Existing route found: {existing_route}")
                response_data = RouteSerializer(existing_route).data
                if existing_route.job_set.count() > 0:
                    existing_job = existing_route.job_set.latest("datetime")

                    response_data.update(
                        {
                            "info": {
                                "info": "Pre-existing route found and returned. To force new calculation, include 'force_recalculate': true in POST request."
                            },
                            "id": str(existing_job.id),
                            "status-url": reverse(
                                "route", args=[existing_job.id], request=request
                            ),
                            "polarrouteserver-version": polarrouteserver_version,
                        }
                    )

                else:
                    response_data.update(
                        {
                            "info": {
                                "error": "Pre-existing route was found but there was an error.\
                                To force new calculation, include 'force_recalculate': true in POST request."
                            }
                        }
                    )
                return Response(
                    data=response_data,
                    headers={"Content-Type": "application/json"},
                    status=rest_framework.status.HTTP_202_ACCEPTED,
                )
            else:
                logger.info(
                    f"Found existing route(s) but got force_recalculate={force_recalculate}, 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,
            # url to request status of requested route
            "status-url": reverse("route", args=[job.id], request=request),
            "polarrouteserver-version": polarrouteserver_version,
        }

        return Response(
            data,
            headers={"Content-Type": "application/json"},
            status=rest_framework.status.HTTP_202_ACCEPTED,
        )

    def get(self, request, id):
        "Return status of route calculation and route itself if complete."

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

        # update job with latest state
        job = Job.objects.get(id=id)

        # status = job.status
        result = AsyncResult(id=str(id), app=app)
        status = result.state

        data = {
            "id": str(id),
            "status": status,
            "polarrouteserver-version": polarrouteserver_version,
        }

        data.update(RouteSerializer(job.route).data)

        if status == "FAILURE":
            data.update({"error": job.route.info})

        return Response(
            data,
            headers={"Content-Type": "application/json"},
            status=rest_framework.status.HTTP_200_OK,
        )

    def delete(self, request, id):
        """Cancel route calculation"""

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

        result = AsyncResult(id=str(id), app=app)
        result.revoke()

        return Response(
            {},
            headers={"Content-Type": "application/json"},
            status=rest_framework.status.HTTP_202_ACCEPTED,
        )

delete(request, id)

Cancel route calculation

Source code in polarrouteserver/route_api/views.py
def delete(self, request, id):
    """Cancel route calculation"""

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

    result = AsyncResult(id=str(id), app=app)
    result.revoke()

    return Response(
        {},
        headers={"Content-Type": "application/json"},
        status=rest_framework.status.HTTP_202_ACCEPTED,
    )

get(request, id)

Return status of route calculation and route itself if complete.

Source code in polarrouteserver/route_api/views.py
def get(self, request, id):
    "Return status of route calculation and route itself if complete."

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

    # update job with latest state
    job = Job.objects.get(id=id)

    # status = job.status
    result = AsyncResult(id=str(id), app=app)
    status = result.state

    data = {
        "id": str(id),
        "status": status,
        "polarrouteserver-version": polarrouteserver_version,
    }

    data.update(RouteSerializer(job.route).data)

    if status == "FAILURE":
        data.update({"error": job.route.info})

    return Response(
        data,
        headers={"Content-Type": "application/json"},
        status=rest_framework.status.HTTP_200_OK,
    )

post(request)

Entry point for route requests

Source code in polarrouteserver/route_api/views.py
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
    start_lat = data["start_lat"]
    start_lon = data["start_lon"]
    end_lat = data["end_lat"]
    end_lon = data["end_lon"]
    start_name = data.get("start_name", None)
    end_name = data.get("end_name", None)
    custom_mesh_id = data.get("mesh_id", None)
    force_recalculate = data.get("force_recalculate", 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 Response(
                data={
                    "info": {"error": msg},
                    "status": "FAILURE",
                },
                headers={"Content-Type": "application/json"},
                status=rest_framework.status.HTTP_202_ACCEPTED,
            )
    else:
        meshes = select_mesh(start_lat, start_lon, end_lat, end_lon)

    if meshes is None:
        return Response(
            data={
                "info": {"error": "No suitable mesh available."},
                "status": "FAILURE",
            },
            headers={"Content-Type": "application/json"},
            status=rest_framework.status.HTTP_200_OK,
        )

    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_recalculate:
            logger.info(f"Existing route found: {existing_route}")
            response_data = RouteSerializer(existing_route).data
            if existing_route.job_set.count() > 0:
                existing_job = existing_route.job_set.latest("datetime")

                response_data.update(
                    {
                        "info": {
                            "info": "Pre-existing route found and returned. To force new calculation, include 'force_recalculate': true in POST request."
                        },
                        "id": str(existing_job.id),
                        "status-url": reverse(
                            "route", args=[existing_job.id], request=request
                        ),
                        "polarrouteserver-version": polarrouteserver_version,
                    }
                )

            else:
                response_data.update(
                    {
                        "info": {
                            "error": "Pre-existing route was found but there was an error.\
                            To force new calculation, include 'force_recalculate': true in POST request."
                        }
                    }
                )
            return Response(
                data=response_data,
                headers={"Content-Type": "application/json"},
                status=rest_framework.status.HTTP_202_ACCEPTED,
            )
        else:
            logger.info(
                f"Found existing route(s) but got force_recalculate={force_recalculate}, 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,
        # url to request status of requested route
        "status-url": reverse("route", args=[job.id], request=request),
        "polarrouteserver-version": polarrouteserver_version,
    }

    return Response(
        data,
        headers={"Content-Type": "application/json"},
        status=rest_framework.status.HTTP_202_ACCEPTED,
    )