add heavy caching to mapdata api

This commit is contained in:
Laura Klünder 2016-12-07 16:11:33 +01:00
parent 23da7e3605
commit 00193f7e11
22 changed files with 259 additions and 138 deletions

View file

View file

@ -0,0 +1,99 @@
from calendar import timegm
from functools import wraps
from django.core.cache import cache
from django.utils.http import http_date
from rest_framework.response import Response as APIResponse
from rest_framework.views import APIView
from c3nav.mapdata.lastupdate import get_last_mapdata_update
def cache_result(cache_key, timeout=900):
def decorator(func):
@wraps(func)
def inner(*args, **kwargs):
last_update = get_last_mapdata_update()
if last_update is None:
return func(*args, **kwargs)
result = cache.get(cache_key)
if not result:
result = func(*args, **kwargs)
cache.set(cache_key, result, timeout)
return result
return inner
return decorator
def cache_mapdata_api_response(timeout=900):
def decorator(func):
@wraps(func)
def inner(self, request, *args, add_cache_key=None, **kwargs):
last_update = get_last_mapdata_update()
if last_update is None:
return func(self, request, *args, **kwargs)
cache_key = '__'.join((
'c3nav__mapdata__api',
last_update.isoformat(),
add_cache_key if add_cache_key is not None else '',
request.accepted_renderer.format if isinstance(self, APIView) else '',
request.path,
))
response = cache.get(cache_key)
if not response:
response = func(self, request, *args, **kwargs)
response['Last-Modifed'] = http_date(timegm(last_update.utctimetuple()))
if isinstance(response, APIResponse):
response = self.finalize_response(request, response, *args, **kwargs)
response.render()
if response.status_code < 400:
cache.set(cache_key, response, timeout)
return response
return inner
return decorator
class CachedReadOnlyViewSetMixin():
include_package_access = False
def _get_unlocked_packages_ids(self, request):
from c3nav.mapdata.permissions import get_unlocked_packages
return ','.join(str(i) for i in sorted(package.id for package in get_unlocked_packages(request)))
def _get_add_cache_key(self, request, add_cache_key=''):
cache_key = add_cache_key
if self.include_package_access:
cache_key += '__'+self._get_unlocked_packages_ids(request)
return cache_key
def list(self, request, *args, **kwargs):
kwargs['add_cache_key'] = self._get_add_cache_key(request, kwargs.get('add_cache_key', ''))
return self._list(request, *args, **kwargs)
@cache_mapdata_api_response()
def _list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
def retrieve(self, request, *args, **kwargs):
kwargs['add_cache_key'] = self._get_add_cache_key(request, kwargs.get('add_cache_key', ''))
return self._retrieve(request, *args, **kwargs)
@cache_mapdata_api_response()
def _retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
@cache_result('c3nav__mapdata__levels')
def get_levels_cached():
from c3nav.mapdata.models import Level
return {level.name: level for level in Level.objects.all()}
@cache_result('c3nav__mapdata__packages')
def get_packages_cached():
from c3nav.mapdata.models import Package
return {package.name: package for package in Package.objects.all()}

View file

@ -0,0 +1,32 @@
from shapely.geometry import Polygon
def clean_geometry(geometry):
"""
if the given geometry is a Polygon and invalid, try to make it valid if it results in a Polygon (not MultiPolygon)
"""
if geometry.is_valid:
return geometry
if isinstance(geometry, Polygon):
p = Polygon(list(geometry.exterior.coords))
for interior in geometry.interiors:
p = p.difference(Polygon(list(interior.coords)))
if isinstance(p, Polygon) and p.is_valid:
return p
return geometry
def assert_multipolygon(geometry):
"""
given a Polygon or a MultiPolygon, return a list of Polygons
:param geometry: a Polygon or a MultiPolygon
:return: a list of Polygons
"""
if isinstance(geometry, Polygon):
polygons = [geometry]
else:
polygons = geometry.geoms
return polygons

View file

@ -0,0 +1,47 @@
import json
from collections import OrderedDict
def _preencode(data, magic_marker, in_coords=False):
if isinstance(data, dict):
data = data.copy()
for name, value in tuple(data.items()):
if name in ('bounds', ):
data[name] = magic_marker+json.dumps(value)+magic_marker
else:
data[name] = _preencode(value, magic_marker, in_coords=(name == 'coordinates'))
return data
elif isinstance(data, (tuple, list)):
if in_coords and len(data) == 2 and isinstance(data[0], (int, float)) and isinstance(data[1], (int, float)):
return magic_marker+json.dumps(data)+magic_marker
else:
return tuple(_preencode(value, magic_marker, in_coords) for value in data)
else:
return data
def json_encoder_reindent(method, data, *args, **kwargs):
magic_marker = '***JSON_MAGIC_MARKER***'
test_encode = json.dumps(data)
while magic_marker in test_encode:
magic_marker += '*'
result = method(_preencode(data, magic_marker), *args, **kwargs)
if type(result) == str:
return result.replace('"'+magic_marker, '').replace(magic_marker+'"', '')
else:
magic_marker = magic_marker.encode()
return result.replace(b'"'+magic_marker, b'').replace(magic_marker+b'"', b'')
def format_geojson(data, round=True):
return OrderedDict((
('type', data['type']),
('coordinates', round_coordinates(data['coordinates']) if round else data['coordinates']),
))
def round_coordinates(data):
if isinstance(data, (list, tuple)):
return tuple(round_coordinates(item) for item in data)
else:
return round(data, 2)