optimize traffic for editor geometries / cache objects locally

This commit is contained in:
Laura Klünder 2019-12-10 00:11:59 +01:00
parent 7062a6c446
commit b3f307b9a6
7 changed files with 88 additions and 11 deletions

View file

@ -1,3 +1,4 @@
from functools import wraps
from itertools import chain
from django.db.models import Prefetch, Q
@ -31,6 +32,29 @@ class EditorViewSetMixin(ViewSet):
return super().initial(request, *args, **kwargs)
def api_etag_with_update_cache_key(**outkwargs):
outkwargs.setdefault('cache_kwargs', {})['update_cache_key_match'] = bool
def wrapper(func):
func = api_etag(**outkwargs)(func)
@wraps(func)
def wrapped_func(self, request, *args, **kwargs):
try:
changeset = request.changeset
except AttributeError:
changeset = ChangeSet.get_for_request(request)
request.changeset = changeset
update_cache_key = request.changeset.raw_cache_key_without_changes
update_cache_key_match = request.GET.get('update_cache_key') == update_cache_key
return func(self, request, *args,
update_cache_key=update_cache_key, update_cache_key_match=update_cache_key_match,
**kwargs)
return wrapped_func
return wrapper
class EditorViewSet(EditorViewSetMixin, ViewSet):
"""
Editor API
@ -103,8 +127,8 @@ class EditorViewSet(EditorViewSetMixin, ViewSet):
# noinspection PyPep8Naming
@action(detail=False, methods=['get'])
@api_etag(etag_func=etag_func, cache_parameters={'level': str, 'space': str})
def geometries(self, request, *args, **kwargs):
@api_etag_with_update_cache_key(etag_func=etag_func, cache_parameters={'level': str, 'space': str})
def geometries(self, request, update_cache_key, update_cache_key_match, *args, **kwargs):
Level = request.changeset.wrap_model('Level')
Space = request.changeset.wrap_model('Space')
Column = request.changeset.wrap_model('Column')
@ -180,8 +204,6 @@ class EditorViewSet(EditorViewSetMixin, ViewSet):
# graphedges,
# graphnodes,
)
return Response([obj.to_geojson(instance=obj) for obj in results])
elif space is not None:
space_q_for_request = Space.q_for_request(request)
qs = Space.objects.filter(space_q_for_request)
@ -295,10 +317,21 @@ class EditorViewSet(EditorViewSetMixin, ViewSet):
graphedges,
graphnodes
)
return Response([obj.to_geojson(instance=obj) for obj in results])
else:
raise ValidationError('No level or space specified.')
return Response({
'update_cache_key': update_cache_key,
'geometries': [
(
obj.get_geojson_key()
if update_cache_key_match and not obj._affected_by_changeset
else obj.to_geojson(instance=obj)
)
for obj in results
],
})
@action(detail=False, methods=['get'])
@api_etag(etag_func=MapUpdate.current_cache_key, cache_parameters={})
def geometrystyles(self, request, *args, **kwargs):

View file

@ -799,6 +799,12 @@ class ChangeSet(models.Model):
def cache_key_by_changes(self):
return 'editor:changeset:' + self.raw_cache_key_by_changes
@property
def raw_cache_key_without_changes(self):
if self.pk is None:
return MapUpdate.current_cache_key()
return ':'.join((str(self.pk), MapUpdate.current_cache_key()))
@property
def raw_cache_key_by_changes(self):
if self.pk is None:

View file

@ -671,8 +671,11 @@ editor = {
editor.map.setMaxBounds(bounds);
},
_last_geometry_url: null,
_last_geometry_update_cache_key: null,
_last_geometry_cache: {},
load_geometries: function (geometry_url, highlight_type, editing_id) {
// load geometries from url
var same_url = (editor._last_geometry_url == geometry_url);
editor._last_geometry_url = geometry_url;
editor._loading_geometry = true;
editor._highlight_type = highlight_type;
@ -688,15 +691,34 @@ editor = {
editor._graph_edges_to = {};
editor._set_max_bounds();
$.getJSON(geometry_url, function(geometries) {
if (same_url && editor._last_geometry_update_cache_key) {
geometry_url += '&update_cache_key='+editor._last_geometry_update_cache_key;
}
$.getJSON(geometry_url, function(result) {
var geometries = [], feature, new_cache = {}, feature_type, feature_id;
// geometries cache logic
for (var i=0;i<result.geometries.length;i++) {
feature = result.geometries[i];
if (Array.isArray(feature)) {
// load from cache
feature = editor._last_geometry_cache[feature[0]][feature[1]];
}
if (!new_cache[feature.properties.type]) {
new_cache[feature.properties.type] = {};
}
new_cache[feature.properties.type][feature.properties.id] = feature;
geometries.push(feature);
}
editor._last_geometry_cache = new_cache;
editor._last_geometry_update_cache_key = result.update_cache_key;
editor.map.removeLayer(editor._highlight_layer);
editor._highlight_layer.clearLayers();
if (editor._geometries_layer !== null) {
editor.map.removeLayer(editor._geometries_layer);
}
var feature = null,
remove_feature = null,
i;
var remove_feature = null;
if (editor._editing_id !== null) {
for (i=0;i<geometries.length;i++) {
feature = geometries[i];

View file

@ -191,8 +191,11 @@ class ModelInstanceWrapper(BaseWrapper):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._affected_by_changeset = False
if self._obj.pk is not None:
self._changeset.get_changed_object(self._obj).apply_to_instance(self)
changed_object = self._changeset.get_changed_object(self._obj)
self._affected_by_changeset = changed_object.pk is not None
changed_object.apply_to_instance(self)
def __eq__(self, other):
if isinstance(other, BaseWrapper):

View file

@ -69,7 +69,8 @@ def api_stats(view_name):
return wrapper
def api_etag(permissions=True, etag_func=AccessPermission.etag_func, cache_parameters=None, base_mapdata_check=False):
def api_etag(permissions=True, etag_func=AccessPermission.etag_func,
cache_parameters=None, cache_kwargs=None, base_mapdata_check=False):
def wrapper(func):
@wraps(func)
def wrapped_func(self, request, *args, **kwargs):
@ -88,6 +89,12 @@ def api_etag(permissions=True, etag_func=AccessPermission.etag_func, cache_param
for param, type_ in cache_parameters.items():
value = int(param in request.GET) if type_ == bool else type_(request.GET.get(param))
cache_key += ':'+urlsafe_base64_encode(str(value).encode())
if cache_kwargs is not None:
for name, type_ in cache_kwargs.items():
value = type_(kwargs[name])
if type_ == bool:
value = int(value)
cache_key += ':'+urlsafe_base64_encode(str(value).encode())
data = request_cache.get(cache_key)
if data is not None:
response = Response(data)

View file

@ -52,6 +52,9 @@ class GeometryMixin(SerializableMixin):
result['bounds'] = True
return result
def get_geojson_key(self):
return (self.__class__.__name__.lower(), self.id)
def to_geojson(self, instance=None) -> dict:
result = {
'type': 'Feature',

View file

@ -89,3 +89,6 @@ class GraphEdge(AccessRestrictionMixin, models.Model):
if self.waytype_id is not None:
result['properties']['color'] = self.waytype.color
return result
def get_geojson_key(self):
return (self.__class__.__name__.lower(), self.pk)