api endpoint to get the current dynamic position

This commit is contained in:
Laura Klünder 2019-12-27 20:02:58 +01:00
parent c6aab7fb36
commit 7e56927a72
4 changed files with 118 additions and 21 deletions

View file

@ -11,11 +11,11 @@ from rest_framework.routers import SimpleRouter
from c3nav.api.api import SessionViewSet from c3nav.api.api import SessionViewSet
from c3nav.editor.api import ChangeSetViewSet, EditorViewSet from c3nav.editor.api import ChangeSetViewSet, EditorViewSet
from c3nav.mapdata.api import (AccessRestrictionGroupViewSet, AccessRestrictionViewSet, AreaViewSet, BuildingViewSet, from c3nav.mapdata.api import (AccessRestrictionGroupViewSet, AccessRestrictionViewSet, AreaViewSet, BuildingViewSet,
ColumnViewSet, CrossDescriptionViewSet, DoorViewSet, HoleViewSet, ColumnViewSet, CrossDescriptionViewSet, DoorViewSet, DynamicLocationPositionViewSet,
LeaveDescriptionViewSet, LevelViewSet, LineObstacleViewSet, LocationBySlugViewSet, HoleViewSet, LeaveDescriptionViewSet, LevelViewSet, LineObstacleViewSet,
LocationGroupCategoryViewSet, LocationGroupViewSet, LocationViewSet, MapViewSet, LocationBySlugViewSet, LocationGroupCategoryViewSet, LocationGroupViewSet,
ObstacleViewSet, POIViewSet, RampViewSet, SourceViewSet, SpaceViewSet, StairViewSet, LocationViewSet, MapViewSet, ObstacleViewSet, POIViewSet, RampViewSet, SourceViewSet,
UpdatesViewSet) SpaceViewSet, StairViewSet, UpdatesViewSet)
from c3nav.mapdata.utils.user import can_access_editor from c3nav.mapdata.utils.user import can_access_editor
from c3nav.routing.api import RoutingViewSet from c3nav.routing.api import RoutingViewSet
@ -41,6 +41,7 @@ router.register(r'accessrestrictiongroups', AccessRestrictionGroupViewSet)
router.register(r'locations', LocationViewSet) router.register(r'locations', LocationViewSet)
router.register(r'locations/by_slug', LocationBySlugViewSet, base_name='location-by-slug') router.register(r'locations/by_slug', LocationBySlugViewSet, base_name='location-by-slug')
router.register(r'locations/dynamic', DynamicLocationPositionViewSet, base_name='dynamic-location')
router.register(r'locationgroupcategories', LocationGroupCategoryViewSet) router.register(r'locationgroupcategories', LocationGroupCategoryViewSet)
router.register(r'locationgroups', LocationGroupViewSet) router.register(r'locationgroups', LocationGroupViewSet)

View file

