remove recursive serialization in api and replace url root with list of all endpoints
This commit is contained in:
parent
9658de72a2
commit
4b97e3532f
6 changed files with 66 additions and 131 deletions
|
@ -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
|
|
@ -24,11 +24,3 @@
|
|||
{% block branding %}
|
||||
<span class="navbar-brand">c3nav API</span>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block description %}
|
||||
{% if breadcrumblist|length == 1 %}
|
||||
<p>Welcome to the c3nav RESTful API.</p>
|
||||
<p><small><em>Add <code>?sparse=1</code> to any request to flatten its result.</em></small></p>
|
||||
{% else %}{{ description }}{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -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)),
|
||||
]
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue