add REST API
This commit is contained in:
parent
906eaea14a
commit
5f5152718f
9 changed files with 159 additions and 0 deletions
0
src/c3nav/api/__init__.py
Normal file
0
src/c3nav/api/__init__.py
Normal file
28
src/c3nav/api/permissions.py
Normal file
28
src/c3nav/api/permissions.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
from rest_framework.permissions import BasePermission
|
||||||
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
from django.conf import settings
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from ..mapdata.models import Source
|
||||||
|
|
||||||
|
|
||||||
|
def get_unlocked_packages(request):
|
||||||
|
return set(settings.PUBLIC_PACKAGES) | set(request.session.get('unlocked_packages', ()))
|
||||||
|
|
||||||
|
|
||||||
|
def can_access_package(request, package):
|
||||||
|
print(package.name == 'de.c3nav.33c3.base')
|
||||||
|
return package.name == 'de.c3nav.33c3.base'
|
||||||
|
return settings.DEBUG or package.name in get_unlocked_packages(request)
|
||||||
|
|
||||||
|
|
||||||
|
def filter_source_queryset(request, queryset):
|
||||||
|
return queryset if settings.DEBUG else queryset.filter(package__name__in=get_unlocked_packages(request))
|
||||||
|
|
||||||
|
|
||||||
|
class LockedMapFeatures(BasePermission):
|
||||||
|
def has_object_permission(self, request, view, obj):
|
||||||
|
if isinstance(obj, Source):
|
||||||
|
if not can_access_package(request, obj.package):
|
||||||
|
raise PermissionDenied(_('This Source belongs to a package you don\'t have access to.'))
|
||||||
|
return True
|
29
src/c3nav/api/serializers.py
Normal file
29
src/c3nav/api/serializers.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
from rest_framework.serializers import ModelSerializer
|
||||||
|
|
||||||
|
from ..mapdata.models import Level, Package, Source
|
||||||
|
|
||||||
|
|
||||||
|
class BoundsMixin:
|
||||||
|
def to_representation(self, obj):
|
||||||
|
result = super().to_representation(obj)
|
||||||
|
if obj.bottom is not None:
|
||||||
|
result['bounds'] = ((obj.bottom, obj.left), (obj.top, obj.right))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class LevelSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Level
|
||||||
|
fields = ('name', 'altitude', 'package')
|
||||||
|
|
||||||
|
|
||||||
|
class PackageSerializer(BoundsMixin, ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Package
|
||||||
|
fields = ('name', 'depends')
|
||||||
|
|
||||||
|
|
||||||
|
class SourceSerializer(BoundsMixin, ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Source
|
||||||
|
fields = ('name', 'package')
|
15
src/c3nav/api/urls.py
Normal file
15
src/c3nav/api/urls.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
from django.conf.urls import include, url
|
||||||
|
|
||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
|
from .views import map as map_views
|
||||||
|
|
||||||
|
router = DefaultRouter()
|
||||||
|
router.register(r'map/levels', map_views.LevelViewSet)
|
||||||
|
router.register(r'map/packages', map_views.PackageViewSet)
|
||||||
|
router.register(r'map/sources', map_views.SourceViewSet)
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^(?P<version>v\d+)/', include(router.urls, namespace='v1')),
|
||||||
|
]
|
0
src/c3nav/api/views/__init__.py
Normal file
0
src/c3nav/api/views/__init__.py
Normal file
65
src/c3nav/api/views/map.py
Normal file
65
src/c3nav/api/views/map.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import mimetypes
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.files import File
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||||
|
from rest_framework.decorators import detail_route
|
||||||
|
|
||||||
|
from ...mapdata.models import Level, Package, Source
|
||||||
|
from ..serializers import LevelSerializer, PackageSerializer, SourceSerializer
|
||||||
|
from ..permissions import filter_source_queryset
|
||||||
|
|
||||||
|
|
||||||
|
class LevelViewSet(ReadOnlyModelViewSet):
|
||||||
|
"""
|
||||||
|
Returns a list of all levels on the map.
|
||||||
|
"""
|
||||||
|
queryset = Level.objects.all()
|
||||||
|
serializer_class = LevelSerializer
|
||||||
|
lookup_value_regex = '[^/]+'
|
||||||
|
filter_fields = ('altitude', 'package')
|
||||||
|
ordering_fields = ('altitude', 'package')
|
||||||
|
ordering = ('altitude',)
|
||||||
|
search_fields = ('name',)
|
||||||
|
|
||||||
|
|
||||||
|
class PackageViewSet(ReadOnlyModelViewSet):
|
||||||
|
"""
|
||||||
|
Returns a list of all packages the map consists of.
|
||||||
|
"""
|
||||||
|
queryset = Package.objects.all()
|
||||||
|
serializer_class = PackageSerializer
|
||||||
|
lookup_value_regex = '[^/]+'
|
||||||
|
filter_fields = ('name', 'depends')
|
||||||
|
ordering_fields = ('name',)
|
||||||
|
ordering = ('name',)
|
||||||
|
search_fields = ('name',)
|
||||||
|
|
||||||
|
|
||||||
|
class SourceViewSet(ReadOnlyModelViewSet):
|
||||||
|
"""
|
||||||
|
Returns a list of source images (to use as a drafts).
|
||||||
|
Call /sources/{name}/image to get the image.
|
||||||
|
"""
|
||||||
|
queryset = Source.objects.all()
|
||||||
|
serializer_class = SourceSerializer
|
||||||
|
lookup_value_regex = '[^/]+'
|
||||||
|
filter_fields = ('package',)
|
||||||
|
ordering_fields = ('name', 'package')
|
||||||
|
ordering = ('name',)
|
||||||
|
search_fields = ('name',)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return filter_source_queryset(self.request, super().get_queryset())
|
||||||
|
|
||||||
|
@detail_route(methods=['get'])
|
||||||
|
def image(self, request, pk=None, version=None):
|
||||||
|
source = self.get_object()
|
||||||
|
response = HttpResponse(content_type=mimetypes.guess_type(source.name)[0])
|
||||||
|
image_path = os.path.join(settings.MAP_ROOT, source.package.directory, 'sources', source.name)
|
||||||
|
for chunk in File(open(image_path, 'rb')).chunks():
|
||||||
|
response.write(chunk)
|
||||||
|
return response
|
|
@ -46,6 +46,7 @@ else:
|
||||||
|
|
||||||
debug_fallback = "runserver" in sys.argv
|
debug_fallback = "runserver" in sys.argv
|
||||||
DEBUG = config.getboolean('django', 'debug', fallback=debug_fallback)
|
DEBUG = config.getboolean('django', 'debug', fallback=debug_fallback)
|
||||||
|
PUBLIC_PACKAGES = [n for n in config.get('c3nav', 'public_packages', fallback='').split(',') if n]
|
||||||
|
|
||||||
db_backend = config.get('database', 'backend', fallback='sqlite3')
|
db_backend = config.get('database', 'backend', fallback='sqlite3')
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
|
@ -108,6 +109,7 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'compressor',
|
'compressor',
|
||||||
'bootstrap3',
|
'bootstrap3',
|
||||||
|
'rest_framework',
|
||||||
'c3nav.mapdata',
|
'c3nav.mapdata',
|
||||||
'c3nav.editor',
|
'c3nav.editor',
|
||||||
'c3nav.control',
|
'c3nav.control',
|
||||||
|
@ -136,6 +138,22 @@ USE_I18N = True
|
||||||
USE_L10N = True
|
USE_L10N = True
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
|
||||||
|
'ALLOWED_VERSIONS': ['v1'],
|
||||||
|
'DEFAULT_VERSION': 'v1',
|
||||||
|
'DEFAULT_PERMISSION_CLASSES': (
|
||||||
|
'c3nav.api.permissions.LockedMapFeatures',
|
||||||
|
),
|
||||||
|
'DEFAULT_FILTER_BACKENDS': (
|
||||||
|
'rest_framework.filters.DjangoFilterBackend',
|
||||||
|
'rest_framework.filters.OrderingFilter',
|
||||||
|
'rest_framework.filters.SearchFilter',
|
||||||
|
),
|
||||||
|
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
|
||||||
|
'PAGE_SIZE': 50
|
||||||
|
}
|
||||||
|
|
||||||
LOCALE_PATHS = (
|
LOCALE_PATHS = (
|
||||||
os.path.join(os.path.dirname(__file__), 'locale'),
|
os.path.join(os.path.dirname(__file__), 'locale'),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .api import urls as api_urls
|
||||||
from .control import urls as control_urls
|
from .control import urls as control_urls
|
||||||
from .editor import urls as editor_urls
|
from .editor import urls as editor_urls
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^control/', include(control_urls)),
|
url(r'^control/', include(control_urls)),
|
||||||
url(r'^editor/', include(editor_urls)),
|
url(r'^editor/', include(editor_urls)),
|
||||||
|
url(r'^api/', include(api_urls)),
|
||||||
url(r'^admin/', admin.site.urls),
|
url(r'^admin/', admin.site.urls),
|
||||||
]
|
]
|
||||||
|
|
|
@ -2,3 +2,5 @@ Django>=1.9,<1.10
|
||||||
django-bootstrap3>=6.2,<6.3
|
django-bootstrap3>=6.2,<6.3
|
||||||
django-compressor==2.0
|
django-compressor==2.0
|
||||||
csscompressor
|
csscompressor
|
||||||
|
djangorestframework==3.4.*
|
||||||
|
django-filter==0.14.*
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue