From 02cafee6be18b5011d3ae5a050adc008c3094049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Thu, 22 Sep 2016 12:58:21 +0200 Subject: [PATCH] refactor api views inter their respecting apps --- src/c3nav/api/fields.py | 26 -------- src/c3nav/api/serializers.py | 63 ------------------- src/c3nav/api/urls.py | 16 ++--- src/c3nav/api/views/__init__.py | 0 .../{api/views/editor.py => editor/api.py} | 16 +---- src/c3nav/editor/serializers.py | 14 +++++ .../{api/views/mapdata.py => mapdata/api.py} | 20 ++++-- src/c3nav/{api/views => mapdata}/cache.py | 2 +- src/c3nav/mapdata/models/package.py | 5 ++ src/c3nav/{api => mapdata}/permissions.py | 2 +- src/c3nav/mapdata/serializers.py | 62 ++++++++++++++++++ src/c3nav/settings.py | 3 +- 12 files changed, 112 insertions(+), 117 deletions(-) delete mode 100644 src/c3nav/api/fields.py delete mode 100644 src/c3nav/api/serializers.py delete mode 100644 src/c3nav/api/views/__init__.py rename src/c3nav/{api/views/editor.py => editor/api.py} (56%) create mode 100644 src/c3nav/editor/serializers.py rename src/c3nav/{api/views/mapdata.py => mapdata/api.py} (79%) rename src/c3nav/{api/views => mapdata}/cache.py (96%) rename src/c3nav/{api => mapdata}/permissions.py (96%) create mode 100644 src/c3nav/mapdata/serializers.py diff --git a/src/c3nav/api/fields.py b/src/c3nav/api/fields.py deleted file mode 100644 index f7dd7317..00000000 --- a/src/c3nav/api/fields.py +++ /dev/null @@ -1,26 +0,0 @@ -from django.utils.translation import ugettext_lazy as _ -from rest_framework import serializers -from rest_framework.exceptions import ValidationError -from shapely.geometry import mapping, shape - -from ..mapdata.utils import sort_geojson - - -class GeometryField(serializers.DictField): - """ - shapely geometry objects serialized using GeoJSON - """ - default_error_messages = { - 'invalid': _('Invalid GeoJSON.') - } - - def to_representation(self, obj): - geojson = sort_geojson(mapping(obj)) - return super().to_representation(geojson) - - def to_internal_value(self, data): - geojson = super().to_internal_value(data) - try: - return shape(geojson) - except: - raise ValidationError(_('Invalid GeoJSON.')) diff --git a/src/c3nav/api/serializers.py b/src/c3nav/api/serializers.py deleted file mode 100644 index 79631b40..00000000 --- a/src/c3nav/api/serializers.py +++ /dev/null @@ -1,63 +0,0 @@ -from django.conf import settings -from rest_framework import serializers - -from ..editor.hosters import get_hoster_for_package -from ..mapdata.models import Feature, Level, Package, Source -from .fields import GeometryField -from .permissions import can_access_package - - -class LevelSerializer(serializers.ModelSerializer): - class Meta: - model = Level - fields = ('name', 'altitude', 'package') - - -class PackageSerializer(serializers.ModelSerializer): - class Meta: - model = Package - fields = ('name', 'home_repo', 'commit_id', 'depends', 'bounds') - - def to_representation(self, obj): - result = super().to_representation(obj) - result['public'] = obj.name in settings.PUBLIC_PACKAGES - hoster = get_hoster_for_package(obj) - if 'request' in self.context: - result['access_granted'] = can_access_package(self.context['request'], obj) - if hoster is not None: - result['hoster'] = hoster.name - return result - - -class SourceSerializer(serializers.ModelSerializer): - class Meta: - model = Source - fields = ('name', 'package', 'bounds') - - -class FeatureTypeSerializer(serializers.Serializer): - name = serializers.CharField() - title = serializers.CharField() - title_plural = serializers.CharField() - geomtype = serializers.CharField() - color = serializers.CharField() - - -class FeatureSerializer(serializers.ModelSerializer): - geometry = GeometryField() - - class Meta: - model = Feature - fields = ('name', 'package', 'feature_type', 'geometry') - - -class HosterSerializer(serializers.Serializer): - name = serializers.CharField() - base_url = serializers.CharField() - - def to_representation(self, obj): - result = super().to_representation(obj) - result['packages'] = tuple(obj.get_packages().values_list('name', flat=True)) - if 'request' in self.context: - result['signed_in'] = obj.is_access_granted(self.context['request']) - return result diff --git a/src/c3nav/api/urls.py b/src/c3nav/api/urls.py index 9c196358..c0d6753f 100644 --- a/src/c3nav/api/urls.py +++ b/src/c3nav/api/urls.py @@ -1,16 +1,16 @@ from django.conf.urls import include, url from rest_framework.routers import DefaultRouter -from .views import editor as editor_views -from .views import mapdata as mapdata_views +from ..editor import api as editor_api +from ..mapdata import api as mapdata_api router = DefaultRouter() -router.register(r'levels', mapdata_views.LevelViewSet) -router.register(r'packages', mapdata_views.PackageViewSet) -router.register(r'sources', mapdata_views.SourceViewSet) -router.register(r'featuretypes', mapdata_views.FeatureTypeViewSet, base_name='featuretype') -router.register(r'features', editor_views.FeatureViewSet) -router.register(r'hosters', editor_views.HosterViewSet, base_name='hoster') +router.register(r'levels', mapdata_api.LevelViewSet) +router.register(r'packages', mapdata_api.PackageViewSet) +router.register(r'sources', mapdata_api.SourceViewSet) +router.register(r'featuretypes', mapdata_api.FeatureTypeViewSet, base_name='featuretype') +router.register(r'features', mapdata_api.FeatureViewSet) +router.register(r'hosters', editor_api.HosterViewSet, base_name='hoster') urlpatterns = [ diff --git a/src/c3nav/api/views/__init__.py b/src/c3nav/api/views/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/c3nav/api/views/editor.py b/src/c3nav/editor/api.py similarity index 56% rename from src/c3nav/api/views/editor.py rename to src/c3nav/editor/api.py index c58f485f..8df60c7e 100644 --- a/src/c3nav/api/views/editor.py +++ b/src/c3nav/editor/api.py @@ -1,10 +1,9 @@ from django.http import Http404 from rest_framework.response import Response -from rest_framework.viewsets import ModelViewSet, ViewSet +from rest_framework.viewsets import ViewSet -from ...editor.hosters import hosters -from ...mapdata.models import Feature -from ..serializers import FeatureSerializer, HosterSerializer +from .hosters import hosters +from .serializers import HosterSerializer class HosterViewSet(ViewSet): @@ -20,12 +19,3 @@ class HosterViewSet(ViewSet): raise Http404 serializer = HosterSerializer(hosters[pk], context={'request': request}) return Response(serializer.data) - - -class FeatureViewSet(ModelViewSet): - """ - Get all Map Features including ones that are only part of the current session - """ - queryset = Feature.objects.all() - serializer_class = FeatureSerializer - lookup_value_regex = '[^/]+' diff --git a/src/c3nav/editor/serializers.py b/src/c3nav/editor/serializers.py new file mode 100644 index 00000000..b84df6df --- /dev/null +++ b/src/c3nav/editor/serializers.py @@ -0,0 +1,14 @@ +from rest_framework import serializers + + +class HosterSerializer(serializers.Serializer): + name = serializers.CharField() + base_url = serializers.CharField() + packages = serializers.SerializerMethodField() + signed_in = serializers.SerializerMethodField() + + def get_packages(self, obj): + return tuple(obj.get_packages().values_list('name', flat=True)) + + def get_signed_in(self, obj): + return obj.is_access_granted(self.context['request']) if 'request' in self.context else None diff --git a/src/c3nav/api/views/mapdata.py b/src/c3nav/mapdata/api.py similarity index 79% rename from src/c3nav/api/views/mapdata.py rename to src/c3nav/mapdata/api.py index 891b7438..cedd4089 100644 --- a/src/c3nav/api/views/mapdata.py +++ b/src/c3nav/mapdata/api.py @@ -6,12 +6,12 @@ from django.core.files import File from django.http import Http404, HttpResponse from rest_framework.decorators import detail_route from rest_framework.response import Response -from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet +from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet, ViewSet -from ...mapdata.models import FEATURE_TYPES, Level, Package, Source -from ..permissions import filter_source_queryset -from ..serializers import FeatureTypeSerializer, LevelSerializer, PackageSerializer, SourceSerializer from .cache import AccessCachedViewSetMixin, CachedViewSetMixin +from .models import FEATURE_TYPES, Feature, Level, Package, Source +from .permissions import filter_source_queryset +from .serializers import FeatureSerializer, FeatureTypeSerializer, LevelSerializer, PackageSerializer, SourceSerializer class LevelViewSet(CachedViewSetMixin, ReadOnlyModelViewSet): @@ -79,3 +79,15 @@ class FeatureTypeViewSet(ViewSet): raise Http404 serializer = FeatureTypeSerializer(FEATURE_TYPES[pk], context={'request': request}) return Response(serializer.data) + + +ParentModelViewSet = ModelViewSet if settings.DIRECT_EDITING else ReadOnlyModelViewSet + + +class FeatureViewSet(ParentModelViewSet): + """ + Get all Map Features including ones that are only part of the current session + """ + queryset = Feature.objects.all() + serializer_class = FeatureSerializer + lookup_value_regex = '[^/]+' diff --git a/src/c3nav/api/views/cache.py b/src/c3nav/mapdata/cache.py similarity index 96% rename from src/c3nav/api/views/cache.py rename to src/c3nav/mapdata/cache.py index c6a52003..ec3aad59 100644 --- a/src/c3nav/api/views/cache.py +++ b/src/c3nav/mapdata/cache.py @@ -4,7 +4,7 @@ from django.core.cache import cache from django.template.response import SimpleTemplateResponse from django.utils.cache import patch_vary_headers -from ..permissions import get_unlocked_packages +from .permissions import get_unlocked_packages class CachedViewSetMixin: diff --git a/src/c3nav/mapdata/models/package.py b/src/c3nav/mapdata/models/package.py index 08e320e5..ae0f3f9b 100644 --- a/src/c3nav/mapdata/models/package.py +++ b/src/c3nav/mapdata/models/package.py @@ -1,5 +1,6 @@ from collections import OrderedDict +from django.conf import settings from django.db import models from django.utils.translation import ugettext_lazy as _ @@ -55,6 +56,10 @@ class Package(models.Model): def package(self): return self + @property + def public(self): + return self.name in settings.PUBLIC_PACKAGES + @property def bounds(self): if self.bottom is None: diff --git a/src/c3nav/api/permissions.py b/src/c3nav/mapdata/permissions.py similarity index 96% rename from src/c3nav/api/permissions.py rename to src/c3nav/mapdata/permissions.py index 434a2497..2fa1c6c0 100644 --- a/src/c3nav/api/permissions.py +++ b/src/c3nav/mapdata/permissions.py @@ -3,7 +3,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework.exceptions import PermissionDenied from rest_framework.permissions import BasePermission -from ..mapdata.models import Source +from .models import Source def get_unlocked_packages(request): diff --git a/src/c3nav/mapdata/serializers.py b/src/c3nav/mapdata/serializers.py new file mode 100644 index 00000000..f152a94f --- /dev/null +++ b/src/c3nav/mapdata/serializers.py @@ -0,0 +1,62 @@ +from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers +from rest_framework.exceptions import ValidationError +from shapely.geometry import mapping, shape + +from .models import Feature, Level, Package, Source +from .utils import sort_geojson + + +class GeometryField(serializers.DictField): + """ + shapely geometry objects serialized using GeoJSON + """ + default_error_messages = { + 'invalid': _('Invalid GeoJSON.') + } + + def to_representation(self, obj): + geojson = sort_geojson(mapping(obj)) + return super().to_representation(geojson) + + def to_internal_value(self, data): + geojson = super().to_internal_value(data) + try: + return shape(geojson) + except: + raise ValidationError(_('Invalid GeoJSON.')) + + +class LevelSerializer(serializers.ModelSerializer): + class Meta: + model = Level + fields = ('name', 'altitude', 'package') + + +class PackageSerializer(serializers.ModelSerializer): + class Meta: + model = Package + fields = ('name', 'home_repo', 'commit_id', 'depends', 'bounds', 'public') + readonly_fields = ('commit_id', ) + + +class SourceSerializer(serializers.ModelSerializer): + class Meta: + model = Source + fields = ('name', 'package', 'bounds') + + +class FeatureTypeSerializer(serializers.Serializer): + name = serializers.CharField() + title = serializers.CharField() + title_plural = serializers.CharField() + geomtype = serializers.CharField() + color = serializers.CharField() + + +class FeatureSerializer(serializers.ModelSerializer): + geometry = GeometryField() + + class Meta: + model = Feature + fields = ('name', 'package', 'feature_type', 'geometry') diff --git a/src/c3nav/settings.py b/src/c3nav/settings.py index 39f5db0f..583dad9d 100644 --- a/src/c3nav/settings.py +++ b/src/c3nav/settings.py @@ -47,6 +47,7 @@ else: debug_fallback = "runserver" in sys.argv DEBUG = config.getboolean('django', 'debug', fallback=debug_fallback) +DIRECT_EDITING = config.getboolean('c3nav', 'enable_editor', fallback=DEBUG) PUBLIC_PACKAGES = [n for n in config.get('c3nav', 'public_packages', fallback='').split(',') if n] EDITOR_HOSTERS = OrderedDict((name[7:], data) for name, data in config.items() if name.startswith('hoster:')) @@ -168,7 +169,7 @@ REST_FRAMEWORK = { 'ALLOWED_VERSIONS': ['v1'], 'DEFAULT_VERSION': 'v1', 'DEFAULT_PERMISSION_CLASSES': ( - 'c3nav.api.permissions.LockedMapFeatures', + 'c3nav.mapdata.permissions.LockedMapFeatures', ), 'DEFAULT_FILTER_BACKENDS': ( 'rest_framework.filters.DjangoFilterBackend',