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 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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue