diff --git a/src/c3nav/mapdata/models/locations.py b/src/c3nav/mapdata/models/locations.py index e98d2a8b..c2de7d65 100644 --- a/src/c3nav/mapdata/models/locations.py +++ b/src/c3nav/mapdata/models/locations.py @@ -470,6 +470,22 @@ class CustomLocationProxyMixin: def get_custom_location(self): raise NotImplementedError + @property + def available(self): + return self.get_custom_location() is not None + + @property + def x(self): + return self.get_custom_location().x + + @property + def y(self): + return self.get_custom_location().y + + @property + def level(self): + return self.get_custom_location().level + def serialize_position(self): raise NotImplementedError @@ -550,6 +566,9 @@ class Position(CustomLocationProxyMixin, models.Model): coordinates_id = models.CharField(_('coordinates'), null=True, max_length=48) api_secret = models.CharField(_('api secret'), max_length=64, default=get_position_api_secret) + can_search = True + can_describe = False + coordinates = LocationById() class Meta: @@ -583,8 +602,8 @@ class Position(CustomLocationProxyMixin, models.Model): custom_location = self.get_custom_location() if custom_location is None: return { - 'id': self.secret, - 'slug': self.secret, + 'id': 'p:%s' % self.secret, + 'slug': 'p:%s' % self.secret, 'available': False, 'icon': 'my_location', 'title': self.name, @@ -606,6 +625,18 @@ class Position(CustomLocationProxyMixin, models.Model): }) return result + @property + def slug(self): + return 'p:%s' % self.secret + + def serialize(self, *args, **kwargs): + return self.serialize_position() + + def get_geometry(self, *args, **kwargs): + return None + + level_id = None + def save(self, *args, **kwargs): with transaction.atomic(): super().save(*args, **kwargs) diff --git a/src/c3nav/mapdata/utils/locations.py b/src/c3nav/mapdata/utils/locations.py index e40574eb..2ceb2fbd 100644 --- a/src/c3nav/mapdata/utils/locations.py +++ b/src/c3nav/mapdata/utils/locations.py @@ -4,7 +4,7 @@ import re from collections import OrderedDict from functools import reduce from itertools import chain -from typing import List, Mapping, Optional +from typing import List, Mapping, Optional, Union from django.apps import apps from django.db.models import Prefetch, Q @@ -18,7 +18,7 @@ from c3nav.mapdata.models.access import AccessPermission from c3nav.mapdata.models.geometry.base import GeometryMixin from c3nav.mapdata.models.geometry.level import LevelGeometryMixin, Space from c3nav.mapdata.models.geometry.space import SpaceGeometryMixin -from c3nav.mapdata.models.locations import LocationRedirect, LocationSlug, SpecificLocation +from c3nav.mapdata.models.locations import LocationRedirect, LocationSlug, Position, SpecificLocation from c3nav.mapdata.utils.cache.local import LocalCacheProxy from c3nav.mapdata.utils.models import get_submodels @@ -206,12 +206,18 @@ def get_location_by_id_for_request(pk, request): if isinstance(pk, str): if pk.isdigit(): pk = int(pk) + elif pk.startswith('p:'): + try: + # return immediately, don't cache for obvious reasons + return Position.objects.get(secret=pk[2:]) + except Position.DoesNotExist: + return None else: return get_custom_location_for_request(pk, request) return locations_for_request(request).get(pk) -def get_location_by_slug_for_request(slug: str, request) -> Optional[LocationSlug]: +def get_location_by_slug_for_request(slug: str, request) -> Optional[Union[LocationSlug, Position]]: cache_key = 'mapdata:location:by_slug:%s:%s' % (AccessPermission.cache_key_for_request(request), slug) location = proxied_cache.get(cache_key, None) if location is not None: @@ -221,6 +227,12 @@ def get_location_by_slug_for_request(slug: str, request) -> Optional[LocationSlu location = get_custom_location_for_request(slug, request) if location is None: return None + elif slug.startswith('p:'): + try: + # return immediately, don't cache for obvious reasons + return Position.objects.get(secret=slug[2:]) + except Position.DoesNotExist: + return None elif ':' in slug: code, pk = slug.split(':', 1) model_name = LocationSlug.LOCATION_TYPE_BY_CODE.get(code) diff --git a/src/c3nav/routing/api.py b/src/c3nav/routing/api.py index 3e82cb24..e8b793fd 100644 --- a/src/c3nav/routing/api.py +++ b/src/c3nav/routing/api.py @@ -7,6 +7,7 @@ from rest_framework.viewsets import ViewSet from c3nav.mapdata.api import api_stats_clean_location_value from c3nav.mapdata.models.access import AccessPermission +from c3nav.mapdata.models.locations import Position from c3nav.mapdata.utils.cache.stats import increment_cache_key from c3nav.mapdata.utils.locations import visible_locations_for_request from c3nav.routing.exceptions import LocationUnreachable, NoRouteFound, NotYetRoutable @@ -71,8 +72,8 @@ class RoutingViewSet(ViewSet): return Response({ 'request': { - 'origin': form.cleaned_data['origin'].pk, - 'destination': form.cleaned_data['destination'].pk, + 'origin': self.get_request_pk(form.cleaned_data['origin']), + 'destination': self.get_request_pk(form.cleaned_data['destination']), }, 'options': options.serialize(), 'report_issue_url': reverse('site.report_create', kwargs={ @@ -83,6 +84,9 @@ class RoutingViewSet(ViewSet): 'result': route.serialize(locations=visible_locations_for_request(request)), }) + def get_request_pk(self, location): + return location.slug if isinstance(location, Position) else location.pk + @action(detail=False, methods=['get', 'post']) def options(self, request, *args, **kwargs): options = RouteOptions.get_for_request(request) diff --git a/src/c3nav/routing/route.py b/src/c3nav/routing/route.py index 19e32a90..60a5ecfe 100644 --- a/src/c3nav/routing/route.py +++ b/src/c3nav/routing/route.py @@ -12,7 +12,10 @@ def describe_location(location, locations): final_location = locations.get(location.pk) if final_location is not None: location = final_location - return location.serialize(include_type=True, detailed=False, simple_geometry=True) + result = location.serialize(include_type=True, detailed=False, simple_geometry=True) + if hasattr(location, 'serialize_position'): + result.update(location.serialize_position()) + return result class Route: @@ -170,7 +173,6 @@ class Route: options_summary = ', '.join(str(s) for s in options_summary) - return OrderedDict(( ('origin', describe_location(self.origin, locations)), ('destination', describe_location(self.destination, locations)), diff --git a/src/c3nav/routing/router.py b/src/c3nav/routing/router.py index 007c465a..d7730341 100644 --- a/src/c3nav/routing/router.py +++ b/src/c3nav/routing/router.py @@ -328,8 +328,8 @@ class Router: if poi.space_id not in restrictions.spaces and poi.access_restriction_id not in restrictions), )) elif isinstance(location, (CustomLocation, CustomLocationProxyMixin)): - if isinstance(location, CustomLocationProxyMixin): - location = location.get_custom_location() + if isinstance(location, CustomLocationProxyMixin) and not location.available: + raise LocationUnreachable point = Point(location.x, location.y) location = RouterPoint(location) space = self.space_for_point(location.level.pk, point, restrictions) diff --git a/src/c3nav/site/static/site/js/c3nav.js b/src/c3nav/site/static/site/js/c3nav.js index b191f77e..a4046b3f 100644 --- a/src/c3nav/site/static/site/js/c3nav.js +++ b/src/c3nav/site/static/site/js/c3nav.js @@ -1547,6 +1547,7 @@ c3nav = { $.getJSON('/api/locations/' + location.id + '/geometry/', c3nav._location_geometry_loaded); } + if (!location.point) return; var point = c3nav._location_point_overrides[location.id] || location.point.slice(1), latlng = L.GeoJSON.coordsToLatLng(point), buttons = $('#location-popup-buttons').clone(); diff --git a/src/c3nav/site/urls.py b/src/c3nav/site/urls.py index 8f8fc918..851c389e 100644 --- a/src/c3nav/site/urls.py +++ b/src/c3nav/site/urls.py @@ -4,9 +4,9 @@ from c3nav.site.views import (about_view, access_redeem_view, account_view, chan login_view, logout_view, map_index, position_create, position_detail, position_list, position_set, qr_code, register_view, report_create, report_detail, report_list) -slug = r'(?P[a-z0-9-_.:]+)' +slug = r'(?P[a-zA-Z0-9-_.:]+)' coordinates = r'(?P[a-z0-9-_:]+:-?\d+(\.\d+)?:-?\d+(\.\d+)?)' -slug2 = r'(?P[a-z0-9-_.:]+)' +slug2 = r'(?P[a-zA-Z0-9-_.:]+)' details = r'(?P
details/)?' nearby = r'(?Pnearby/)?' options = r'(?Poptions/)?'