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.editor.api import ChangeSetViewSet, EditorViewSet
from c3nav.mapdata.api import (AccessRestrictionGroupViewSet, AccessRestrictionViewSet, AreaViewSet, BuildingViewSet,
ColumnViewSet, CrossDescriptionViewSet, DoorViewSet, HoleViewSet,
LeaveDescriptionViewSet, LevelViewSet, LineObstacleViewSet, LocationBySlugViewSet,
LocationGroupCategoryViewSet, LocationGroupViewSet, LocationViewSet, MapViewSet,
ObstacleViewSet, POIViewSet, RampViewSet, SourceViewSet, SpaceViewSet, StairViewSet,
UpdatesViewSet)
ColumnViewSet, CrossDescriptionViewSet, DoorViewSet, DynamicLocationPositionViewSet,
HoleViewSet, LeaveDescriptionViewSet, LevelViewSet, LineObstacleViewSet,
LocationBySlugViewSet, LocationGroupCategoryViewSet, LocationGroupViewSet,
LocationViewSet, MapViewSet, ObstacleViewSet, POIViewSet, RampViewSet, SourceViewSet,
SpaceViewSet, StairViewSet, UpdatesViewSet)
from c3nav.mapdata.utils.user import can_access_editor
from c3nav.routing.api import RoutingViewSet
@ -41,6 +41,7 @@ router.register(r'accessrestrictiongroups', AccessRestrictionGroupViewSet)
router.register(r'locations', LocationViewSet)
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'locationgroups', LocationGroupViewSet)

View file

@ -6,7 +6,7 @@ from urllib.parse import urlparse
from django.core.cache import cache
from django.db.models import Prefetch
from django.http import HttpResponse
from django.http import Http404, HttpResponse
from django.shortcuts import redirect
from django.utils.cache import get_conditional_response
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 rest_framework.decorators import action
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.response import Response
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,
Obstacle, Ramp, SpaceGeometryMixin, Stair)
from c3nav.mapdata.models.level import Level
from c3nav.mapdata.models.locations import (Location, LocationGroupCategory, LocationRedirect, LocationSlug,
SpecificLocation)
from c3nav.mapdata.models.locations import (DynamicLocation, Location, LocationGroupCategory, LocationRedirect,
LocationSlug, Position, SpecificLocation)
from c3nav.mapdata.utils.cache.local import LocalCacheProxy
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,
@ -463,6 +464,25 @@ class LocationBySlugViewSet(LocationViewSetBase):
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):
queryset = Source.objects.all()
order_by = ('name',)

View file

@ -199,7 +199,7 @@ class AccessPermission(models.Model):
@classmethod
def get_for_request(cls, request):
if not request.user.is_authenticated:
if not request or not request.user.is_authenticated:
return set()
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)
if not detailed:
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}
return result
@ -236,11 +236,15 @@ class SpecificLocation(Location, models.Model):
return result
@property
def subtitle(self):
@cached_property
def describing_groups(self):
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)
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:
return '%s, %s' % (subtitle, self.grid_square)
return subtitle
@ -462,7 +466,15 @@ class LabelSettings(SerializableMixin, models.Model):
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)
class Meta:
@ -470,21 +482,55 @@ class DynamicLocation(SpecificLocation, models.Model):
verbose_name_plural = _('Dynamic locations')
default_related_name = 'dynamic_locations'
"""
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['dynamic'] = True
return result
@property
def grid_square(self):
return grid.get_squares_for_bounds(self.geometry.bounds) or ''
def serialize_position(self):
custom_location = self.get_custom_location()
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):
result = super().details_display(**kwargs)
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
"""
def get_position_secret():
@ -495,7 +541,7 @@ def get_position_api_secret():
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)
name = models.CharField(_('name'), max_length=32)
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.last_coordinates_update = end_time
def get_custom_location(self):
return self.coordinates
@classmethod
def user_has_positions(cls, user):
if not user.is_authenticated:
@ -530,6 +579,33 @@ class Position(models.Model):
cache.set(cache_key, result, 600)
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):
with transaction.atomic():
super().save(*args, **kwargs)