api endpoint to get the current dynamic position
This commit is contained in:
parent
c6aab7fb36
commit
7e56927a72
4 changed files with 118 additions and 21 deletions
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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',)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue