diff --git a/src/c3nav/mapdata/newapi/map.py b/src/c3nav/mapdata/newapi/map.py index d5ea5ec2..da2562b8 100644 --- a/src/c3nav/mapdata/newapi/map.py +++ b/src/c3nav/mapdata/newapi/map.py @@ -7,6 +7,7 @@ from django.shortcuts import redirect from ninja import Query from ninja import Router as APIRouter from ninja import Schema +from ninja.decorators import decorate_view from pydantic import Field as APIField from c3nav.api.exceptions import API404 @@ -15,6 +16,7 @@ from c3nav.api.utils import NonEmptyStr from c3nav.mapdata.models import Source from c3nav.mapdata.models.access import AccessPermission from c3nav.mapdata.models.locations import DynamicLocation, LocationRedirect, Position +from c3nav.mapdata.newapi.base import newapi_etag from c3nav.mapdata.schemas.filters import BySearchableFilter, RemoveGeometryFilter from c3nav.mapdata.schemas.model_base import AnyLocationID, AnyPositionID from c3nav.mapdata.schemas.models import (AnyPositionStatusSchema, FullListableLocationSchema, FullLocationSchema, @@ -29,6 +31,7 @@ map_api_router = APIRouter(tags=["map"]) @map_api_router.get('/bounds/', summary="Get map boundaries", response={200: BoundsSchema, **auth_responses}) +@newapi_etag(permissions=False) def bounds(request): return { "bounds": Source.max_bounds(), @@ -76,6 +79,7 @@ def _location_list(request, detailed: bool, filters: LocationListFilters): @map_api_router.get('/locations/', response={200: list[SlimListableLocationSchema], **validate_responses, **auth_responses}, summary="Get locations (with most important attributes)") +@newapi_etag(base_mapdata=True) def location_list(request, filters: Query[LocationListFilters]): return _location_list(request, detailed=False, filters=filters) @@ -83,6 +87,7 @@ def location_list(request, filters: Query[LocationListFilters]): @map_api_router.get('/locations/full/', response={200: list[FullListableLocationSchema], **validate_responses, **auth_responses}, summary="Get locations (with all attributes)") +@newapi_etag(base_mapdata=True) def location_list_full(request, filters: Query[LocationListFilters]): return _location_list(request, detailed=True, filters=filters) @@ -152,6 +157,7 @@ class ShowRedirects(Schema): response={200: SlimLocationSchema, **API404.dict(), **validate_responses, **auth_responses}, summary="Get location by ID (with most important attributes)", description="a numeric ID for a map location or a string ID for generated locations can be used") +@newapi_etag(base_mapdata=True) def location_by_id(request, location_id: AnyLocationID, filters: Query[RemoveGeometryFilter], redirects: Query[ShowRedirects]): return _location_retrieve( @@ -165,6 +171,7 @@ def location_by_id(request, location_id: AnyLocationID, filters: Query[RemoveGeo response={200: FullLocationSchema, **API404.dict(), **validate_responses, **auth_responses}, summary="Get location by ID (with all attributes)", description="a numeric ID for a map location or a string ID for generated locations can be used") +@newapi_etag(base_mapdata=True) def location_by_id_full(request, location_id: AnyLocationID, filters: Query[RemoveGeometryFilter], redirects: Query[ShowRedirects]): return _location_retrieve( @@ -178,6 +185,7 @@ def location_by_id_full(request, location_id: AnyLocationID, filters: Query[Remo response={200: LocationDisplay, **API404.dict(), **auth_responses}, summary="Get location display data by ID", description="a numeric ID for a map location or a string ID for generated locations can be used") +@newapi_etag(base_mapdata=True) def location_by_id_display(request, location_id: AnyLocationID): return _location_display( request, @@ -189,6 +197,7 @@ def location_by_id_display(request, location_id: AnyLocationID): response={200: LocationGeometry, **API404.dict(), **auth_responses}, summary="Get location geometry (if available) by ID", description="a numeric ID for a map location or a string ID for generated locations can be used") +@newapi_etag(base_mapdata=True) def location_by_id_geometry(request, location_id: AnyLocationID): return _location_geometry( request, @@ -199,6 +208,7 @@ def location_by_id_geometry(request, location_id: AnyLocationID): @map_api_router.get('/locations/by-slug/{location_slug}/', response={200: SlimLocationSchema, **API404.dict(), **validate_responses, **auth_responses}, summary="Get location by slug (with most important attributes)") +@newapi_etag(base_mapdata=True) def location_by_slug(request, location_slug: NonEmptyStr, filters: Query[RemoveGeometryFilter], redirects: Query[ShowRedirects]): return _location_retrieve( @@ -211,6 +221,7 @@ def location_by_slug(request, location_slug: NonEmptyStr, filters: Query[RemoveG @map_api_router.get('/locations/by-slug/{location_slug}/full/', response={200: FullLocationSchema, **API404.dict(), **validate_responses, **auth_responses}, summary="Get location by slug (with all attributes)") +@newapi_etag(base_mapdata=True) def location_by_slug_full(request, location_slug: NonEmptyStr, filters: Query[RemoveGeometryFilter], redirects: Query[ShowRedirects]): return _location_retrieve( @@ -223,6 +234,7 @@ def location_by_slug_full(request, location_slug: NonEmptyStr, filters: Query[Re @map_api_router.get('/locations/by-slug/{location_slug}/display/', response={200: LocationDisplay, **API404.dict(), **auth_responses}, summary="Get location display data by slug") +@newapi_etag(base_mapdata=True) def location_by_slug_display(request, location_slug: NonEmptyStr): return _location_display( request, @@ -233,6 +245,7 @@ def location_by_slug_display(request, location_slug: NonEmptyStr): @map_api_router.get('/locations/by-slug/{location_slug}/geometry/', response={200: LocationGeometry, **API404.dict(), **auth_responses}, summary="Get location geometry (if available) by slug") +@newapi_etag(base_mapdata=True) def location_by_slug_geometry(request, location_slug: NonEmptyStr): return _location_geometry( request, @@ -245,6 +258,7 @@ def location_by_slug_geometry(request, location_slug: NonEmptyStr): summary="get current position of a moving object", description="a numeric ID for a dynamic location or a string ID for the position secret can be used") def get_current_position_by_id(request, position_id: AnyPositionID): + # no caching for obvious reasons! location = None if isinstance(position_id, int) or position_id.isdigit(): location = get_location_by_id_for_request(position_id, request) diff --git a/src/c3nav/mapdata/newapi/mapdata.py b/src/c3nav/mapdata/newapi/mapdata.py index 8ce20ead..48989f07 100644 --- a/src/c3nav/mapdata/newapi/mapdata.py +++ b/src/c3nav/mapdata/newapi/mapdata.py @@ -3,7 +3,6 @@ from typing import Optional, Sequence, Type from django.db.models import Model from ninja import Query from ninja import Router as APIRouter -from ninja.pagination import paginate from c3nav.api.exceptions import API404 from c3nav.api.newauth import auth_responses, validate_responses @@ -14,6 +13,7 @@ from c3nav.mapdata.models.access import AccessPermission, AccessRestriction, Acc from c3nav.mapdata.models.geometry.space import (POI, Column, CrossDescription, LeaveDescription, LineObstacle, Obstacle, Ramp) from c3nav.mapdata.models.locations import DynamicLocation +from c3nav.mapdata.newapi.base import newapi_etag from c3nav.mapdata.schemas.filters import (ByCategoryFilter, ByGroupFilter, ByOnTopOfFilter, FilterSchema, LevelGeometryFilter, SpaceGeometryFilter) from c3nav.mapdata.schemas.models import (AccessRestrictionGroupSchema, AccessRestrictionSchema, AreaSchema, @@ -85,7 +85,7 @@ class LevelFilters(ByGroupFilter, ByOnTopOfFilter): @mapdata_api_router.get('/levels/', response={200: list[LevelSchema], **validate_responses, **auth_responses}, summary="Get level list") -@paginate +@newapi_etag() def level_list(request, filters: Query[LevelFilters]): # todo cache? return mapdata_list_endpoint(request, model=Level, filters=filters) @@ -94,6 +94,7 @@ def level_list(request, filters: Query[LevelFilters]): @mapdata_api_router.get('/levels/{level_id}/', response={200: LevelSchema, **API404.dict(), **auth_responses}, summary="Get level by ID") +@newapi_etag() def level_by_id(request, level_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, Level, pk=level_id) @@ -107,7 +108,7 @@ Buildings @mapdata_api_router.get('/buildings/', response={200: list[BuildingSchema], **validate_responses, **auth_responses}, summary="Get building list") -@paginate +@newapi_etag(base_mapdata=True) def building_list(request, filters: Query[LevelGeometryFilter]): # todo cache? return mapdata_list_endpoint(request, model=Building, filters=filters) @@ -116,6 +117,7 @@ def building_list(request, filters: Query[LevelGeometryFilter]): @mapdata_api_router.get('/buildings/{building_id}/', response={200: BuildingSchema, **API404.dict(), **auth_responses}, summary="Get building by ID") +@newapi_etag(base_mapdata=True) def building_by_id(request, building_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, Building, pk=building_id) @@ -133,7 +135,7 @@ class SpaceFilters(ByGroupFilter, LevelGeometryFilter): @mapdata_api_router.get('/spaces/', response={200: list[SpaceSchema], **validate_responses, **auth_responses}, summary="Get space list") -@paginate +@newapi_etag(base_mapdata=True) def space_list(request, filters: Query[SpaceFilters]): # todo cache? return mapdata_list_endpoint(request, model=Space, filters=filters) @@ -142,6 +144,7 @@ def space_list(request, filters: Query[SpaceFilters]): @mapdata_api_router.get('/space/{space_id}/', response={200: SpaceSchema, **API404.dict(), **auth_responses}, summary="Get space by ID") +@newapi_etag(base_mapdata=True) def space_by_id(request, space_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, Space, pk=space_id) @@ -155,7 +158,7 @@ Doors @mapdata_api_router.get('/doors/', response={200: list[DoorSchema], **validate_responses, **auth_responses}, summary="Get door list") -@paginate +@newapi_etag(base_mapdata=True) def door_list(request, filters: Query[LevelGeometryFilter]): # todo cache? return mapdata_list_endpoint(request, model=Door, filters=filters) @@ -164,6 +167,7 @@ def door_list(request, filters: Query[LevelGeometryFilter]): @mapdata_api_router.get('/doors/{door_id}/', response={200: DoorSchema, **API404.dict(), **auth_responses}, summary="Get door by ID") +@newapi_etag(base_mapdata=True) def door_by_id(request, door_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, Door, pk=door_id) @@ -177,7 +181,7 @@ Holes @mapdata_api_router.get('/holes/', response={200: list[HoleSchema], **validate_responses, **auth_responses}, summary="Get hole list") -@paginate +@newapi_etag() def hole_list(request, filters: Query[SpaceGeometryFilter]): # todo cache? return mapdata_list_endpoint(request, model=Hole, filters=filters) @@ -186,6 +190,7 @@ def hole_list(request, filters: Query[SpaceGeometryFilter]): @mapdata_api_router.get('/holes/{hole_id}/', response={200: HoleSchema, **API404.dict(), **auth_responses}, summary="Get hole by ID") +@newapi_etag() def hole_by_id(request, hole_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, Hole, pk=hole_id) @@ -203,7 +208,7 @@ class AreaFilters(ByGroupFilter, SpaceGeometryFilter): @mapdata_api_router.get('/areas/', response={200: list[AreaSchema], **validate_responses, **auth_responses}, summary="Get area list") -@paginate +@newapi_etag() def area_list(request, filters: Query[AreaFilters]): # todo cache? return mapdata_list_endpoint(request, model=Area, filters=filters) @@ -212,6 +217,7 @@ def area_list(request, filters: Query[AreaFilters]): @mapdata_api_router.get('/areas/{area_id}/', response={200: AreaSchema, **API404.dict(), **auth_responses}, summary="Get area by ID") +@newapi_etag() def area_by_id(request, area_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, Area, pk=area_id) @@ -225,7 +231,7 @@ Stairs @mapdata_api_router.get('/stairs/', response={200: list[StairSchema], **validate_responses, **auth_responses}, summary="Get stair list") -@paginate +@newapi_etag() def stair_list(request, filters: Query[SpaceGeometryFilter]): # todo cache? return mapdata_list_endpoint(request, model=Stair, filters=filters) @@ -234,6 +240,7 @@ def stair_list(request, filters: Query[SpaceGeometryFilter]): @mapdata_api_router.get('/stairs/{stair_id}/', response={200: StairSchema, **API404.dict(), **auth_responses}, summary="Get stair by ID") +@newapi_etag() def stair_by_id(request, stair_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, Stair, pk=stair_id) @@ -247,7 +254,7 @@ Ramps @mapdata_api_router.get('/ramps/', response={200: list[RampSchema], **validate_responses, **auth_responses}, summary="Get ramp list") -@paginate +@newapi_etag() def ramp_list(request, filters: Query[SpaceGeometryFilter]): # todo cache? return mapdata_list_endpoint(request, model=Ramp, filters=filters) @@ -256,6 +263,7 @@ def ramp_list(request, filters: Query[SpaceGeometryFilter]): @mapdata_api_router.get('/ramps/{ramp_id}/', response={200: RampSchema, **API404.dict(), **auth_responses}, summary="Get ramp by ID") +@newapi_etag() def ramp_by_id(request, ramp_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, Ramp, pk=ramp_id) @@ -269,7 +277,7 @@ Obstacles @mapdata_api_router.get('/obstacles/', response={200: list[ObstacleSchema], **validate_responses, **auth_responses}, summary="Get obstacle list") -@paginate +@newapi_etag() def obstacle_list(request, filters: Query[SpaceGeometryFilter]): # todo cache? return mapdata_list_endpoint(request, model=Obstacle, filters=filters) @@ -278,6 +286,7 @@ def obstacle_list(request, filters: Query[SpaceGeometryFilter]): @mapdata_api_router.get('/obstacles/{obstacle_id}/', response={200: ObstacleSchema, **API404.dict(), **auth_responses}, summary="Get obstacle by ID") +@newapi_etag() def obstacle_by_id(request, obstacle_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, Obstacle, pk=obstacle_id) @@ -291,7 +300,7 @@ LineObstacles @mapdata_api_router.get('/lineobstacles/', response={200: list[LineObstacleSchema], **validate_responses, **auth_responses}, summary="Get line obstacle list") -@paginate +@newapi_etag() def lineobstacle_list(request, filters: Query[SpaceGeometryFilter]): # todo cache? return mapdata_list_endpoint(request, model=LineObstacle, filters=filters) @@ -300,6 +309,7 @@ def lineobstacle_list(request, filters: Query[SpaceGeometryFilter]): @mapdata_api_router.get('/lineobstacles/{lineobstacle_id}/', response={200: LineObstacleSchema, **API404.dict(), **auth_responses}, summary="Get line obstacle by ID") +@newapi_etag() def lineobstacle_by_id(request, lineobstacle_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, LineObstacle, pk=lineobstacle_id) @@ -313,7 +323,7 @@ Columns @mapdata_api_router.get('/columns/', response={200: list[ColumnSchema], **validate_responses, **auth_responses}, summary="Get column list") -@paginate +@newapi_etag() def column_list(request, filters: Query[SpaceGeometryFilter]): # todo cache? return mapdata_list_endpoint(request, model=Column, filters=filters) @@ -322,6 +332,7 @@ def column_list(request, filters: Query[SpaceGeometryFilter]): @mapdata_api_router.get('/columns/{column_id}/', response={200: ColumnSchema, **API404.dict(), **auth_responses}, summary="Get column by ID") +@newapi_etag() def column_by_id(request, column_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, Column, pk=column_id) @@ -335,7 +346,7 @@ POIs @mapdata_api_router.get('/pois/', response={200: list[POISchema], **validate_responses, **auth_responses}, summary="Get POI list") -@paginate +@newapi_etag() def poi_list(request, filters: Query[SpaceGeometryFilter]): # todo cache? return mapdata_list_endpoint(request, model=POI, filters=filters) @@ -344,6 +355,7 @@ def poi_list(request, filters: Query[SpaceGeometryFilter]): @mapdata_api_router.get('/pois/{poi_id}/', response={200: POISchema, **API404.dict(), **auth_responses}, summary="Get POI by ID") +@newapi_etag() def poi_by_id(request, poi_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, POI, pk=poi_id) @@ -357,7 +369,7 @@ LeaveDescriptions @mapdata_api_router.get('/leavedescriptions/', response={200: list[LeaveDescriptionSchema], **validate_responses, **auth_responses}, summary="Get leave description list") -@paginate +@newapi_etag() def leavedescription_list(request, filters: Query[SpaceGeometryFilter]): # todo cache? return mapdata_list_endpoint(request, model=LeaveDescription, filters=filters) @@ -366,6 +378,7 @@ def leavedescription_list(request, filters: Query[SpaceGeometryFilter]): @mapdata_api_router.get('/leavedescriptions/{leavedescription_id}/', response={200: LeaveDescriptionSchema, **API404.dict(), **auth_responses}, summary="Get leave description by ID") +@newapi_etag() def leavedescription_by_id(request, leavedescription_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, LeaveDescription, pk=leavedescription_id) @@ -379,7 +392,7 @@ CrossDescriptions @mapdata_api_router.get('/crossdescriptions/', response={200: list[CrossDescriptionSchema], **validate_responses, **auth_responses}, summary="Get cross description list") -@paginate +@newapi_etag() def crossdescription_list(request, filters: Query[SpaceGeometryFilter]): # todo cache? return mapdata_list_endpoint(request, model=CrossDescription, filters=filters) @@ -388,6 +401,7 @@ def crossdescription_list(request, filters: Query[SpaceGeometryFilter]): @mapdata_api_router.get('/crossdescriptions/{crossdescription_id}/', response={200: CrossDescriptionSchema, **API404.dict(), **auth_responses}, summary="Get cross description by ID") +@newapi_etag() def crossdescription_by_id(request, crossdescription_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, CrossDescription, pk=crossdescription_id) @@ -401,7 +415,7 @@ LocationGroup @mapdata_api_router.get('/locationgroups/', response={200: list[LocationGroupSchema], **validate_responses, **auth_responses}, summary="Get location group list") -@paginate +@newapi_etag() def locationgroup_list(request, filters: Query[ByCategoryFilter]): # todo cache? return mapdata_list_endpoint(request, model=LocationGroup, filters=filters) @@ -410,6 +424,7 @@ def locationgroup_list(request, filters: Query[ByCategoryFilter]): @mapdata_api_router.get('/locationgroups/{locationgroup_id}/', response={200: LocationGroupSchema, **API404.dict(), **auth_responses}, summary="Get location group by ID") +@newapi_etag() def locationgroup_by_id(request, locationgroup_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, LocationGroup, pk=locationgroup_id) @@ -423,7 +438,7 @@ LocationGroupCategories @mapdata_api_router.get('/locationgroupcategories/', response={200: list[LocationGroupCategorySchema], **auth_responses}, summary="Get location group category list") -@paginate +@newapi_etag() def locationgroupcategory_list(request): # todo cache? return mapdata_list_endpoint(request, model=LocationGroupCategory) @@ -432,6 +447,7 @@ def locationgroupcategory_list(request): @mapdata_api_router.get('/locationgroupcategories/{category_id}/', response={200: LocationGroupCategorySchema, **API404.dict(), **auth_responses}, summary="Get location group category by ID") +@newapi_etag() def locationgroupcategory_by_id(request, category_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, LocationGroupCategory, pk=category_id) @@ -445,7 +461,7 @@ Sources @mapdata_api_router.get('/sources/', response={200: list[SourceSchema], **auth_responses}, summary="Get source list") -@paginate +@newapi_etag() def source_list(request): # todo cache? return mapdata_list_endpoint(request, model=Source) @@ -454,6 +470,7 @@ def source_list(request): @mapdata_api_router.get('/sources/{source_id}/', response={200: SourceSchema, **API404.dict(), **auth_responses}, summary="Get source by ID") +@newapi_etag() def source_by_id(request, source_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, Source, pk=source_id) @@ -467,7 +484,7 @@ AccessRestrictions @mapdata_api_router.get('/accessrestrictions/', response={200: list[AccessRestrictionSchema], **auth_responses}, summary="Get access restriction list") -@paginate +@newapi_etag() def accessrestriction_list(request): # todo cache? return mapdata_list_endpoint(request, model=AccessRestriction) @@ -476,6 +493,7 @@ def accessrestriction_list(request): @mapdata_api_router.get('/accessrestrictions/{accessrestriction_id}/', response={200: AccessRestrictionSchema, **API404.dict(), **auth_responses}, summary="Get access restriction by ID") +@newapi_etag() def accessrestriction_by_id(request, accessrestriction_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, AccessRestriction, pk=accessrestriction_id) @@ -489,7 +507,7 @@ AccessRestrictionGroups @mapdata_api_router.get('/accessrestrictiongroups/', response={200: list[AccessRestrictionGroupSchema], **auth_responses}, summary="Get access restriction group list") -@paginate +@newapi_etag() def accessrestrictiongroup_list(request): # todo cache? return mapdata_list_endpoint(request, model=AccessRestrictionGroup) @@ -498,6 +516,7 @@ def accessrestrictiongroup_list(request): @mapdata_api_router.get('/accessrestrictiongroups/{group_id}/', response={200: AccessRestrictionGroupSchema, **API404.dict(), **auth_responses}, summary="Get access restriction group by ID") +@newapi_etag() def accessrestrictiongroups_by_id(request, group_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, AccessRestrictionGroup, pk=group_id) @@ -511,7 +530,7 @@ DynamicLocations @mapdata_api_router.get('/dynamiclocations/', response={200: list[DynamicLocationSchema], **auth_responses}, summary="Get dynamic location list") -@paginate +@newapi_etag() def dynamiclocation_list(request): # todo cache? return mapdata_list_endpoint(request, model=DynamicLocation) @@ -520,6 +539,7 @@ def dynamiclocation_list(request): @mapdata_api_router.get('/dynamiclocations/{dynamiclocation_id}/', response={200: DynamicLocationSchema, **API404.dict(), **auth_responses}, summary="Get dynamic location by ID") +@newapi_etag() def dynamiclocation_by_id(request, dynamiclocation_id: int): # todo: access, caching, filtering, etc return mapdata_retrieve_endpoint(request, DynamicLocation, pk=dynamiclocation_id)