fix etag_add feature and overlay stuff and generated api naming thing
This commit is contained in:
parent
2063b715e7
commit
393637b6ad
3 changed files with 29 additions and 23 deletions
|
@ -49,6 +49,18 @@ def api_etag(permissions=True, quests=False, etag_func=AccessPermission.etag_fun
|
||||||
raw_etag += 'all' if request.user.is_superuser else f':{','.join(request.user_permissions.quests)}'
|
raw_etag += 'all' if request.user.is_superuser else f':{','.join(request.user_permissions.quests)}'
|
||||||
if base_mapdata:
|
if base_mapdata:
|
||||||
raw_etag += ':%d' % request.user_permissions.can_access_base_mapdata
|
raw_etag += ':%d' % request.user_permissions.can_access_base_mapdata
|
||||||
|
|
||||||
|
if etag_add_key:
|
||||||
|
etag_add_cache_key = (
|
||||||
|
f'mapdata:etag_add:{etag_add_key[1]}:{getattr(kwargs[etag_add_key[0]], etag_add_key[1])}'
|
||||||
|
)
|
||||||
|
etag_add = cache.get(etag_add_cache_key, None)
|
||||||
|
if etag_add is None:
|
||||||
|
etag_add = int(time.time())
|
||||||
|
cache.set(etag_add_cache_key, etag_add, 300)
|
||||||
|
raw_etag += ':%d' % etag_add
|
||||||
|
|
||||||
|
|
||||||
etag = quote_etag(raw_etag)
|
etag = quote_etag(raw_etag)
|
||||||
|
|
||||||
response = get_conditional_response(request, etag)
|
response = get_conditional_response(request, etag)
|
||||||
|
@ -68,19 +80,9 @@ def api_etag(permissions=True, quests=False, etag_func=AccessPermission.etag_fun
|
||||||
value = model_dump()
|
value = model_dump()
|
||||||
data[name] = value
|
data[name] = value
|
||||||
|
|
||||||
etag_add = ''
|
cache_key = 'mapdata:api:%s:%s:%s' % (
|
||||||
if etag_add_key:
|
|
||||||
etag_add_cache_key = (
|
|
||||||
f'mapdata:etag_add:{etag_add_key[1]}:{getattr(kwargs[etag_add_key[0]], etag_add_key[1])}'
|
|
||||||
)
|
|
||||||
etag_add = cache.get(etag_add_cache_key, None)
|
|
||||||
if etag_add is None:
|
|
||||||
etag_add = int(time.time())
|
|
||||||
cache.set(etag_add_cache_key, etag_add, 300)
|
|
||||||
cache_key = 'mapdata:api:%s:%s:%s:%s' % (
|
|
||||||
request.resolver_match.route.replace('/', '-').strip('-'),
|
request.resolver_match.route.replace('/', '-').strip('-'),
|
||||||
raw_etag,
|
raw_etag,
|
||||||
etag_add,
|
|
||||||
json.dumps(data, separators=(',', ':'), sort_keys=True, cls=DjangoJSONEncoder),
|
json.dumps(data, separators=(',', ':'), sort_keys=True, cls=DjangoJSONEncoder),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,12 @@ class MapdataEndpoint:
|
||||||
schema: Type[BaseSchema]
|
schema: Type[BaseSchema]
|
||||||
filters: Type[FilterSchema] | None = None
|
filters: Type[FilterSchema] | None = None
|
||||||
no_cache: bool = False
|
no_cache: bool = False
|
||||||
|
|
||||||
|
# etag_add_key is a weird, limited and hacky solution to add cache/etag invalidation for data that can be changed without triggering mapupdate
|
||||||
|
# if set, its value *must* be the name of an attribute in the filter schema, and the value of that filter will be used in the etag/cache key
|
||||||
|
# all api endpoints that have the same etag_add_key value have shared cache/etag invalidation, i.e. if one is invalidated then all are
|
||||||
|
# the cache is invalidated by deleting the cache key "mapdata:etag_add:<etag_add_key>:<filter-value>" where filter-value is the value of the
|
||||||
|
# filter attribute that etag_add_key references
|
||||||
etag_add_key: Optional[str] = None
|
etag_add_key: Optional[str] = None
|
||||||
name: Optional[str] = None
|
name: Optional[str] = None
|
||||||
|
|
||||||
|
@ -91,6 +97,10 @@ class MapdataEndpoint:
|
||||||
def endpoint_name(self):
|
def endpoint_name(self):
|
||||||
return self.name if self.name is not None else self.model._meta.default_related_name
|
return self.name if self.name is not None else self.model._meta.default_related_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def endpoint_operation_name(self):
|
||||||
|
return self.name if self.name is not None else self.model_name
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MapdataAPIBuilder:
|
class MapdataAPIBuilder:
|
||||||
|
@ -142,7 +152,8 @@ class MapdataAPIBuilder:
|
||||||
call_func=mapdata_list_endpoint,
|
call_func=mapdata_list_endpoint,
|
||||||
add_call_params={"model": endpoint.model.__name__}
|
add_call_params={"model": endpoint.model.__name__}
|
||||||
)
|
)
|
||||||
list_func.__name__ = f"{endpoint.model_name}_list"
|
list_func.__name__ = f"{endpoint.endpoint_operation_name}_list"
|
||||||
|
|
||||||
|
|
||||||
if not endpoint.no_cache:
|
if not endpoint.no_cache:
|
||||||
list_func = api_etag(
|
list_func = api_etag(
|
||||||
|
@ -168,7 +179,7 @@ class MapdataAPIBuilder:
|
||||||
call_func=mapdata_retrieve_endpoint,
|
call_func=mapdata_retrieve_endpoint,
|
||||||
add_call_params={"model": endpoint.model.__name__, "pk": id_field}
|
add_call_params={"model": endpoint.model.__name__, "pk": id_field}
|
||||||
)
|
)
|
||||||
list_func.__name__ = f"{endpoint.model_name}_by_id"
|
list_func.__name__ = f"{endpoint.endpoint_operation_name}_by_id"
|
||||||
|
|
||||||
self.router.get(f'/{endpoint.endpoint_name}/{{{id_field}}}/', summary=f"{endpoint.model_name} by ID",
|
self.router.get(f'/{endpoint.endpoint_name}/{{{id_field}}}/', summary=f"{endpoint.model_name} by ID",
|
||||||
tags=[f"mapdata-{tag}"], description=schema_description(endpoint.schema),
|
tags=[f"mapdata-{tag}"], description=schema_description(endpoint.schema),
|
||||||
|
@ -243,7 +254,6 @@ mapdata_endpoints: dict[str, list[MapdataEndpoint]] = {
|
||||||
model=DataOverlayFeature,
|
model=DataOverlayFeature,
|
||||||
schema=DataOverlayFeatureGeometrySchema,
|
schema=DataOverlayFeatureGeometrySchema,
|
||||||
filters=ByOverlayFilter,
|
filters=ByOverlayFilter,
|
||||||
etag_add_key="overlay",
|
|
||||||
name='dataoverlayfeaturegeometries'
|
name='dataoverlayfeaturegeometries'
|
||||||
),
|
),
|
||||||
MapdataEndpoint(
|
MapdataEndpoint(
|
||||||
|
@ -325,13 +335,9 @@ mapdata_endpoints: dict[str, list[MapdataEndpoint]] = {
|
||||||
MapdataAPIBuilder(router=mapdata_api_router).build_all_endpoints(mapdata_endpoints)
|
MapdataAPIBuilder(router=mapdata_api_router).build_all_endpoints(mapdata_endpoints)
|
||||||
|
|
||||||
|
|
||||||
@mapdata_api_router.post('/dataoverlayfeatures/{id}', summary="update a data overlay feature (including geometries)",
|
@mapdata_api_router.post('/dataoverlayfeatures/{id}', summary="update a data overlay feature (no geometries)",
|
||||||
response={204: None, **API404.dict(), **auth_permission_responses})
|
response={204: None, **API404.dict(), **auth_permission_responses})
|
||||||
def update_data_overlay_feature(request, id: int, parameters: DataOverlayFeatureUpdateSchema):
|
def update_data_overlay_feature(request, id: int, parameters: DataOverlayFeatureUpdateSchema):
|
||||||
"""
|
|
||||||
update the data overlay feature
|
|
||||||
"""
|
|
||||||
|
|
||||||
feature = get_object_or_404(DataOverlayFeature, id=id)
|
feature = get_object_or_404(DataOverlayFeature, id=id)
|
||||||
|
|
||||||
if feature.overlay.edit_access_restriction_id is None or feature.overlay.edit_access_restriction_id not in AccessPermission.get_for_request(
|
if feature.overlay.edit_access_restriction_id is None or feature.overlay.edit_access_restriction_id not in AccessPermission.get_for_request(
|
||||||
|
@ -350,7 +356,7 @@ def update_data_overlay_feature(request, id: int, parameters: DataOverlayFeature
|
||||||
return 204, None
|
return 204, None
|
||||||
|
|
||||||
|
|
||||||
@mapdata_api_router.post('/dataoverlayfeatures-bulk', summary="bulk-update data overlays (including geometries)",
|
@mapdata_api_router.post('/dataoverlayfeatures-bulk', summary="bulk-update data overlays (no geometries)",
|
||||||
response={204: None, **API404.dict(), **auth_permission_responses})
|
response={204: None, **API404.dict(), **auth_permission_responses})
|
||||||
def update_data_overlay_features_bulk(request, parameters: DataOverlayFeatureBulkUpdateSchema):
|
def update_data_overlay_features_bulk(request, parameters: DataOverlayFeatureBulkUpdateSchema):
|
||||||
permissions = AccessPermission.get_for_request(request)
|
permissions = AccessPermission.get_for_request(request)
|
||||||
|
|
|
@ -408,7 +408,6 @@ class DataOverlayFeatureUpdateSchema(BaseSchema):
|
||||||
An update to a data overlay feature.
|
An update to a data overlay feature.
|
||||||
"""
|
"""
|
||||||
level_id: Optional[PositiveInt] = None
|
level_id: Optional[PositiveInt] = None
|
||||||
geometry: Optional[AnyGeometrySchema] = None
|
|
||||||
stroke_color: Optional[str] = None
|
stroke_color: Optional[str] = None
|
||||||
stroke_width: Optional[float] = None
|
stroke_width: Optional[float] = None
|
||||||
stroke_opacity: Optional[float] = None
|
stroke_opacity: Optional[float] = None
|
||||||
|
@ -423,11 +422,10 @@ class DataOverlayFeatureUpdateSchema(BaseSchema):
|
||||||
|
|
||||||
class DataOverlayFeatureBulkUpdateItemSchema(BaseSchema):
|
class DataOverlayFeatureBulkUpdateItemSchema(BaseSchema):
|
||||||
"""
|
"""
|
||||||
An item of a bulk update to data overlay features.
|
An item of a bulk update to data overlay features (no geometries).
|
||||||
"""
|
"""
|
||||||
id: PositiveInt
|
id: PositiveInt
|
||||||
level_id: Optional[PositiveInt] = None
|
level_id: Optional[PositiveInt] = None
|
||||||
geometry: Optional[AnyGeometrySchema] = None
|
|
||||||
stroke_color: Optional[str] = None
|
stroke_color: Optional[str] = None
|
||||||
stroke_width: Optional[float] = None
|
stroke_width: Optional[float] = None
|
||||||
stroke_opacity: Optional[float] = None
|
stroke_opacity: Optional[float] = None
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue