diff --git a/src/c3nav/api/urls.py b/src/c3nav/api/urls.py index 810444c4..d9439f56 100644 --- a/src/c3nav/api/urls.py +++ b/src/c3nav/api/urls.py @@ -1,3 +1,4 @@ +import inspect import re from collections import OrderedDict @@ -14,6 +15,7 @@ from c3nav.mapdata.api import (AccessRestrictionGroupViewSet, AccessRestrictionV 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 router = SimpleRouter() @@ -62,8 +64,11 @@ class APIRoot(GenericAPIView): @cached_property def urls(self): + include_editor = can_access_editor(self.request) urls = OrderedDict() for urlpattern in router.urls: + if not include_editor and inspect.getmodule(urlpattern.callback).__name__.startswith('c3nav.editor.'): + continue name = urlpattern.name url = self._format_pattern(str(urlpattern.pattern)).replace('{pk}', '{id}') base = url.split('/', 1)[0] diff --git a/src/c3nav/control/migrations/0005_editor_mapdata_permissions.py b/src/c3nav/control/migrations/0005_editor_mapdata_permissions.py new file mode 100644 index 00000000..9f9d5426 --- /dev/null +++ b/src/c3nav/control/migrations/0005_editor_mapdata_permissions.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1.1 on 2018-09-19 15:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('control', '0004_more_user_permissions'), + ] + + operations = [ + migrations.AddField( + model_name='userpermissions', + name='base_mapdata_access', + field=models.BooleanField(default=False, verbose_name='can always access base map data'), + ), + migrations.AddField( + model_name='userpermissions', + name='editor_access', + field=models.BooleanField(default=False, verbose_name='can always access editor'), + ), + ] diff --git a/src/c3nav/control/models.py b/src/c3nav/control/models.py index e13174a7..204cdacc 100644 --- a/src/c3nav/control/models.py +++ b/src/c3nav/control/models.py @@ -14,6 +14,8 @@ class UserPermissions(models.Model): review_changesets = models.BooleanField(default=False, verbose_name=_('can review changesets')) direct_edit = models.BooleanField(default=False, verbose_name=_('can activate direct editing')) max_changeset_changes = models.PositiveSmallIntegerField(default=10, verbose_name=_('max changes per changeset')) + editor_access = models.BooleanField(default=False, verbose_name=_('can always access editor')) + base_mapdata_access = models.BooleanField(default=False, verbose_name=_('can always access base map data')) control_panel = models.BooleanField(default=False, verbose_name=_('can access control panel')) grant_permissions = models.BooleanField(default=False, verbose_name=_('can grant control permissions')) diff --git a/src/c3nav/editor/api.py b/src/c3nav/editor/api.py index 780fe421..1c43298b 100644 --- a/src/c3nav/editor/api.py +++ b/src/c3nav/editor/api.py @@ -2,7 +2,7 @@ from itertools import chain from django.db.models import Prefetch, Q from rest_framework.decorators import detail_route, list_route -from rest_framework.exceptions import ValidationError +from rest_framework.exceptions import PermissionDenied, ValidationError from rest_framework.generics import get_object_or_404 from rest_framework.response import Response from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet @@ -13,6 +13,7 @@ from c3nav.editor.views.base import etag_func from c3nav.mapdata.api import api_etag from c3nav.mapdata.models import Area, Door, MapUpdate, Source from c3nav.mapdata.models.geometry.space import POI +from c3nav.mapdata.utils.user import can_access_editor class EditorViewSet(ViewSet): @@ -71,6 +72,9 @@ class EditorViewSet(ViewSet): @list_route(methods=['get']) @api_etag(etag_func=etag_func, cache_parameters={'level': str, 'space': str}) def geometries(self, request, *args, **kwargs): + if not can_access_editor(request): + return PermissionDenied + Level = request.changeset.wrap_model('Level') Space = request.changeset.wrap_model('Space') @@ -209,6 +213,9 @@ class EditorViewSet(ViewSet): @list_route(methods=['get']) @api_etag(etag_func=MapUpdate.current_cache_key, cache_parameters={}) def geometrystyles(self, request, *args, **kwargs): + if not can_access_editor(request): + return PermissionDenied + return Response({ 'building': '#aaaaaa', 'space': '#eeeeee', @@ -231,6 +238,9 @@ class EditorViewSet(ViewSet): @list_route(methods=['get']) @api_etag(etag_func=etag_func, cache_parameters={}) def bounds(self, request, *args, **kwargs): + if not can_access_editor(request): + return PermissionDenied + return Response({ 'bounds': Source.max_bounds(), }) @@ -247,18 +257,26 @@ class ChangeSetViewSet(ReadOnlyModelViewSet): return ChangeSet.qs_for_request(self.request).select_related('last_update', 'last_state_update', 'last_change') def list(self, request, *args, **kwargs): + if not can_access_editor(request): + return PermissionDenied return Response([obj.serialize() for obj in self.get_queryset().order_by('id')]) def retrieve(self, request, *args, **kwargs): + if not can_access_editor(request): + return PermissionDenied return Response(self.get_object().serialize()) @list_route(methods=['get']) def current(self, request, *args, **kwargs): + if not can_access_editor(request): + return PermissionDenied changeset = ChangeSet.get_for_request(request) return Response(changeset.serialize()) @detail_route(methods=['get']) def changes(self, request, *args, **kwargs): + if not can_access_editor(request): + return PermissionDenied changeset = self.get_object() changeset.fill_changes_cache() return Response([obj.serialize() for obj in changeset.iter_changed_objects()]) diff --git a/src/c3nav/editor/views/base.py b/src/c3nav/editor/views/base.py index 74188bab..d959e2f2 100644 --- a/src/c3nav/editor/views/base.py +++ b/src/c3nav/editor/views/base.py @@ -1,5 +1,6 @@ from functools import wraps +from django.core.exceptions import PermissionDenied from django.http import HttpResponseNotModified, HttpResponseRedirect from django.shortcuts import render from django.utils.cache import patch_vary_headers @@ -7,6 +8,7 @@ from django.utils.translation import get_language from c3nav.editor.models import ChangeSet from c3nav.mapdata.models.access import AccessPermission +from c3nav.mapdata.utils.user import can_access_editor def sidebar_view(func=None, select_related=None): @@ -17,6 +19,9 @@ def sidebar_view(func=None, select_related=None): @wraps(func) def with_ajax_check(request, *args, **kwargs): + if not can_access_editor(request): + raise PermissionDenied + request.changeset = ChangeSet.get_for_request(request, select_related) ajax = request.is_ajax() or 'ajax' in request.GET diff --git a/src/c3nav/mapdata/api.py b/src/c3nav/mapdata/api.py index 7a605a6f..3ddb2a51 100644 --- a/src/c3nav/mapdata/api.py +++ b/src/c3nav/mapdata/api.py @@ -29,7 +29,7 @@ from c3nav.mapdata.models.locations import (Location, LocationGroupCategory, Loc from c3nav.mapdata.utils.locations import (get_location_by_id_for_request, get_location_by_slug_for_request, searchable_locations_for_request, visible_locations_for_request) from c3nav.mapdata.utils.models import get_submodels -from c3nav.mapdata.utils.user import get_user_data +from c3nav.mapdata.utils.user import can_access_base_mapdata, can_access_editor, get_user_data from c3nav.mapdata.views import set_tile_access_cookie @@ -42,7 +42,7 @@ def optimize_query(qs): return qs -def api_etag(permissions=True, etag_func=AccessPermission.etag_func, cache_parameters=None): +def api_etag(permissions=True, etag_func=AccessPermission.etag_func, cache_parameters=None, base_mapdata_check=False): def wrapper(func): @wraps(func) def wrapped_func(self, request, *args, **kwargs): @@ -50,6 +50,8 @@ def api_etag(permissions=True, etag_func=AccessPermission.etag_func, cache_param etag_user = (':'+str(request.user.pk or 0)) if response_format == 'api' else '' raw_etag = '%s%s:%s:%s' % (response_format, etag_user, get_language(), (etag_func(request) if permissions else MapUpdate.current_cache_key())) + if base_mapdata_check and self.base_mapdata: + raw_etag += ':%d' % can_access_base_mapdata(request) etag = quote_etag(raw_etag) response = get_conditional_response(request, etag=etag) @@ -68,8 +70,9 @@ def api_etag(permissions=True, etag_func=AccessPermission.etag_func, cache_param if cache_parameters is not None and response.status_code == 200: cache.set(cache_key, response.data, 300) - response['ETag'] = etag - response['Cache-Control'] = 'no-cache' + if response.status_code == 200: + response['ETag'] = etag + response['Cache-Control'] = 'no-cache' return response return wrapped_func return wrapper @@ -90,6 +93,7 @@ class MapViewSet(ViewSet): class MapdataViewSet(ReadOnlyModelViewSet): + base_mapdata = False order_by = ('id', ) def get_queryset(self): @@ -98,6 +102,12 @@ class MapdataViewSet(ReadOnlyModelViewSet): return qs.model.qs_for_request(self.request) return qs + @staticmethod + def can_access_geometry(request, obj): + if isinstance(obj, (Building, Space, Door)): + return can_access_base_mapdata(request) + return True + qs_filter = namedtuple('qs_filter', ('field', 'model', 'key', 'value')) def _get_keys_for_model(self, request, model, key): @@ -165,16 +175,19 @@ class MapdataViewSet(ReadOnlyModelViewSet): cache.set(cache_key, results, 300) return results - @api_etag() + @api_etag(base_mapdata_check=True) def list(self, request, *args, **kwargs): - geometry = ('geometry' in request.GET) + geometry = 'geometry' in request.GET results = self._get_list(request) + if results: + geometry = geometry and self.can_access_geometry(request, results[0]) return Response([obj.serialize(geometry=geometry) for obj in results]) - @api_etag() + @api_etag(base_mapdata_check=True) def retrieve(self, request, *args, **kwargs): - return Response(self.get_object().serialize()) + obj = self.get_object() + return Response(obj.serialize(geometry=self.can_access_geometry(request, obj))) @staticmethod def list_types(models_list, **kwargs): @@ -197,16 +210,18 @@ class LevelViewSet(MapdataViewSet): class BuildingViewSet(MapdataViewSet): - """ Add ?geometry=1 to get geometries, add ?level= to filter by level. """ + """ Add ?geometry=1 to get geometries if available, add ?level= to filter by level. """ queryset = Building.objects.all() + base_mapdata = True class SpaceViewSet(MapdataViewSet): """ - Add ?geometry=1 to get geometries, add ?level= to filter by level, add ?group= to filter by group. + Add ?geometry=1 to get geometries if available, ?level= to filter by level, ?group= to filter by group. A Space is a Location – so if it is visible, you can use its ID in the Location API as well. """ queryset = Space.objects.all() + base_mapdata = True @list_route(methods=['get']) @api_etag(permissions=False, cache_parameters={}) @@ -215,8 +230,9 @@ class SpaceViewSet(MapdataViewSet): class DoorViewSet(MapdataViewSet): - """ Add ?geometry=1 to get geometries, add ?level= to filter by level. """ + """ Add ?geometry=1 to get geometries if available, add ?level= to filter by level. """ queryset = Door.objects.all() + base_mapdata = True class HoleViewSet(MapdataViewSet): @@ -287,11 +303,12 @@ class LocationGroupViewSet(MapdataViewSet): class LocationViewSetBase(RetrieveModelMixin, GenericViewSet): queryset = LocationSlug.objects.all() + base_mapdata = True def get_object(self) -> LocationSlug: raise NotImplementedError - @api_etag(cache_parameters={'show_redirects': bool, 'detailed': bool, 'geometry': bool}) + @api_etag(cache_parameters={'show_redirects': bool, 'detailed': bool, 'geometry': bool}, base_mapdata_check=True) def retrieve(self, request, key=None, *args, **kwargs): show_redirects = 'show_redirects' in request.GET detailed = 'detailed' in request.GET @@ -307,10 +324,11 @@ class LocationViewSetBase(RetrieveModelMixin, GenericViewSet): return redirect('../' + str(location.target.slug)) # todo: why does redirect/reverse not work here? return Response(location.serialize(include_type=True, detailed=detailed, - geometry=geometry, simple_geometry=True)) + geometry=geometry and MapdataViewSet.can_access_geometry(request, location), + simple_geometry=True)) @detail_route(methods=['get']) - @api_etag() + @api_etag(base_mapdata_check=True) def details(self, request, **kwargs): location = self.get_object() @@ -320,7 +338,10 @@ class LocationViewSetBase(RetrieveModelMixin, GenericViewSet): if isinstance(location, LocationRedirect): return redirect('../' + str(location.target.pk) + '/details/') - return Response(location.details_display()) + return Response(location.details_display( + detailed_geometry=MapdataViewSet.can_access_geometry(request, location), + editor_url=can_access_editor(request) + )) class LocationViewSet(LocationViewSetBase): @@ -332,7 +353,7 @@ class LocationViewSet(LocationViewSetBase): add ?searchable to only show locations with can_search set to true ordered by relevance add ?detailed to show all attributes - add ?geometry to show geometries + add ?geometry to show geometries if available /{id}/ add ?show_redirect=1 to suppress redirects and show them as JSON. also possible: /by_slug/{slug}/ """ @@ -342,24 +363,27 @@ class LocationViewSet(LocationViewSetBase): def get_object(self): return get_location_by_id_for_request(self.kwargs['pk'], self.request) - @api_etag(cache_parameters={'searchable': bool, 'detailed': bool, 'geometry': bool}) + @api_etag(cache_parameters={'searchable': bool, 'detailed': bool, 'geometry': bool}, base_mapdata_check=True) def list(self, request, *args, **kwargs): searchable = 'searchable' in request.GET detailed = 'detailed' in request.GET geometry = 'geometry' in request.GET - cache_key = 'mapdata:api:location:list:%d:%s' % ( + cache_key = 'mapdata:api:location:list:%d:%s:%d' % ( searchable + detailed*2 + geometry*4, - AccessPermission.cache_key_for_request(self.request) + AccessPermission.cache_key_for_request(request), + can_access_base_mapdata(request) ) result = cache.get(cache_key, None) if result is None: if searchable: - locations = searchable_locations_for_request(self.request) + locations = searchable_locations_for_request(request) else: - locations = visible_locations_for_request(self.request).values() + locations = visible_locations_for_request(request).values() - result = tuple(obj.serialize(include_type=True, detailed=detailed, geometry=geometry, simple_geometry=True) + result = tuple(obj.serialize(include_type=True, detailed=detailed, + geometry=geometry and MapdataViewSet.can_access_geometry(request, obj), + simple_geometry=True) for obj in locations) cache.set(cache_key, result, 300) diff --git a/src/c3nav/mapdata/models/access.py b/src/c3nav/mapdata/models/access.py index f25e1a48..fec361c2 100644 --- a/src/c3nav/mapdata/models/access.py +++ b/src/c3nav/mapdata/models/access.py @@ -250,8 +250,8 @@ class AccessRestrictionMixin(SerializableMixin, models.Model): result['access_restriction'] = self.access_restriction_id return result - def details_display(self): - result = super().details_display() + def details_display(self, **kwargs): + result = super().details_display(**kwargs) result['display'].extend([ (_('Access Restriction'), self.access_restriction_id and self.access_restriction.title), ]) diff --git a/src/c3nav/mapdata/models/base.py b/src/c3nav/mapdata/models/base.py index 3ce57fa2..68331dc5 100644 --- a/src/c3nav/mapdata/models/base.py +++ b/src/c3nav/mapdata/models/base.py @@ -33,7 +33,7 @@ class SerializableMixin(models.Model): result['id'] = self.pk return result - def details_display(self): + def details_display(self, **kwargs): return { 'id': self.pk, 'display': [ @@ -72,8 +72,8 @@ class TitledMixin(SerializableMixin, models.Model): result['title'] = self.title return result - def details_display(self): - result = super().details_display() + def details_display(self, **kwargs): + result = super().details_display(**kwargs) for lang, title in sorted(self.titles.items(), key=lambda item: item[0] != get_language()): language = _('Title ({lang})').format(lang=get_language_info(lang)['name_translated']) result['display'].append((language, title)) diff --git a/src/c3nav/mapdata/models/geometry/base.py b/src/c3nav/mapdata/models/geometry/base.py index ca9024de..431e7aa5 100644 --- a/src/c3nav/mapdata/models/geometry/base.py +++ b/src/c3nav/mapdata/models/geometry/base.py @@ -2,7 +2,7 @@ import math from collections import OrderedDict from django.utils.functional import cached_property -from shapely.geometry import Point, mapping +from shapely.geometry import Point, box, mapping from shapely.ops import unary_union from c3nav.mapdata.models.base import SerializableMixin @@ -78,9 +78,12 @@ class GeometryMixin(SerializableMixin): (int(math.ceil(maxx)), int(math.ceil(maxy)))) return result - def details_display(self): - result = super().details_display() - result['geometry'] = format_geojson(mapping(self.geometry), round=False) + def details_display(self, detailed_geometry=True, **kwargs): + result = super().details_display(**kwargs) + if detailed_geometry: + result['geometry'] = format_geojson(mapping(self.geometry), round=False) + else: + result['geometry'] = format_geojson(mapping(box(*self.geometry.bounds)), round=False) return result def get_shadow_geojson(self): diff --git a/src/c3nav/mapdata/models/geometry/level.py b/src/c3nav/mapdata/models/geometry/level.py index 0d64e399..3fa51773 100644 --- a/src/c3nav/mapdata/models/geometry/level.py +++ b/src/c3nav/mapdata/models/geometry/level.py @@ -49,8 +49,8 @@ class LevelGeometryMixin(GeometryMixin): result['level'] = self.level_id return result - def details_display(self): - result = super().details_display() + def details_display(self, **kwargs): + result = super().details_display(**kwargs) result['display'].insert(3, ( _('Level'), { @@ -126,13 +126,14 @@ class Space(LevelGeometryMixin, SpecificLocation, models.Model): result['height'] = None if self.height is None else float(str(self.height)) return result - def details_display(self): - result = super().details_display() + def details_display(self, editor_url=True, **kwargs): + result = super().details_display(**kwargs) result['display'].extend([ (_('height'), self.height), (_('outside only'), _('Yes') if self.outside else _('No')), ]) - result['editor_url'] = reverse('editor.spaces.detail', kwargs={'level': self.level_id, 'pk': self.pk}) + if editor_url: + result['editor_url'] = reverse('editor.spaces.detail', kwargs={'level': self.level_id, 'pk': self.pk}) return result diff --git a/src/c3nav/mapdata/models/geometry/space.py b/src/c3nav/mapdata/models/geometry/space.py index c748fb0c..9d56bfce 100644 --- a/src/c3nav/mapdata/models/geometry/space.py +++ b/src/c3nav/mapdata/models/geometry/space.py @@ -73,8 +73,8 @@ class SpaceGeometryMixin(GeometryMixin): self.geometry if force else self.get_changed_geometry() )) - def details_display(self): - result = super().details_display() + def details_display(self, **kwargs): + result = super().details_display(**kwargs) result['display'].insert(3, ( _('Space'), { @@ -125,9 +125,10 @@ class Area(SpaceGeometryMixin, SpecificLocation, models.Model): result = super()._serialize(**kwargs) return result - def details_display(self): - result = super().details_display() - result['editor_url'] = reverse('editor.areas.edit', kwargs={'space': self.space_id, 'pk': self.pk}) + 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}) return result @@ -224,9 +225,10 @@ class POI(SpaceGeometryMixin, SpecificLocation, models.Model): verbose_name_plural = _('Points of Interest') default_related_name = 'pois' - def details_display(self): - result = super().details_display() - result['editor_url'] = reverse('editor.pois.edit', kwargs={'space': self.space_id, 'pk': self.pk}) + def details_display(self, editor_url=True, **kwargs): + result = super().details_display(**kwargs) + if editor_url: + result['editor_url'] = reverse('editor.pois.edit', kwargs={'space': self.space_id, 'pk': self.pk}) return result @property diff --git a/src/c3nav/mapdata/models/level.py b/src/c3nav/mapdata/models/level.py index 047632b7..3b3c3c2d 100644 --- a/src/c3nav/mapdata/models/level.py +++ b/src/c3nav/mapdata/models/level.py @@ -77,14 +77,15 @@ class Level(SpecificLocation, models.Model): result['door_height'] = float(str(self.door_height)) return result - def details_display(self): - result = super().details_display() + def details_display(self, editor_url=True, **kwargs): + result = super().details_display(**kwargs) result['display'].insert(3, (_('short label'), self.short_label)) result['display'].extend([ (_('outside only'), self.base_altitude), (_('default height'), self.default_height), ]) - result['editor_url'] = reverse('editor.levels.detail', kwargs={'pk': self.pk}) + if editor_url: + result['editor_url'] = reverse('editor.levels.detail', kwargs={'pk': self.pk}) return result @cached_property diff --git a/src/c3nav/mapdata/models/locations.py b/src/c3nav/mapdata/models/locations.py index 81a699f7..aad5faa4 100644 --- a/src/c3nav/mapdata/models/locations.py +++ b/src/c3nav/mapdata/models/locations.py @@ -60,8 +60,8 @@ class LocationSlug(SerializableMixin, models.Model): result['slug'] = self.get_slug() return result - def details_display(self): - result = super().details_display() + def details_display(self, **kwargs): + result = super().details_display(**kwargs) result['display'].insert(2, (_('Slug'), str(self.get_slug()))) return result @@ -96,8 +96,8 @@ class Location(LocationSlug, AccessRestrictionMixin, TitledMixin, models.Model): result['can_describe'] = self.can_search return result - def details_display(self): - result = super().details_display() + def details_display(self, **kwargs): + result = super().details_display(**kwargs) result['display'].extend([ (_('searchable'), _('Yes') if self.can_search else _('No')), (_('can describe'), _('Yes') if self.can_describe else _('No')) @@ -143,8 +143,8 @@ class SpecificLocation(Location, models.Model): result['groups'] = groups return result - def details_display(self): - result = super().details_display() + def details_display(self, **kwargs): + result = super().details_display(**kwargs) groupcategories = {} for group in self.groups.all(): @@ -258,14 +258,15 @@ class LocationGroup(Location, models.Model): result['locations'] = tuple(obj.pk for obj in getattr(self, 'locations', ())) return result - def details_display(self): - result = super().details_display() + def details_display(self, editor_url=True, **kwargs): + result = super().details_display(**kwargs) result['display'].insert(3, (_('Category'), self.category.title)) result['display'].extend([ (_('color'), self.color), (_('priority'), self.priority), ]) - result['editor_url'] = reverse('editor.locationgroups.edit', kwargs={'pk': self.pk}) + if editor_url: + result['editor_url'] = reverse('editor.locationgroups.edit', kwargs={'pk': self.pk}) return result @property diff --git a/src/c3nav/mapdata/utils/locations.py b/src/c3nav/mapdata/utils/locations.py index c8339430..957bf9f2 100644 --- a/src/c3nav/mapdata/utils/locations.py +++ b/src/c3nav/mapdata/utils/locations.py @@ -294,7 +294,7 @@ class CustomLocation: return result - def details_display(self): + def details_display(self, **kwargs): return { 'id': self.pk, 'display': [ diff --git a/src/c3nav/mapdata/utils/user.py b/src/c3nav/mapdata/utils/user.py index b7cca198..b4f2b723 100644 --- a/src/c3nav/mapdata/utils/user.py +++ b/src/c3nav/mapdata/utils/user.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.utils.functional import lazy from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ungettext_lazy @@ -28,3 +29,11 @@ def get_user_data(request): get_user_data_lazy = lazy(get_user_data, dict) + + +def can_access_base_mapdata(request): + return settings.PUBLIC_BASE_MAPDATA or request.user_permissions.base_mapdata_access + + +def can_access_editor(request): + return settings.PUBLIC_EDITOR or request.user_permissions.editor_access diff --git a/src/c3nav/settings.py b/src/c3nav/settings.py index 2543e095..fcfd9d68 100644 --- a/src/c3nav/settings.py +++ b/src/c3nav/settings.py @@ -41,6 +41,9 @@ if not os.path.exists(TILES_ROOT): if not os.path.exists(CACHE_ROOT): os.mkdir(CACHE_ROOT) +PUBLIC_EDITOR = config.get('c3nav', 'editor', fallback=True) +PUBLIC_BASE_MAPDATA = config.get('c3nav', 'public_base_mapdata', fallback=False) + if config.has_option('django', 'secret'): SECRET_KEY = config.get('django', 'secret') else: diff --git a/src/c3nav/site/templates/site/map.html b/src/c3nav/site/templates/site/map.html index b9af0489..37ec6168 100644 --- a/src/c3nav/site/templates/site/map.html +++ b/src/c3nav/site/templates/site/map.html @@ -10,7 +10,9 @@ {% get_current_language as CURRENT_LANGUAGE %} {{ CURRENT_LANGUAGE | language_name_local }} – {% endif %} - {% trans 'Editor' %} // + {% if editor %} + {% trans 'Editor' %} // + {% endif %} {% trans 'API' %} // Twitter // GitHub diff --git a/src/c3nav/site/views.py b/src/c3nav/site/views.py index 28fe409e..2df532cd 100644 --- a/src/c3nav/site/views.py +++ b/src/c3nav/site/views.py @@ -26,7 +26,7 @@ from c3nav.mapdata.models import Location, Source from c3nav.mapdata.models.access import AccessPermissionToken from c3nav.mapdata.models.locations import LocationRedirect, SpecificLocation from c3nav.mapdata.utils.locations import get_location_by_slug_for_request, levels_by_short_label_for_request -from c3nav.mapdata.utils.user import get_user_data +from c3nav.mapdata.utils.user import can_access_editor, get_user_data from c3nav.mapdata.views import set_tile_access_cookie from c3nav.site.models import Announcement, SiteUpdate @@ -123,6 +123,7 @@ def map_index(request, mode=None, slug=None, slug2=None, details=None, options=N 'initial_bounds': json.dumps(initial_bounds, separators=(',', ':')) if initial_bounds else None, 'last_site_update': json.dumps(SiteUpdate.last_update()), 'ssids': json.dumps(settings.WIFI_SSIDS, separators=(',', ':')) if settings.WIFI_SSIDS else None, + 'editor': can_access_editor(request), 'embed': bool(embed), }