diff --git a/src/c3nav/api/serializers.py b/src/c3nav/api/serializers.py
deleted file mode 100644
index 54640ad2..00000000
--- a/src/c3nav/api/serializers.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from collections import Iterable
-
-from django.db.models.manager import BaseManager
-from rest_framework import serializers
-
-
-class RelatedNameField(serializers.DictField):
- """
- give primary key
- """
- def to_representation(self, obj):
- if hasattr(obj, 'name'):
- return obj.name
- elif isinstance(obj, Iterable):
- return tuple(self.to_representation(elem) for elem in obj)
- elif isinstance(obj, BaseManager):
- return tuple(self.to_representation(elem) for elem in obj.all())
- return None
-
-
-class RecursiveSerializerMixin(serializers.Serializer):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- request = self.context.get('request')
- request_sparse = self.context['request_sparse'] = request is not None and request.GET.get('sparse')
- sparse = self.context['sparse'] = request_sparse or self.context.get('sparse')
-
- if sparse:
- for name in getattr(self.Meta, 'sparse_exclude', ()):
- value = self.fields.get(name)
- if value is not None and isinstance(value, serializers.Serializer):
- self.fields[name] = RelatedNameField()
-
- if request_sparse:
- for name in tuple(self.fields):
- if name == 'url' or name.endswith('_url'):
- self.fields.pop(name)
-
- def sparse_context(self):
- return {'request': self.context.get('request'), 'sparse': True}
-
- def recursive_value(self, serializer, obj, *args, **kwargs):
- if self.context.get('sparse'):
- return RelatedNameField().to_representation(obj)
- return serializer(obj, context=self.sparse_context(), *args, **kwargs).data
diff --git a/src/c3nav/api/templates/rest_framework/api.html b/src/c3nav/api/templates/rest_framework/api.html
index cc6a6094..cef19449 100644
--- a/src/c3nav/api/templates/rest_framework/api.html
+++ b/src/c3nav/api/templates/rest_framework/api.html
@@ -24,11 +24,3 @@
{% block branding %}
c3nav API
{% endblock %}
-
-
-{% block description %}
-{% if breadcrumblist|length == 1 %}
-
Welcome to the c3nav RESTful API.
-Add ?sparse=1
to any request to flatten its result.
-{% else %}{{ description }}{% endif %}
-{% endblock %}
diff --git a/src/c3nav/api/urls.py b/src/c3nav/api/urls.py
index a52f345d..51f26eb7 100644
--- a/src/c3nav/api/urls.py
+++ b/src/c3nav/api/urls.py
@@ -1,9 +1,16 @@
-from rest_framework.routers import DefaultRouter
+import re
+from collections import OrderedDict
+
+from compressor.utils.decorators import cached_property
+from django.conf.urls import include, url
+from rest_framework.generics import GenericAPIView
+from rest_framework.response import Response
+from rest_framework.routers import SimpleRouter
from c3nav.editor.api import HosterViewSet, SubmitTaskViewSet
from c3nav.mapdata.api import FeatureTypeViewSet, FeatureViewSet, LevelViewSet, PackageViewSet, SourceViewSet
-router = DefaultRouter()
+router = SimpleRouter()
router.register(r'levels', LevelViewSet)
router.register(r'packages', PackageViewSet)
router.register(r'sources', SourceViewSet)
@@ -12,4 +19,33 @@ router.register(r'features', FeatureViewSet)
router.register(r'hosters', HosterViewSet, base_name='hoster')
router.register(r'submittasks', SubmitTaskViewSet, base_name='submittask')
-urlpatterns = router.urls
+
+class APIRoot(GenericAPIView):
+ """
+ Welcome to the c3nav RESTful API.
+ """
+
+ def _format_pattern(self, pattern):
+ return re.sub(r'\(\?P<([^>]*[^>_])_?>[^)]+\)', r'{\1}', pattern)[1:-1]
+
+ @cached_property
+ def urls(self):
+ urls = OrderedDict()
+ for urlpattern in router.urls:
+ name = urlpattern.name
+ url = self._format_pattern(urlpattern.regex.pattern)
+ base = url.split('/', 1)[0]
+ if '-' in name:
+ urls.setdefault(base, OrderedDict())[name.split('-', 1)[1]] = url
+ else:
+ urls[name] = url
+ return urls
+
+ def get(self, request):
+ return Response(self.urls)
+
+
+urlpatterns = [
+ url(r'^$', APIRoot.as_view()),
+ url(r'', include(router.urls)),
+]
diff --git a/src/c3nav/editor/api.py b/src/c3nav/editor/api.py
index eeecc543..51597d83 100644
--- a/src/c3nav/editor/api.py
+++ b/src/c3nav/editor/api.py
@@ -20,18 +20,18 @@ class HosterViewSet(ViewSet):
"""
lookup_field = 'name'
- def retrieve(self, request, pk=None):
- if pk not in hosters:
+ def retrieve(self, request, name=None):
+ if name not in hosters:
raise Http404
- serializer = HosterSerializer(hosters[pk], context={'request': request})
+ serializer = HosterSerializer(hosters[name], context={'request': request})
return Response(serializer.data)
@detail_route(methods=['get'])
- def state(self, request, pk=None):
- if pk not in hosters:
+ def state(self, request, name=None):
+ if name not in hosters:
raise Http404
- hoster = hosters[pk]
+ hoster = hosters[name]
state = hoster.get_state(request)
error = hoster.get_error(request) if state == 'logged_out' else None
@@ -41,18 +41,18 @@ class HosterViewSet(ViewSet):
)))
@detail_route(methods=['post'])
- def auth_uri(self, request, pk=None):
- if pk not in hosters:
+ def auth_uri(self, request, name=None):
+ if name not in hosters:
raise Http404
return Response({
- 'auth_uri': hosters[pk].get_auth_uri(request)
+ 'auth_uri': hosters[name].get_auth_uri(request)
})
@detail_route(methods=['post'])
- def submit(self, request, pk=None):
- if pk not in hosters:
+ def submit(self, request, name=None):
+ if name not in hosters:
raise Http404
- hoster = hosters[pk]
+ hoster = hosters[name]
if 'data' not in request.POST:
raise ValidationError('Missing POST parameter: data')
@@ -94,10 +94,10 @@ class SubmitTaskViewSet(ViewSet):
"""
Get hoster submit tasks
"""
- lookup_field = 'id'
+ lookup_field = 'id_'
- def retrieve(self, request, pk=None):
- task = submit_edit_task.AsyncResult(task_id=pk)
+ def retrieve(self, request, id_=None):
+ task = submit_edit_task.AsyncResult(task_id=id_)
try:
task.ready()
except:
diff --git a/src/c3nav/editor/serializers.py b/src/c3nav/editor/serializers.py
index b9ed3241..cea1b4c9 100644
--- a/src/c3nav/editor/serializers.py
+++ b/src/c3nav/editor/serializers.py
@@ -1,28 +1,13 @@
from rest_framework import serializers
-from rest_framework.reverse import reverse
class HosterSerializer(serializers.Serializer):
name = serializers.CharField()
- url = serializers.HyperlinkedIdentityField(view_name='api:hoster-detail', lookup_field='name')
- state_url = serializers.SerializerMethodField()
- auth_uri_url = serializers.SerializerMethodField()
- submit_url = serializers.SerializerMethodField()
base_url = serializers.CharField()
- def get_state_url(self, obj):
- return reverse('api:hoster-state', args=(obj.name, ), request=self.context.get('request'))
-
- def get_auth_uri_url(self, obj):
- return reverse('api:hoster-auth-uri', args=(obj.name, ), request=self.context.get('request'))
-
- def get_submit_url(self, obj):
- return reverse('api:hoster-submit', args=(obj.name, ), request=self.context.get('request'))
-
class TaskSerializer(serializers.Serializer):
id = serializers.CharField()
- url = serializers.HyperlinkedIdentityField(view_name='api:hoster-detail', lookup_field='id')
started = serializers.SerializerMethodField()
done = serializers.SerializerMethodField()
success = serializers.SerializerMethodField()
diff --git a/src/c3nav/mapdata/serializers.py b/src/c3nav/mapdata/serializers.py
index 90613f25..507c797a 100644
--- a/src/c3nav/mapdata/serializers.py
+++ b/src/c3nav/mapdata/serializers.py
@@ -1,13 +1,10 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
-from rest_framework.reverse import reverse
from shapely.geometry import mapping, shape
-from c3nav.api.serializers import RecursiveSerializerMixin
from c3nav.editor.hosters import get_hoster_for_package
from c3nav.mapdata.models import Feature, Level, Package, Source
-from c3nav.mapdata.models.feature import FEATURE_TYPES
from c3nav.mapdata.utils import format_geojson
@@ -31,78 +28,49 @@ class GeometryField(serializers.DictField):
raise ValidationError(_('Invalid GeoJSON.'))
-class PackageSerializer(RecursiveSerializerMixin, serializers.ModelSerializer):
+class PackageSerializer(serializers.ModelSerializer):
hoster = serializers.SerializerMethodField()
- depends = serializers.SerializerMethodField()
+ depends = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True)
class Meta:
model = Package
- fields = ('name', 'url', 'home_repo', 'commit_id', 'depends', 'bounds', 'public', 'hoster')
- sparse_exclude = ('depends', 'hoster')
- extra_kwargs = {
- 'url': {'view_name': 'api:package-detail', 'lookup_field': 'name'}
- }
+ fields = ('name', 'home_repo', 'commit_id', 'depends', 'bounds', 'public', 'hoster')
def get_depends(self, obj):
return self.recursive_value(PackageSerializer, obj.depends, many=True)
def get_hoster(self, obj):
- from c3nav.editor.serializers import HosterSerializer
- return self.recursive_value(HosterSerializer, get_hoster_for_package(obj))
+ return get_hoster_for_package(obj).name
-class LevelSerializer(RecursiveSerializerMixin, serializers.ModelSerializer):
- package = PackageSerializer(context={'sparse': True})
+class LevelSerializer(serializers.ModelSerializer):
+ package = serializers.SlugRelatedField(slug_field='name', read_only=True)
class Meta:
model = Level
- fields = ('name', 'url', 'altitude', 'package')
- sparse_exclude = ('package',)
- extra_kwargs = {
- 'url': {'view_name': 'api:level-detail', 'lookup_field': 'name'}
- }
+ fields = ('name', 'altitude', 'package')
-class SourceSerializer(RecursiveSerializerMixin, serializers.ModelSerializer):
- image_url = serializers.SerializerMethodField()
- package = PackageSerializer(context={'sparse': True})
+class SourceSerializer(serializers.ModelSerializer):
+ package = serializers.SlugRelatedField(slug_field='name', read_only=True)
class Meta:
model = Source
- fields = ('name', 'url', 'image_url', 'package', 'bounds')
- sparse_exclude = ('package', )
- extra_kwargs = {
- 'url': {'view_name': 'api:source-detail', 'lookup_field': 'name'}
- }
-
- def get_image_url(self, obj):
- return reverse('api:source-image', args=(obj.name, ), request=self.context.get('request'))
+ fields = ('name', 'package', 'bounds')
class FeatureTypeSerializer(serializers.Serializer):
name = serializers.CharField()
- url = serializers.HyperlinkedIdentityField(view_name='api:featuretype-detail')
title = serializers.CharField()
title_plural = serializers.CharField()
geomtype = serializers.CharField()
color = serializers.CharField()
-class FeatureSerializer(RecursiveSerializerMixin, serializers.ModelSerializer):
+class FeatureSerializer(serializers.ModelSerializer):
titles = serializers.JSONField()
- feature_type = serializers.SerializerMethodField()
- level = LevelSerializer()
- package = PackageSerializer()
geometry = GeometryField()
class Meta:
model = Feature
- fields = ('name', 'url', 'title', 'feature_type', 'level', 'titles', 'package', 'geometry')
- sparse_exclude = ('feature_type', 'level', 'package')
- extra_kwargs = {
- 'lookup_field': 'name',
- 'url': {'view_name': 'api:feature-detail', 'lookup_field': 'name'}
- }
-
- def get_feature_type(self, obj):
- return self.recursive_value(FeatureTypeSerializer, FEATURE_TYPES.get(obj.feature_type))
+ fields = ('name', 'title', 'feature_type', 'level', 'titles', 'package', 'geometry')