optimize traffic for editor geometries / cache objects locally
This commit is contained in:
parent
7062a6c446
commit
b3f307b9a6
7 changed files with 88 additions and 11 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
from functools import wraps
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
from django.db.models import Prefetch, Q
|
from django.db.models import Prefetch, Q
|
||||||
|
@ -31,6 +32,29 @@ class EditorViewSetMixin(ViewSet):
|
||||||
return super().initial(request, *args, **kwargs)
|
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):
|
class EditorViewSet(EditorViewSetMixin, ViewSet):
|
||||||
"""
|
"""
|
||||||
Editor API
|
Editor API
|
||||||
|
@ -103,8 +127,8 @@ class EditorViewSet(EditorViewSetMixin, ViewSet):
|
||||||
|
|
||||||
# noinspection PyPep8Naming
|
# noinspection PyPep8Naming
|
||||||
@action(detail=False, methods=['get'])
|
@action(detail=False, methods=['get'])
|
||||||
@api_etag(etag_func=etag_func, cache_parameters={'level': str, 'space': str})
|
@api_etag_with_update_cache_key(etag_func=etag_func, cache_parameters={'level': str, 'space': str})
|
||||||
def geometries(self, request, *args, **kwargs):
|
def geometries(self, request, update_cache_key, update_cache_key_match, *args, **kwargs):
|
||||||
Level = request.changeset.wrap_model('Level')
|
Level = request.changeset.wrap_model('Level')
|
||||||
Space = request.changeset.wrap_model('Space')
|
Space = request.changeset.wrap_model('Space')
|
||||||
Column = request.changeset.wrap_model('Column')
|
Column = request.changeset.wrap_model('Column')
|
||||||
|
@ -180,8 +204,6 @@ class EditorViewSet(EditorViewSetMixin, ViewSet):
|
||||||
# graphedges,
|
# graphedges,
|
||||||
# graphnodes,
|
# graphnodes,
|
||||||
)
|
)
|
||||||
|
|
||||||
return Response([obj.to_geojson(instance=obj) for obj in results])
|
|
||||||
elif space is not None:
|
elif space is not None:
|
||||||
space_q_for_request = Space.q_for_request(request)
|
space_q_for_request = Space.q_for_request(request)
|
||||||
qs = Space.objects.filter(space_q_for_request)
|
qs = Space.objects.filter(space_q_for_request)
|
||||||
|
@ -295,10 +317,21 @@ class EditorViewSet(EditorViewSetMixin, ViewSet):
|
||||||
graphedges,
|
graphedges,
|
||||||
graphnodes
|
graphnodes
|
||||||
)
|
)
|
||||||
return Response([obj.to_geojson(instance=obj) for obj in results])
|
|
||||||
else:
|
else:
|
||||||
raise ValidationError('No level or space specified.')
|
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'])
|
@action(detail=False, methods=['get'])
|
||||||
@api_etag(etag_func=MapUpdate.current_cache_key, cache_parameters={})
|
@api_etag(etag_func=MapUpdate.current_cache_key, cache_parameters={})
|
||||||
def geometrystyles(self, request, *args, **kwargs):
|
def geometrystyles(self, request, *args, **kwargs):
|
||||||
|
|
|
@ -799,6 +799,12 @@ class ChangeSet(models.Model):
|
||||||
def cache_key_by_changes(self):
|
def cache_key_by_changes(self):
|
||||||
return 'editor:changeset:' + self.raw_cache_key_by_changes
|
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
|
@property
|
||||||
def raw_cache_key_by_changes(self):
|
def raw_cache_key_by_changes(self):
|
||||||
if self.pk is None:
|
if self.pk is None:
|
||||||
|
|
|
@ -671,8 +671,11 @@ editor = {
|
||||||
editor.map.setMaxBounds(bounds);
|
editor.map.setMaxBounds(bounds);
|
||||||
},
|
},
|
||||||
_last_geometry_url: null,
|
_last_geometry_url: null,
|
||||||
|
_last_geometry_update_cache_key: null,
|
||||||
|
_last_geometry_cache: {},
|
||||||
load_geometries: function (geometry_url, highlight_type, editing_id) {
|
load_geometries: function (geometry_url, highlight_type, editing_id) {
|
||||||
// load geometries from url
|
// load geometries from url
|
||||||
|
var same_url = (editor._last_geometry_url == geometry_url);
|
||||||
editor._last_geometry_url = geometry_url;
|
editor._last_geometry_url = geometry_url;
|
||||||
editor._loading_geometry = true;
|
editor._loading_geometry = true;
|
||||||
editor._highlight_type = highlight_type;
|
editor._highlight_type = highlight_type;
|
||||||
|
@ -688,15 +691,34 @@ editor = {
|
||||||
editor._graph_edges_to = {};
|
editor._graph_edges_to = {};
|
||||||
|
|
||||||
editor._set_max_bounds();
|
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.map.removeLayer(editor._highlight_layer);
|
||||||
editor._highlight_layer.clearLayers();
|
editor._highlight_layer.clearLayers();
|
||||||
if (editor._geometries_layer !== null) {
|
if (editor._geometries_layer !== null) {
|
||||||
editor.map.removeLayer(editor._geometries_layer);
|
editor.map.removeLayer(editor._geometries_layer);
|
||||||
}
|
}
|
||||||
var feature = null,
|
var remove_feature = null;
|
||||||
remove_feature = null,
|
|
||||||
i;
|
|
||||||
if (editor._editing_id !== null) {
|
if (editor._editing_id !== null) {
|
||||||
for (i=0;i<geometries.length;i++) {
|
for (i=0;i<geometries.length;i++) {
|
||||||
feature = geometries[i];
|
feature = geometries[i];
|
||||||
|
|
|
@ -191,8 +191,11 @@ class ModelInstanceWrapper(BaseWrapper):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
self._affected_by_changeset = False
|
||||||
if self._obj.pk is not None:
|
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):
|
def __eq__(self, other):
|
||||||
if isinstance(other, BaseWrapper):
|
if isinstance(other, BaseWrapper):
|
||||||
|
|
|
@ -69,7 +69,8 @@ def api_stats(view_name):
|
||||||
return wrapper
|
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):
|
def wrapper(func):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapped_func(self, request, *args, **kwargs):
|
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():
|
for param, type_ in cache_parameters.items():
|
||||||
value = int(param in request.GET) if type_ == bool else type_(request.GET.get(param))
|
value = int(param in request.GET) if type_ == bool else type_(request.GET.get(param))
|
||||||
cache_key += ':'+urlsafe_base64_encode(str(value).encode())
|
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)
|
data = request_cache.get(cache_key)
|
||||||
if data is not None:
|
if data is not None:
|
||||||
response = Response(data)
|
response = Response(data)
|
||||||
|
|
|
@ -52,6 +52,9 @@ class GeometryMixin(SerializableMixin):
|
||||||
result['bounds'] = True
|
result['bounds'] = True
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def get_geojson_key(self):
|
||||||
|
return (self.__class__.__name__.lower(), self.id)
|
||||||
|
|
||||||
def to_geojson(self, instance=None) -> dict:
|
def to_geojson(self, instance=None) -> dict:
|
||||||
result = {
|
result = {
|
||||||
'type': 'Feature',
|
'type': 'Feature',
|
||||||
|
|
|
@ -89,3 +89,6 @@ class GraphEdge(AccessRestrictionMixin, models.Model):
|
||||||
if self.waytype_id is not None:
|
if self.waytype_id is not None:
|
||||||
result['properties']['color'] = self.waytype.color
|
result['properties']['color'] = self.waytype.color
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def get_geojson_key(self):
|
||||||
|
return (self.__class__.__name__.lower(), self.pk)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue