get rid of editor api hybrid responses
This commit is contained in:
parent
79fb026363
commit
09c092e129
4 changed files with 65 additions and 385 deletions
|
@ -97,62 +97,6 @@ def level_geometries(request, level_id: EditorID, update_cache_key: UpdateCacheK
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# todo: need a way to pass the changeset if it's not a session API key
|
|
||||||
|
|
||||||
def resolve_editor_path_api(request, path):
|
|
||||||
resolved = None
|
|
||||||
if path:
|
|
||||||
try:
|
|
||||||
resolved = resolve('/editor/'+path+'/')
|
|
||||||
except Resolver404:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not resolved:
|
|
||||||
try:
|
|
||||||
resolved = resolve('/editor/'+path)
|
|
||||||
except Resolver404:
|
|
||||||
pass
|
|
||||||
|
|
||||||
request.sub_resolver_match = resolved
|
|
||||||
|
|
||||||
return resolved
|
|
||||||
|
|
||||||
|
|
||||||
@editor_api_router.get('/as_api/{path:path}', summary="raw editor access",
|
|
||||||
response={200: dict, **API404.dict(), **auth_permission_responses},
|
|
||||||
openapi_extra={"security": [{"APIKeyAuth": ["editor_access"]}]})
|
|
||||||
@api_etag() # todo: correct?
|
|
||||||
def get_view_as_api(request, path: str):
|
|
||||||
"""
|
|
||||||
get editor views rendered as JSON instead of HTML.
|
|
||||||
`path` is the path after /editor/.
|
|
||||||
this is a mess. good luck. if you actually want to use this, poke us so we might add better documentation.
|
|
||||||
"""
|
|
||||||
resolved = resolve_editor_path_api(request, path)
|
|
||||||
|
|
||||||
if not resolved:
|
|
||||||
raise API404(_('No matching editor view endpoint found.'))
|
|
||||||
|
|
||||||
if not getattr(resolved.func, 'api_hybrid', False):
|
|
||||||
raise API404(_('Matching editor view point does not provide an API.'))
|
|
||||||
|
|
||||||
response = resolved.func(request, api=True, *resolved.args, **resolved.kwargs)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
@editor_api_router.post('/as_api/{path:path}', summary="raw editor access",
|
|
||||||
response={200: dict, **API404.dict(), **auth_permission_responses},
|
|
||||||
openapi_extra={"security": [{"APIKeyAuth": ["editor_access", "write"]}]})
|
|
||||||
@api_etag() # todo: correct?
|
|
||||||
def post_view_as_api(request, path: str):
|
|
||||||
"""
|
|
||||||
get editor views rendered as JSON instead of HTML.
|
|
||||||
`path` is the path after /editor/.
|
|
||||||
this is a mess. good luck. if you actually want to use this, poke us so we might add better documentation.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
@editor_api_router.get('/beacons-lookup/', summary="get beacon coordinates",
|
@editor_api_router.get('/beacons-lookup/', summary="get beacon coordinates",
|
||||||
description="get xyz coordinates for all known positioning beacons",
|
description="get xyz coordinates for all known positioning beacons",
|
||||||
response={200: EditorBeaconsLookup, **auth_permission_responses},
|
response={200: EditorBeaconsLookup, **auth_permission_responses},
|
||||||
|
|
|
@ -9,7 +9,6 @@ from django.contrib.auth.views import redirect_to_login
|
||||||
from django.contrib.messages import DEFAULT_TAGS as DEFAULT_MESSAGE_TAGS
|
from django.contrib.messages import DEFAULT_TAGS as DEFAULT_MESSAGE_TAGS
|
||||||
from django.contrib.messages import get_messages
|
from django.contrib.messages import get_messages
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.db.models import QuerySet
|
|
||||||
from django.http import HttpResponse, HttpResponseNotModified, HttpResponseRedirect
|
from django.http import HttpResponse, HttpResponseNotModified, HttpResponseRedirect
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
from django.utils.cache import patch_vary_headers
|
from django.utils.cache import patch_vary_headers
|
||||||
|
@ -100,34 +99,24 @@ def accesses_mapdata(func):
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
def sidebar_view(func=None, select_related=None, api_hybrid=False):
|
def sidebar_view(func=None, select_related=None):
|
||||||
if func is None:
|
if func is None:
|
||||||
def wrapped(inner_func):
|
def wrapped(inner_func):
|
||||||
return sidebar_view(inner_func, select_related=select_related, api_hybrid=api_hybrid)
|
return sidebar_view(inner_func, select_related=select_related)
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapped(request, *args, api=False, **kwargs):
|
def wrapped(request, *args, **kwargs):
|
||||||
if api and not api_hybrid:
|
|
||||||
raise Exception('API call on a view without api_hybrid!')
|
|
||||||
|
|
||||||
if not can_access_editor(request):
|
if not can_access_editor(request):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
if getattr(request, "changeset", None) is None:
|
if getattr(request, "changeset", None) is None:
|
||||||
request.changeset = ChangeSet.get_for_request(request, select_related)
|
request.changeset = ChangeSet.get_for_request(request, select_related)
|
||||||
|
|
||||||
if api:
|
|
||||||
request.is_delete = request.method == 'DELETE'
|
|
||||||
return call_api_hybrid_view_for_api(func, request, *args, **kwargs)
|
|
||||||
|
|
||||||
ajax = request.headers.get('x-requested-with') == 'XMLHttpRequest' or 'ajax' in request.GET
|
ajax = request.headers.get('x-requested-with') == 'XMLHttpRequest' or 'ajax' in request.GET
|
||||||
if not ajax:
|
if not ajax:
|
||||||
request.META.pop('HTTP_IF_NONE_MATCH', None)
|
request.META.pop('HTTP_IF_NONE_MATCH', None)
|
||||||
|
|
||||||
if api_hybrid:
|
|
||||||
response = call_api_hybrid_view_for_html(func, request, *args, **kwargs)
|
|
||||||
else:
|
|
||||||
response = func(request, *args, **kwargs)
|
response = func(request, *args, **kwargs)
|
||||||
|
|
||||||
if ajax:
|
if ajax:
|
||||||
|
@ -147,227 +136,9 @@ def sidebar_view(func=None, select_related=None, api_hybrid=False):
|
||||||
patch_vary_headers(response, ('X-Requested-With', ))
|
patch_vary_headers(response, ('X-Requested-With', ))
|
||||||
return response
|
return response
|
||||||
|
|
||||||
wrapped.api_hybrid = api_hybrid
|
|
||||||
|
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
class APIHybridResponse(ABC):
|
|
||||||
status_code = None
|
|
||||||
etag = None
|
|
||||||
last_modified = None
|
|
||||||
|
|
||||||
def has_header(self, header):
|
|
||||||
header = header.lower()
|
|
||||||
if header == 'etag':
|
|
||||||
return self.etag is not None
|
|
||||||
elif header == 'last-modified':
|
|
||||||
return self.last_modified is not None
|
|
||||||
else:
|
|
||||||
raise KeyError
|
|
||||||
|
|
||||||
def __setitem__(self, header, value):
|
|
||||||
header = header.lower()
|
|
||||||
if header == 'etag':
|
|
||||||
self.etag = value
|
|
||||||
elif header == 'last-modified':
|
|
||||||
self.last_modified = value
|
|
||||||
else:
|
|
||||||
raise KeyError
|
|
||||||
|
|
||||||
def setdefault(self, header, value):
|
|
||||||
if not self.has_header(header):
|
|
||||||
self[header] = value
|
|
||||||
|
|
||||||
def add_headers(self, response):
|
|
||||||
if self.etag is not None:
|
|
||||||
response['ETag'] = self.etag
|
|
||||||
if self.last_modified is not None:
|
|
||||||
response['Last-Modified'] = self.last_modified
|
|
||||||
return response
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_api_response(self, request):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_html_response(self, request):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class APIHybridMessageRedirectResponse(APIHybridResponse):
|
|
||||||
def __init__(self, level, message, redirect_to, status_code=None):
|
|
||||||
self.level = level
|
|
||||||
self.message = message
|
|
||||||
self.redirect_to = redirect_to
|
|
||||||
if self.level == 'error' and status_code is None:
|
|
||||||
raise Exception('Error with HTTP 200 makes no sense!')
|
|
||||||
self.status_code = status_code
|
|
||||||
|
|
||||||
def get_api_response(self, request):
|
|
||||||
return {self.level: self.message}
|
|
||||||
|
|
||||||
def get_html_response(self, request):
|
|
||||||
getattr(messages, self.level)(request, self.message)
|
|
||||||
return redirect(self.redirect_to)
|
|
||||||
|
|
||||||
|
|
||||||
class APIHybridLoginRequiredResponse(APIHybridResponse):
|
|
||||||
def __init__(self, next, login_url=None, level='error', message=_('Log in required.')):
|
|
||||||
self.login_url = login_url
|
|
||||||
self.next = next
|
|
||||||
self.level = level
|
|
||||||
self.message = message
|
|
||||||
|
|
||||||
def get_api_response(self, request):
|
|
||||||
return {self.level: self.message}
|
|
||||||
|
|
||||||
def get_html_response(self, request):
|
|
||||||
getattr(messages, self.level)(request, self.message)
|
|
||||||
return redirect_to_login(self.next, self.login_url)
|
|
||||||
|
|
||||||
|
|
||||||
class APIHybridError:
|
|
||||||
def __init__(self, status_code: int, message):
|
|
||||||
self.status_code = status_code
|
|
||||||
self.message = message
|
|
||||||
|
|
||||||
|
|
||||||
class APIHybridFormTemplateResponse(APIHybridResponse):
|
|
||||||
name_to_type_mapping = {
|
|
||||||
'geometry': 'geojson'
|
|
||||||
}
|
|
||||||
type_mapping = {
|
|
||||||
'TextInput': 'text',
|
|
||||||
'NumberInput': 'number',
|
|
||||||
'Textarea': 'text',
|
|
||||||
'CheckboxInput': 'boolean',
|
|
||||||
'Select': 'single_choice',
|
|
||||||
'SelectMultiple': 'multiple_choice',
|
|
||||||
'HiddenInput': 'hidden',
|
|
||||||
}
|
|
||||||
type_required_mapping = {
|
|
||||||
# name, inverted, only_required
|
|
||||||
'TextInput': ('allowed_empty', True, False),
|
|
||||||
'NumberInput': ('null_allowed', True, False),
|
|
||||||
'Textarea': ('allowed_empty', True, False),
|
|
||||||
'CheckboxInput': ('true_required', False, True),
|
|
||||||
'Select': ('choice_required', False, False),
|
|
||||||
'SelectMultiple': ('choice_required', False, False),
|
|
||||||
'HiddenInput': ('null_allowed', True, False),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, template: str, ctx: dict, form, error: Optional[APIHybridError]):
|
|
||||||
self.template = template
|
|
||||||
self.ctx = ctx
|
|
||||||
self.form = form
|
|
||||||
self.error = error
|
|
||||||
|
|
||||||
def get_api_response(self, request):
|
|
||||||
result = {}
|
|
||||||
if self.error:
|
|
||||||
result['error'] = self.error.message
|
|
||||||
self.status_code = self.error.status_code
|
|
||||||
if request.method == 'POST':
|
|
||||||
if not self.form.is_valid():
|
|
||||||
if self.status_code is None:
|
|
||||||
self.status_code = 400
|
|
||||||
result['form_errors'] = self.form.errors
|
|
||||||
else:
|
|
||||||
form = OrderedDict()
|
|
||||||
for name, field in self.form.fields.items():
|
|
||||||
widget = field.widget
|
|
||||||
required = field.required
|
|
||||||
field = {
|
|
||||||
'type': self.name_to_type_mapping.get(name, None) or self.type_mapping[type(widget).__name__],
|
|
||||||
}
|
|
||||||
required_name, required_invert, required_only_true = self.type_required_mapping[type(widget).__name__]
|
|
||||||
if not required_only_true or required:
|
|
||||||
field[required_name] = not required if required_invert else required
|
|
||||||
if hasattr(widget, 'choices'):
|
|
||||||
field['choices'] = dict(widget.choices)
|
|
||||||
if hasattr(widget, 'disabled'):
|
|
||||||
field['disabled'] = True
|
|
||||||
field.update(widget.attrs)
|
|
||||||
field.update({
|
|
||||||
'value': self.form[name].value(),
|
|
||||||
})
|
|
||||||
form[name] = field
|
|
||||||
result['form'] = form
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_html_response(self, request):
|
|
||||||
if self.error:
|
|
||||||
messages.error(request, self.error.message)
|
|
||||||
response = render(request, self.template, self.ctx)
|
|
||||||
return self.add_headers(response) if request.method == 'GET' else response
|
|
||||||
|
|
||||||
|
|
||||||
class APIHybridTemplateContextResponse(APIHybridResponse):
|
|
||||||
def __init__(self, template: str, ctx: dict, fields=None):
|
|
||||||
self.template = template
|
|
||||||
self.ctx = ctx
|
|
||||||
self.fields = fields
|
|
||||||
|
|
||||||
def _maybe_serialize_value(self, value):
|
|
||||||
if isinstance(value, SerializableMixin):
|
|
||||||
value = value.serialize(geometry=False, detailed=False)
|
|
||||||
elif isinstance(value, QuerySet) and issubclass(value.model, SerializableMixin):
|
|
||||||
value = [item.serialize(geometry=False, detailed=False) for item in value]
|
|
||||||
return value
|
|
||||||
|
|
||||||
def get_api_response(self, request):
|
|
||||||
result = self.ctx
|
|
||||||
if self.fields:
|
|
||||||
result = {name: self._maybe_serialize_value(value)
|
|
||||||
for name, value in result.items() if name in self.fields}
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_html_response(self, request):
|
|
||||||
response = render(request, self.template, self.ctx)
|
|
||||||
return self.add_headers(response) if request.method == 'GET' else response
|
|
||||||
|
|
||||||
|
|
||||||
class NoAPIHybridResponse(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def call_api_hybrid_view_for_api(func, request, *args, **kwargs):
|
|
||||||
response = func(request, *args, **kwargs)
|
|
||||||
if isinstance(response, APIHybridResponse):
|
|
||||||
result = OrderedDict(response.get_api_response(request))
|
|
||||||
|
|
||||||
messages = []
|
|
||||||
for message in get_messages(request):
|
|
||||||
messages.append({
|
|
||||||
'level': DEFAULT_MESSAGE_TAGS[message.level],
|
|
||||||
'message': message.message
|
|
||||||
})
|
|
||||||
if messages:
|
|
||||||
result['messages'] = messages
|
|
||||||
result.move_to_end('messages', last=False)
|
|
||||||
|
|
||||||
# todo: fix this
|
|
||||||
# api_response = APIResponse(result, status=response.status_code)
|
|
||||||
# if request.method == 'GET':
|
|
||||||
# response.add_headers(api_response)
|
|
||||||
# return api_response
|
|
||||||
elif isinstance(response, HttpResponse) and response.status_code in (304, 412):
|
|
||||||
# 304 Not Modified, 412 Precondition Failed
|
|
||||||
return response
|
|
||||||
raise NoAPIHybridResponse
|
|
||||||
|
|
||||||
|
|
||||||
def call_api_hybrid_view_for_html(func, request, *args, **kwargs):
|
|
||||||
response = func(request, *args, **kwargs)
|
|
||||||
if isinstance(response, APIHybridResponse):
|
|
||||||
return response.get_html_response(request)
|
|
||||||
elif isinstance(response, HttpResponse) and response.status_code in (304, 412):
|
|
||||||
# 304 Not Modified, 412 Precondition Failed
|
|
||||||
return response
|
|
||||||
raise NoAPIHybridResponse
|
|
||||||
|
|
||||||
|
|
||||||
def editor_etag_func(request, *args, **kwargs):
|
def editor_etag_func(request, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
changeset = request.changeset
|
changeset = request.changeset
|
||||||
|
|
|
@ -5,6 +5,7 @@ from contextlib import suppress
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth.views import redirect_to_login
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.core.exceptions import FieldDoesNotExist, PermissionDenied
|
from django.core.exceptions import FieldDoesNotExist, PermissionDenied
|
||||||
from django.db import IntegrityError, models
|
from django.db import IntegrityError, models
|
||||||
|
@ -18,9 +19,7 @@ from shapely import LineString
|
||||||
|
|
||||||
from c3nav.editor.forms import GraphEdgeSettingsForm, GraphEditorActionForm, get_editor_form, DoorGraphForm
|
from c3nav.editor.forms import GraphEdgeSettingsForm, GraphEditorActionForm, get_editor_form, DoorGraphForm
|
||||||
from c3nav.editor.utils import DefaultEditUtils, LevelChildEditUtils, SpaceChildEditUtils
|
from c3nav.editor.utils import DefaultEditUtils, LevelChildEditUtils, SpaceChildEditUtils
|
||||||
from c3nav.editor.views.base import (APIHybridError, APIHybridFormTemplateResponse, APIHybridLoginRequiredResponse,
|
from c3nav.editor.views.base import editor_etag_func, sidebar_view, accesses_mapdata
|
||||||
APIHybridMessageRedirectResponse, APIHybridTemplateContextResponse,
|
|
||||||
editor_etag_func, sidebar_view, accesses_mapdata)
|
|
||||||
from c3nav.mapdata.models import Level, Space, LocationGroupCategory, GraphNode, GraphEdge, Door
|
from c3nav.mapdata.models import Level, Space, LocationGroupCategory, GraphNode, GraphEdge, Door
|
||||||
from c3nav.mapdata.models.access import AccessPermission, AccessRestriction, AccessRestrictionGroup
|
from c3nav.mapdata.models.access import AccessPermission, AccessRestriction, AccessRestrictionGroup
|
||||||
from c3nav.mapdata.utils.geometry import unwrap_geom
|
from c3nav.mapdata.utils.geometry import unwrap_geom
|
||||||
|
@ -47,9 +46,9 @@ def child_model(request, model: typing.Union[str, models.Model], kwargs=None, pa
|
||||||
|
|
||||||
@etag(editor_etag_func)
|
@etag(editor_etag_func)
|
||||||
@accesses_mapdata
|
@accesses_mapdata
|
||||||
@sidebar_view(api_hybrid=True)
|
@sidebar_view
|
||||||
def main_index(request):
|
def main_index(request):
|
||||||
return APIHybridTemplateContextResponse('editor/index.html', {
|
return render(request, 'editor/index.html', {
|
||||||
'levels': Level.objects.filter(Level.q_for_request(request), on_top_of__isnull=True),
|
'levels': Level.objects.filter(Level.q_for_request(request), on_top_of__isnull=True),
|
||||||
'can_create_level': (request.user_permissions.can_access_base_mapdata and
|
'can_create_level': (request.user_permissions.can_access_base_mapdata and
|
||||||
request.changeset.can_edit(request)),
|
request.changeset.can_edit(request)),
|
||||||
|
@ -68,12 +67,12 @@ def main_index(request):
|
||||||
child_model(request, 'Theme'),
|
child_model(request, 'Theme'),
|
||||||
child_model(request, 'DataOverlay'),
|
child_model(request, 'DataOverlay'),
|
||||||
],
|
],
|
||||||
}, fields=('can_create_level', 'child_models'))
|
})
|
||||||
|
|
||||||
|
|
||||||
@etag(editor_etag_func)
|
@etag(editor_etag_func)
|
||||||
@accesses_mapdata
|
@accesses_mapdata
|
||||||
@sidebar_view(api_hybrid=True)
|
@sidebar_view
|
||||||
def level_detail(request, pk):
|
def level_detail(request, pk):
|
||||||
qs = Level.objects.filter(Level.q_for_request(request))
|
qs = Level.objects.filter(Level.q_for_request(request))
|
||||||
level = get_object_or_404(qs.select_related('on_top_of').prefetch_related('levels_on_top'), pk=pk)
|
level = get_object_or_404(qs.select_related('on_top_of').prefetch_related('levels_on_top'), pk=pk)
|
||||||
|
@ -83,7 +82,7 @@ def level_detail(request, pk):
|
||||||
else:
|
else:
|
||||||
submodels = ('Space', )
|
submodels = ('Space', )
|
||||||
|
|
||||||
return APIHybridTemplateContextResponse('editor/level.html', {
|
return render(request, 'editor/level.html', {
|
||||||
'levels': Level.objects.filter(Level.q_for_request(request), on_top_of__isnull=True),
|
'levels': Level.objects.filter(Level.q_for_request(request), on_top_of__isnull=True),
|
||||||
'level': level,
|
'level': level,
|
||||||
'level_url': 'editor.levels.detail',
|
'level_url': 'editor.levels.detail',
|
||||||
|
@ -97,12 +96,12 @@ def level_detail(request, pk):
|
||||||
'levels_on_top': level.levels_on_top.filter(Level.q_for_request(request)).all(),
|
'levels_on_top': level.levels_on_top.filter(Level.q_for_request(request)).all(),
|
||||||
'geometry_url': ('/api/v2/editor/geometries/level/'+str(level.primary_level_pk)
|
'geometry_url': ('/api/v2/editor/geometries/level/'+str(level.primary_level_pk)
|
||||||
if request.user_permissions.can_access_base_mapdata else None),
|
if request.user_permissions.can_access_base_mapdata else None),
|
||||||
}, fields=('level', 'can_edit_graph', 'can_create_level', 'child_models', 'levels_on_top'))
|
})
|
||||||
|
|
||||||
|
|
||||||
@etag(editor_etag_func)
|
@etag(editor_etag_func)
|
||||||
@accesses_mapdata
|
@accesses_mapdata
|
||||||
@sidebar_view(api_hybrid=True)
|
@sidebar_view
|
||||||
def space_detail(request, level, pk):
|
def space_detail(request, level, pk):
|
||||||
# todo: HOW TO GET DATA
|
# todo: HOW TO GET DATA
|
||||||
qs = Space.objects.filter(Space.q_for_request(request))
|
qs = Space.objects.filter(Space.q_for_request(request))
|
||||||
|
@ -117,7 +116,7 @@ def space_detail(request, level, pk):
|
||||||
else:
|
else:
|
||||||
submodels = ('POI', 'Area', 'AltitudeMarker', 'LeaveDescription', 'CrossDescription')
|
submodels = ('POI', 'Area', 'AltitudeMarker', 'LeaveDescription', 'CrossDescription')
|
||||||
|
|
||||||
return APIHybridTemplateContextResponse('editor/space.html', {
|
return render(request, 'editor/space.html', {
|
||||||
'levels': Level.objects.filter(Level.q_for_request(request), on_top_of__isnull=True),
|
'levels': Level.objects.filter(Level.q_for_request(request), on_top_of__isnull=True),
|
||||||
'level': space.level,
|
'level': space.level,
|
||||||
'level_url': 'editor.spaces.list',
|
'level_url': 'editor.spaces.list',
|
||||||
|
@ -127,7 +126,7 @@ def space_detail(request, level, pk):
|
||||||
'child_models': [child_model(request, model_name, kwargs={'space': pk}, parent=space)
|
'child_models': [child_model(request, model_name, kwargs={'space': pk}, parent=space)
|
||||||
for model_name in submodels],
|
for model_name in submodels],
|
||||||
'geometry_url': edit_utils.geometry_url,
|
'geometry_url': edit_utils.geometry_url,
|
||||||
}, fields=('level', 'space', 'can_edit_graph', 'child_models'))
|
})
|
||||||
|
|
||||||
|
|
||||||
def get_changeset_exceeded(request):
|
def get_changeset_exceeded(request):
|
||||||
|
@ -136,7 +135,7 @@ def get_changeset_exceeded(request):
|
||||||
|
|
||||||
@etag(editor_etag_func)
|
@etag(editor_etag_func)
|
||||||
@accesses_mapdata
|
@accesses_mapdata
|
||||||
@sidebar_view(api_hybrid=True)
|
@sidebar_view
|
||||||
def edit(request, pk=None, model=None, level=None, space=None, on_top_of=None, explicit_edit=False):
|
def edit(request, pk=None, model=None, level=None, space=None, on_top_of=None, explicit_edit=False):
|
||||||
if isinstance(model, str):
|
if isinstance(model, str):
|
||||||
model = apps.get_model(app_label="mapdata", model_name=model)
|
model = apps.get_model(app_label="mapdata", model_name=model)
|
||||||
|
@ -266,10 +265,8 @@ def edit(request, pk=None, model=None, level=None, space=None, on_top_of=None, e
|
||||||
nosave = False
|
nosave = False
|
||||||
if changeset_exceeded:
|
if changeset_exceeded:
|
||||||
if new:
|
if new:
|
||||||
return APIHybridMessageRedirectResponse(
|
messages.error(request, _('You can not create new objects because your changeset is full.'))
|
||||||
level='error', message=_('You can not create new objects because your changeset is full.'),
|
return redirect(ctx['back_url'])
|
||||||
redirect_to=ctx['back_url'], status_code=409,
|
|
||||||
)
|
|
||||||
elif obj.pk not in request.changeset.changes.objects.get(obj._meta.model_name, {}):
|
elif obj.pk not in request.changeset.changes.objects.get(obj._meta.model_name, {}):
|
||||||
messages.warning(request, _('You can not edit this object because your changeset is full.'))
|
messages.warning(request, _('You can not edit this object because your changeset is full.'))
|
||||||
nosave = True
|
nosave = True
|
||||||
|
@ -284,8 +281,8 @@ def edit(request, pk=None, model=None, level=None, space=None, on_top_of=None, e
|
||||||
})
|
})
|
||||||
|
|
||||||
if new and model.__name__ == 'BeaconMeasurement' and not request.user.is_authenticated:
|
if new and model.__name__ == 'BeaconMeasurement' and not request.user.is_authenticated:
|
||||||
return APIHybridLoginRequiredResponse(next=request.path_info, login_url='editor.login', level='info',
|
messages.info(request, _('You need to log in to create Beacon Measurements.'))
|
||||||
message=_('You need to log in to create Beacon Measurements.'))
|
return redirect_to_login(request.path_info, 'editor.login')
|
||||||
|
|
||||||
graph_form = None
|
graph_form = None
|
||||||
if model == Door and not new:
|
if model == Door and not new:
|
||||||
|
@ -316,16 +313,12 @@ def edit(request, pk=None, model=None, level=None, space=None, on_top_of=None, e
|
||||||
delete = getattr(request, 'is_delete', None)
|
delete = getattr(request, 'is_delete', None)
|
||||||
if request.method == 'POST' or (not new and delete):
|
if request.method == 'POST' or (not new and delete):
|
||||||
if nosave:
|
if nosave:
|
||||||
return APIHybridMessageRedirectResponse(
|
messages.error(request, _('You can not edit this object because your changeset is full.'))
|
||||||
level='error', message=_('You can not edit this object because your changeset is full.'),
|
return redirect(request.path)
|
||||||
redirect_to=request.path, status_code=409,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not can_edit_changeset:
|
if not can_edit_changeset:
|
||||||
return APIHybridMessageRedirectResponse(
|
messages.error(request, _('You can not edit changes on this changeset.'))
|
||||||
level='error', message=_('You can not edit changes on this changeset.'),
|
return redirect(request.path)
|
||||||
redirect_to=request.path, status_code=403,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not new and ((request.POST.get('delete') == '1' and delete is not False) or delete):
|
if not new and ((request.POST.get('delete') == '1' and delete is not False) or delete):
|
||||||
# Delete this mapitem!
|
# Delete this mapitem!
|
||||||
|
@ -333,11 +326,8 @@ def edit(request, pk=None, model=None, level=None, space=None, on_top_of=None, e
|
||||||
if request.changeset.can_edit(request): # todo: move this somewhere else
|
if request.changeset.can_edit(request): # todo: move this somewhere else
|
||||||
obj.delete()
|
obj.delete()
|
||||||
else:
|
else:
|
||||||
return APIHybridMessageRedirectResponse(
|
messages.error(request, _('You can not edit changes on this changeset.'))
|
||||||
level='error',
|
return redirect(request.path)
|
||||||
message=_('You can not edit changes on this changeset.'),
|
|
||||||
redirect_to=request.path, status_code=403,
|
|
||||||
)
|
|
||||||
|
|
||||||
if model == Level:
|
if model == Level:
|
||||||
if obj.on_top_of_id is not None:
|
if obj.on_top_of_id is not None:
|
||||||
|
@ -348,13 +338,10 @@ def edit(request, pk=None, model=None, level=None, space=None, on_top_of=None, e
|
||||||
redirect_to = reverse('editor.spaces.list', kwargs={'level': obj.level.pk})
|
redirect_to = reverse('editor.spaces.list', kwargs={'level': obj.level.pk})
|
||||||
else:
|
else:
|
||||||
redirect_to = ctx['back_url']
|
redirect_to = ctx['back_url']
|
||||||
return APIHybridMessageRedirectResponse(
|
messages.success(request, _('Object was successfully deleted.'))
|
||||||
level='success',
|
return redirect(redirect_to)
|
||||||
message=_('Object was successfully deleted.'),
|
|
||||||
redirect_to=redirect_to
|
|
||||||
)
|
|
||||||
ctx['obj_title'] = obj.title
|
ctx['obj_title'] = obj.title
|
||||||
return APIHybridTemplateContextResponse('editor/delete.html', ctx, fields=())
|
return render(request, 'editor/delete.html', ctx)
|
||||||
|
|
||||||
json_body = getattr(request, 'json_body', None)
|
json_body = getattr(request, 'json_body', None)
|
||||||
data = json_body if json_body is not None else request.POST
|
data = json_body if json_body is not None else request.POST
|
||||||
|
@ -383,7 +370,7 @@ def edit(request, pk=None, model=None, level=None, space=None, on_top_of=None, e
|
||||||
try:
|
try:
|
||||||
obj.save()
|
obj.save()
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
error = APIHybridError(status_code=400, message=_('Duplicate entry.'))
|
messages.error(request, _('Duplicate entry.'))
|
||||||
else:
|
else:
|
||||||
if form.redirect_slugs is not None:
|
if form.redirect_slugs is not None:
|
||||||
for slug in form.add_redirect_slugs:
|
for slug in form.add_redirect_slugs:
|
||||||
|
@ -394,13 +381,10 @@ def edit(request, pk=None, model=None, level=None, space=None, on_top_of=None, e
|
||||||
if graph_form is not None:
|
if graph_form is not None:
|
||||||
graph_form.save()
|
graph_form.save()
|
||||||
form.save_m2m()
|
form.save_m2m()
|
||||||
return APIHybridMessageRedirectResponse(
|
messages.success(request, _('Object was successfully saved.'))
|
||||||
level='success',
|
return redirect(ctx['back_url'])
|
||||||
message=_('Object was successfully saved.'),
|
|
||||||
redirect_to=ctx['back_url']
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
error = APIHybridError(status_code=403, message=_('You can not edit changes on this changeset.'))
|
messages.error(request, _('You can not edit changes on this changeset.'))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
form = get_editor_form(model)(instance=obj, request=request, space_id=space_id,
|
form = get_editor_form(model)(instance=obj, request=request, space_id=space_id,
|
||||||
|
@ -421,7 +405,7 @@ def edit(request, pk=None, model=None, level=None, space=None, on_top_of=None, e
|
||||||
"access_restriction_select": True,
|
"access_restriction_select": True,
|
||||||
})
|
})
|
||||||
|
|
||||||
return APIHybridFormTemplateResponse('editor/edit.html', ctx, form=form, error=error)
|
return render(request, 'editor/edit.html', ctx)
|
||||||
|
|
||||||
|
|
||||||
def get_visible_spaces(request):
|
def get_visible_spaces(request):
|
||||||
|
@ -448,7 +432,7 @@ def get_visible_spaces_kwargs(model, request):
|
||||||
|
|
||||||
@etag(editor_etag_func)
|
@etag(editor_etag_func)
|
||||||
@accesses_mapdata
|
@accesses_mapdata
|
||||||
@sidebar_view(api_hybrid=True)
|
@sidebar_view
|
||||||
def list_objects(request, model=None, level=None, space=None, explicit_edit=False):
|
def list_objects(request, model=None, level=None, space=None, explicit_edit=False):
|
||||||
if isinstance(model, str):
|
if isinstance(model, str):
|
||||||
model = apps.get_model(app_label="mapdata", model_name=model)
|
model = apps.get_model(app_label="mapdata", model_name=model)
|
||||||
|
@ -582,8 +566,7 @@ def list_objects(request, model=None, level=None, space=None, explicit_edit=Fals
|
||||||
"level_geometry_urls": True,
|
"level_geometry_urls": True,
|
||||||
})
|
})
|
||||||
|
|
||||||
return APIHybridTemplateContextResponse('editor/list.html', ctx,
|
return render(request, 'editor/list.html', ctx)
|
||||||
fields=('can_create', 'create_url', 'objects'))
|
|
||||||
|
|
||||||
|
|
||||||
def connect_nodes(request, active_node, clicked_node, edge_settings_form):
|
def connect_nodes(request, active_node, clicked_node, edge_settings_form):
|
||||||
|
|
|
@ -1,23 +1,21 @@
|
||||||
from c3nav.editor.forms import get_editor_form
|
from django.contrib import messages
|
||||||
from c3nav.editor.views.base import (APIHybridError, APIHybridFormTemplateResponse,
|
from django.core.exceptions import PermissionDenied
|
||||||
APIHybridMessageRedirectResponse, APIHybridTemplateContextResponse,
|
from django.db import IntegrityError
|
||||||
editor_etag_func, sidebar_view, accesses_mapdata)
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from django.views.decorators.http import etag
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
from django.views.decorators.http import etag
|
||||||
from django.contrib import messages
|
|
||||||
from django.db import IntegrityError
|
|
||||||
|
|
||||||
from c3nav.editor.utils import DefaultEditUtils, LevelChildEditUtils
|
from c3nav.editor.forms import get_editor_form
|
||||||
|
from c3nav.editor.utils import LevelChildEditUtils
|
||||||
|
from c3nav.editor.views.base import editor_etag_func, sidebar_view, accesses_mapdata
|
||||||
from c3nav.editor.views.edit import get_changeset_exceeded
|
from c3nav.editor.views.edit import get_changeset_exceeded
|
||||||
from c3nav.mapdata.models import DataOverlay, Level, DataOverlayFeature
|
from c3nav.mapdata.models import DataOverlay, Level, DataOverlayFeature
|
||||||
|
|
||||||
|
|
||||||
@etag(editor_etag_func)
|
@etag(editor_etag_func)
|
||||||
@accesses_mapdata
|
@accesses_mapdata
|
||||||
@sidebar_view(api_hybrid=True)
|
@sidebar_view
|
||||||
def overlays_list(request, level):
|
def overlays_list(request, level):
|
||||||
queryset = DataOverlay.objects.all().order_by('id')
|
queryset = DataOverlay.objects.all().order_by('id')
|
||||||
if hasattr(DataOverlay, 'q_for_request'):
|
if hasattr(DataOverlay, 'q_for_request'):
|
||||||
|
@ -34,11 +32,11 @@ def overlays_list(request, level):
|
||||||
'overlays': queryset,
|
'overlays': queryset,
|
||||||
}
|
}
|
||||||
|
|
||||||
return APIHybridTemplateContextResponse('editor/overlays.html', ctx, fields=('overlays',))
|
return render(request, 'editor/overlays.html', ctx)
|
||||||
|
|
||||||
@etag(editor_etag_func)
|
@etag(editor_etag_func)
|
||||||
@accesses_mapdata
|
@accesses_mapdata
|
||||||
@sidebar_view(api_hybrid=True)
|
@sidebar_view
|
||||||
def overlay_features(request, level, pk):
|
def overlay_features(request, level, pk):
|
||||||
ctx = {
|
ctx = {
|
||||||
'path': request.path,
|
'path': request.path,
|
||||||
|
@ -79,12 +77,11 @@ def overlay_features(request, level, pk):
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return APIHybridTemplateContextResponse('editor/overlay_features.html', ctx,
|
return render(request, 'editor/overlay_features.html', ctx)
|
||||||
fields=('can_create', 'create_url', 'objects'))
|
|
||||||
|
|
||||||
@etag(editor_etag_func)
|
@etag(editor_etag_func)
|
||||||
@accesses_mapdata
|
@accesses_mapdata
|
||||||
@sidebar_view(api_hybrid=True)
|
@sidebar_view
|
||||||
def overlay_feature_edit(request, level=None, overlay=None, pk=None):
|
def overlay_feature_edit(request, level=None, overlay=None, pk=None):
|
||||||
changeset_exceeded = get_changeset_exceeded(request)
|
changeset_exceeded = get_changeset_exceeded(request)
|
||||||
|
|
||||||
|
@ -142,10 +139,8 @@ def overlay_feature_edit(request, level=None, overlay=None, pk=None):
|
||||||
nosave = False
|
nosave = False
|
||||||
if changeset_exceeded:
|
if changeset_exceeded:
|
||||||
if new:
|
if new:
|
||||||
return APIHybridMessageRedirectResponse(
|
messages.error(request, _('You can not create new objects because your changeset is full.'))
|
||||||
level='error', message=_('You can not create new objects because your changeset is full.'),
|
return redirect(ctx['back_url'])
|
||||||
redirect_to=ctx['back_url'], status_code=409,
|
|
||||||
)
|
|
||||||
elif obj.pk not in request.changeset.changes.objects.get(obj._meta.model_name, {}):
|
elif obj.pk not in request.changeset.changes.objects.get(obj._meta.model_name, {}):
|
||||||
messages.warning(request, _('You can not edit this object because your changeset is full.'))
|
messages.warning(request, _('You can not edit this object because your changeset is full.'))
|
||||||
nosave = True
|
nosave = True
|
||||||
|
@ -164,16 +159,12 @@ def overlay_feature_edit(request, level=None, overlay=None, pk=None):
|
||||||
|
|
||||||
if request.method == 'POST' or (not new and delete):
|
if request.method == 'POST' or (not new and delete):
|
||||||
if nosave:
|
if nosave:
|
||||||
return APIHybridMessageRedirectResponse(
|
messages.error(request, _('You can not edit this object because your changeset is full.'))
|
||||||
level='error', message=_('You can not edit this object because your changeset is full.'),
|
return redirect(request.path)
|
||||||
redirect_to=request.path, status_code=409,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not can_edit_changeset:
|
if not can_edit_changeset:
|
||||||
return APIHybridMessageRedirectResponse(
|
messages.error(request, _('You can not edit changes on this changeset.'))
|
||||||
level='error', message=_('You can not edit changes on this changeset.'),
|
return redirect(request.path)
|
||||||
redirect_to=request.path, status_code=403,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not new and ((request.POST.get('delete') == '1' and delete is not False) or delete):
|
if not new and ((request.POST.get('delete') == '1' and delete is not False) or delete):
|
||||||
# Delete this mapitem!
|
# Delete this mapitem!
|
||||||
|
@ -182,20 +173,14 @@ def overlay_feature_edit(request, level=None, overlay=None, pk=None):
|
||||||
if changeset.can_edit(request):
|
if changeset.can_edit(request):
|
||||||
obj.delete()
|
obj.delete()
|
||||||
else:
|
else:
|
||||||
return APIHybridMessageRedirectResponse(
|
messages.error(request, _('You can not edit changes on this changeset.'))
|
||||||
level='error',
|
return redirect(request.path)
|
||||||
message=_('You can not edit changes on this changeset.'),
|
|
||||||
redirect_to=request.path, status_code=403,
|
|
||||||
)
|
|
||||||
|
|
||||||
redirect_to = ctx['back_url']
|
redirect_to = ctx['back_url']
|
||||||
return APIHybridMessageRedirectResponse(
|
messages.success(request, _('Object was successfully deleted.'))
|
||||||
level='success',
|
return redirect(redirect_to)
|
||||||
message=_('Object was successfully deleted.'),
|
|
||||||
redirect_to=redirect_to
|
|
||||||
)
|
|
||||||
ctx['obj_title'] = obj.title
|
ctx['obj_title'] = obj.title
|
||||||
return APIHybridTemplateContextResponse('editor/delete.html', ctx, fields=())
|
return render(request, 'editor/delete.html', ctx)
|
||||||
|
|
||||||
json_body = getattr(request, 'json_body', None)
|
json_body = getattr(request, 'json_body', None)
|
||||||
data = json_body if json_body is not None else request.POST
|
data = json_body if json_body is not None else request.POST
|
||||||
|
@ -214,7 +199,7 @@ def overlay_feature_edit(request, level=None, overlay=None, pk=None):
|
||||||
try:
|
try:
|
||||||
obj.save()
|
obj.save()
|
||||||
except IntegrityError as e:
|
except IntegrityError as e:
|
||||||
error = APIHybridError(status_code=400, message=_('Duplicate entry.'))
|
messages.error(request, _('Duplicate entry.'))
|
||||||
else:
|
else:
|
||||||
if form.redirect_slugs is not None:
|
if form.redirect_slugs is not None:
|
||||||
for slug in form.add_redirect_slugs:
|
for slug in form.add_redirect_slugs:
|
||||||
|
@ -224,13 +209,10 @@ def overlay_feature_edit(request, level=None, overlay=None, pk=None):
|
||||||
obj.redirects.filter(slug=slug).delete()
|
obj.redirects.filter(slug=slug).delete()
|
||||||
|
|
||||||
form.save_m2m()
|
form.save_m2m()
|
||||||
return APIHybridMessageRedirectResponse(
|
messages.success(request, _('Object was successfully saved.'))
|
||||||
level='success',
|
return redirect(ctx['back_url'])
|
||||||
message=_('Object was successfully saved.'),
|
|
||||||
redirect_to=ctx['back_url']
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
error = APIHybridError(status_code=403, message=_('You can not edit changes on this changeset.'))
|
messages.error(request, _('You can not edit changes on this changeset.'))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
form = get_editor_form(DataOverlayFeature)(instance=obj, request=request, space_id=space_id,
|
form = get_editor_form(DataOverlayFeature)(instance=obj, request=request, space_id=space_id,
|
||||||
|
@ -243,5 +225,5 @@ def overlay_feature_edit(request, level=None, overlay=None, pk=None):
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return APIHybridFormTemplateResponse('editor/edit.html', ctx, form=form, error=error)
|
return render(request, 'editor/edit.html', ctx)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue