fix etag_add feature and overlay stuff and generated api naming thing

This commit is contained in:
Gwendolyn 2024-12-27 02:25:07 +01:00
parent 2063b715e7
commit 393637b6ad
3 changed files with 29 additions and 23 deletions

View file

@ -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),
) )

View file

@ -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)

View file

@ -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