@ -6,7 +6,7 @@ from urllib.parse import urlparse
from django.core.cache import cache from django.core.cache import cache
from django.db.models import Prefetch from django.db.models import Prefetch
from django.http import HttpResponse from django.http import Http404, HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.cache import get_conditional_response from django.utils.cache import get_conditional_response
from django.utils.http import http_date, quote_etag, urlsafe_base64_encode from django.utils.http import http_date, quote_etag, urlsafe_base64_encode
@ -14,6 +14,7 @@ from django.utils.translation import get_language
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.exceptions import NotFound, ValidationError from rest_framework.exceptions import NotFound, ValidationError
from rest_framework.generics import get_object_or_404
from rest_framework.mixins import RetrieveModelMixin from rest_framework.mixins import RetrieveModelMixin
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet, ReadOnlyModelViewSet, ViewSet from rest_framework.viewsets import GenericViewSet, ReadOnlyModelViewSet, ViewSet
@ -25,8 +26,8 @@ from c3nav.mapdata.models.geometry.level import LevelGeometryMixin
from c3nav.mapdata.models.geometry.space import (POI, Area, Column, CrossDescription, LeaveDescription, LineObstacle, from c3nav.mapdata.models.geometry.space import (POI, Area, Column, CrossDescription, LeaveDescription, LineObstacle,
Obstacle, Ramp, SpaceGeometryMixin, Stair) Obstacle, Ramp, SpaceGeometryMixin, Stair)
from c3nav.mapdata.models.level import Level from c3nav.mapdata.models.level import Level
from c3nav.mapdata.models.locations import (Location, LocationGroupCategory, LocationRedirect, LocationSlug, from c3nav.mapdata.models.locations import (DynamicLocation, Location, LocationGroupCategory, LocationRedirect,
SpecificLocation) LocationSlug, Position, SpecificLocation)
from c3nav.mapdata.utils.cache.local import LocalCacheProxy from c3nav.mapdata.utils.cache.local import LocalCacheProxy
from c3nav.mapdata.utils.cache.stats import increment_cache_key from c3nav.mapdata.utils.cache.stats import increment_cache_key
from c3nav.mapdata.utils.locations import (get_location_by_id_for_request, get_location_by_slug_for_request, from c3nav.mapdata.utils.locations import (get_location_by_id_for_request, get_location_by_slug_for_request,
@ -463,6 +464,25 @@ class LocationBySlugViewSet(LocationViewSetBase):
return get_location_by_slug_for_request(self.kwargs['slug'], self.request) return get_location_by_slug_for_request(self.kwargs['slug'], self.request)
class DynamicLocationPositionViewSet(RetrieveModelMixin, GenericViewSet):
queryset = LocationSlug.objects.all()
lookup_field = 'slug'
lookup_value_regex = r'[^/]+'
def get_object(self):
slug = self.kwargs['slug']
if slug.startswith('p:'):
return get_object_or_404(Position, secret=slug[2:])
if slug.isdigit():
return get_object_or_404(DynamicLocation, pk=slug)
raise Http404
@api_stats('dynamic_location_retrieve')
def retrieve(self, request, key=None, *args, **kwargs):
obj = self.get_object()
return Response(obj.serialize_position())
class SourceViewSet(MapdataViewSet): class SourceViewSet(MapdataViewSet):
queryset = Source.objects.all() queryset = Source.objects.all()
order_by = ('name',) order_by = ('name',)

View file

@ -199,7 +199,7 @@ class AccessPermission(models.Model):
@classmethod @classmethod
def get_for_request(cls, request): def get_for_request(cls, request):
if not request.user.is_authenticated: if not request or not request.user.is_authenticated:
return set() return set()
if request.user_permissions.grant_all_access: if request.user_permissions.grant_all_access:

View file

@ -111,7 +111,7 @@ class Location(LocationSlug, AccessRestrictionMixin, TitledMixin, models.Model):
result = super().serialize(detailed=detailed, **kwargs) result = super().serialize(detailed=detailed, **kwargs)
if not detailed: if not detailed:
fields = ('id', 'type', 'slug', 'title', 'subtitle', 'icon', 'point', 'bounds', 'grid_square', fields = ('id', 'type', 'slug', 'title', 'subtitle', 'icon', 'point', 'bounds', 'grid_square',
'locations', 'on_top_of', 'label_settings', 'label_override', 'add_search') 'locations', 'on_top_of', 'label_settings', 'label_override', 'add_search', 'dynamic')
result = {name: result[name] for name in fields if name in result} result = {name: result[name] for name in fields if name in result}
return result return result
@ -236,11 +236,15 @@ class SpecificLocation(Location, models.Model):
return result return result
@property @cached_property
def subtitle(self): def describing_groups(self):
groups = tuple(self.groups.all() if 'groups' in getattr(self, '_prefetched_objects_cache', ()) else ()) groups = tuple(self.groups.all() if 'groups' in getattr(self, '_prefetched_objects_cache', ()) else ())
groups = tuple(group for group in groups if group.can_describe) groups = tuple(group for group in groups if group.can_describe)
subtitle = groups[0].title if groups else self.__class__._meta.verbose_name return groups
@property
def subtitle(self):
subtitle = self.describing_groups[0].title if self.describing_groups else self.__class__._meta.verbose_name
if self.grid_square: if self.grid_square:
return '%s, %s' % (subtitle, self.grid_square) return '%s, %s' % (subtitle, self.grid_square)
return subtitle return subtitle
@ -462,7 +466,15 @@ class LabelSettings(SerializableMixin, models.Model):
ordering = ('min_zoom', '-font_size') ordering = ('min_zoom', '-font_size')
class DynamicLocation(SpecificLocation, models.Model): class CustomLocationProxyMixin:
def get_custom_location(self):
raise NotImplementedError
def serialize_position(self):
raise NotImplementedError
class DynamicLocation(CustomLocationProxyMixin, SpecificLocation, models.Model):
position_secret = models.CharField(_('position secret'), max_length=32, null=True, blank=True) position_secret = models.CharField(_('position secret'), max_length=32, null=True, blank=True)
class Meta: class Meta:
@ -470,21 +482,55 @@ class DynamicLocation(SpecificLocation, models.Model):
verbose_name_plural = _('Dynamic locations') verbose_name_plural = _('Dynamic locations')
default_related_name = 'dynamic_locations' default_related_name = 'dynamic_locations'
"""
def _serialize(self, **kwargs): def _serialize(self, **kwargs):
"""custom_location = self.get_custom_location()
print(custom_location)
result = {} if custom_location is None else custom_location.serialize(**kwargs)
super_result = super()._serialize(**kwargs)
super_result['subtitle'] = '%s %s, %s' % (_('(moving)'), result['title'], result['subtitle'])
result.update(super_result)"""
result = super()._serialize(**kwargs) result = super()._serialize(**kwargs)
result['dynamic'] = True
return result return result
@property def serialize_position(self):
def grid_square(self): custom_location = self.get_custom_location()
return grid.get_squares_for_bounds(self.geometry.bounds) or '' if custom_location is None:
return {
'available': False,
'title': self.title,
'subtitle': '%s %s, %s' % (_('currently unavailable'), _('(moving)'), self.subtitle)
}
result = custom_location.serialize()
result.update({
'available': True,
'id': self.pk,
'slug': self.slug,
'coordinates': custom_location.pk,
'icon': self.get_icon(),
'title': self.title,
'subtitle': '%s %s%s, %s' % (
_('(moving)'),
('%s, ' % self.subtitle) if self.describing_groups else '',
result['title'],
result['subtitle']
),
})
return result
def get_custom_location(self):
if not self.position_secret:
return None
try:
return Position.objects.get(secret=self.position_secret).get_custom_location()
except Position.DoesNotExist:
return None
def details_display(self, editor_url=True, **kwargs): def details_display(self, editor_url=True, **kwargs):
result = super().details_display(**kwargs) result = super().details_display(**kwargs)
if editor_url: if editor_url:
result['editor_url'] = reverse('editor.areas.edit', kwargs={'space': self.space_id, 'pk': self.pk}) result['editor_url'] = reverse('editor.dynamic_locations.edit', kwargs={'pk': self.pk})
return result return result
"""
def get_position_secret(): def get_position_secret():
@ -495,7 +541,7 @@ def get_position_api_secret():
return get_random_string(64, string.ascii_letters+string.digits) return get_random_string(64, string.ascii_letters+string.digits)
class Position(models.Model): class Position(CustomLocationProxyMixin, models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
name = models.CharField(_('name'), max_length=32) name = models.CharField(_('name'), max_length=32)
secret = models.CharField(_('secret'), unique=True, max_length=32, default=get_position_secret) secret = models.CharField(_('secret'), unique=True, max_length=32, default=get_position_secret)
@ -519,6 +565,9 @@ class Position(models.Model):
self.cordinates = None self.cordinates = None
self.last_coordinates_update = end_time self.last_coordinates_update = end_time
def get_custom_location(self):
return self.coordinates
@classmethod @classmethod
def user_has_positions(cls, user): def user_has_positions(cls, user):
if not user.is_authenticated: if not user.is_authenticated:
@ -530,6 +579,33 @@ class Position(models.Model):
cache.set(cache_key, result, 600) cache.set(cache_key, result, 600)
return result return result
def serialize_position(self):
custom_location = self.get_custom_location()
if custom_location is None:
return {
'id': self.secret,
'slug': self.secret,
'available': False,
'icon': 'my_location',
'title': self.name,
'subtitle': _('currently unavailable'),
}
result = custom_location.serialize()
result.update({
'available': True,
'id': 'p:%s' % self.secret,
'slug': 'p:%s' % self.secret,
'coordinates': custom_location.pk,
'icon': 'my_location',
'title': self.name,
'subtitle': '%s, %s, %s' % (
_('Position'),
result['title'],
result['subtitle']
),
})
return result
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
with transaction.atomic(): with transaction.atomic():
super().save(*args, **kwargs) super().save(*args, **kwargs)