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",
|
||||
description="get xyz coordinates for all known positioning beacons",
|
||||
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 get_messages
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import QuerySet
|
||||
from django.http import HttpResponse, HttpResponseNotModified, HttpResponseRedirect
|
||||
from django.shortcuts import redirect, render
|
||||
from django.utils.cache import patch_vary_headers
|
||||
|
@ -100,35 +99,25 @@ def accesses_mapdata(func):
|
|||
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:
|
||||
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
|
||||
|
||||
@wraps(func)
|
||||
def wrapped(request, *args, api=False, **kwargs):
|
||||
if api and not api_hybrid:
|
||||
raise Exception('API call on a view without api_hybrid!')
|
||||
|
||||
def wrapped(request, *args, **kwargs):
|
||||
if not can_access_editor(request):
|
||||
raise PermissionDenied
|
||||
|
||||
if getattr(request, "changeset", None) is None:
|
||||
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
|
||||
if not ajax:
|
||||
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 isinstance(response, HttpResponseRedirect):
|
||||
|
@ -147,227 +136,9 @@ def sidebar_view(func=None, select_related=None, api_hybrid=False):
|
|||
patch_vary_headers(response, ('X-Requested-With', ))
|
||||
return response
|
||||
|
||||
wrapped.api_hybrid = api_hybrid
|
||||
|
||||
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):
|
||||
try:
|
||||
changeset = request.changeset
|
||||
|
|
|
@ -5,6 +5,7 @@ from contextlib import suppress
|
|||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.views import redirect_to_login
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import FieldDoesNotExist, PermissionDenied
|
||||
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.utils import DefaultEditUtils, LevelChildEditUtils, SpaceChildEditUtils
|
||||
from c3nav.editor.views.base import (APIHybridError, APIHybridFormTemplateResponse, APIHybridLoginRequiredResponse,
|
||||
APIHybridMessageRedirectResponse, APIHybridTemplateContextResponse,
|
||||
editor_etag_func, sidebar_view, accesses_mapdata)
|
||||
from c3nav.editor.views.base import editor_etag_func, sidebar_view, accesses_mapdata
|
||||
from c3nav.mapdata.models import Level, Space, LocationGroupCategory, GraphNode, GraphEdge, Door
|
||||
from c3nav.mapdata.models.access import AccessPermission, AccessRestriction, AccessRestrictionGroup
|
||||
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)
|
||||
@accesses_mapdata
|
||||
@sidebar_view(api_hybrid=True)
|
||||
@sidebar_view
|
||||
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),
|
||||
'can_create_level': (request.user_permissions.can_access_base_mapdata and
|
||||
request.changeset.can_edit(request)),
|
||||
|
@ -68,12 +67,12 @@ def main_index(request):
|
|||
child_model(request, 'Theme'),
|
||||
child_model(request, 'DataOverlay'),
|
||||
],
|
||||
}, fields=('can_create_level', 'child_models'))
|
||||
})
|
||||
|
||||
|
||||
@etag(editor_etag_func)
|
||||
@accesses_mapdata
|
||||
@sidebar_view(api_hybrid=True)
|
||||
@sidebar_view
|
||||
def level_detail(request, pk):
|
||||
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)
|
||||
|
@ -83,7 +82,7 @@ def level_detail(request, pk):
|
|||
else:
|
||||
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),
|
||||
'level': level,
|
||||
'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(),
|
||||
'geometry_url': ('/api/v2/editor/geometries/level/'+str(level.primary_level_pk)
|
||||
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)
|
||||
@accesses_mapdata
|
||||
@sidebar_view(api_hybrid=True)
|
||||
@sidebar_view
|
||||
def space_detail(request, level, pk):
|
||||
# todo: HOW TO GET DATA
|
||||
qs = Space.objects.filter(Space.q_for_request(request))
|
||||
|
@ -117,7 +116,7 @@ def space_detail(request, level, pk):
|
|||
else:
|
||||
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),
|
||||
'level': space.level,
|
||||
'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)
|
||||
for model_name in submodels],
|
||||
'geometry_url': edit_utils.geometry_url,
|
||||
}, fields=('level', 'space', 'can_edit_graph', 'child_models'))
|
||||
})
|
||||
|
||||
|
||||
def get_changeset_exceeded(request):
|
||||
|
@ -136,7 +135,7 @@ def get_changeset_exceeded(request):
|
|||
|
||||
@etag(editor_etag_func)
|
||||
@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):
|
||||
if isinstance(model, str):
|
||||
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
|
||||
if changeset_exceeded:
|
||||
if new:
|
||||
return APIHybridMessageRedirectResponse(
|
||||
level='error', message=_('You can not create new objects because your changeset is full.'),
|
||||
redirect_to=ctx['back_url'], status_code=409,
|
||||
)
|
||||
messages.error(request, _('You can not create new objects because your changeset is full.'))
|
||||
return redirect(ctx['back_url'])
|
||||
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.'))
|
||||
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:
|
||||
return APIHybridLoginRequiredResponse(next=request.path_info, login_url='editor.login', level='info',
|
||||
message=_('You need to log in to create Beacon Measurements.'))
|
||||
messages.info(request, _('You need to log in to create Beacon Measurements.'))
|
||||
return redirect_to_login(request.path_info, 'editor.login')
|
||||
|
||||
graph_form = None
|
||||
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)
|
||||
if request.method == 'POST' or (not new and delete):
|
||||
if nosave:
|
||||
return APIHybridMessageRedirectResponse(
|
||||
level='error', message=_('You can not edit this object because your changeset is full.'),
|
||||
redirect_to=request.path, status_code=409,
|
||||
)
|
||||
messages.error(request, _('You can not edit this object because your changeset is full.'))
|
||||
return redirect(request.path)
|
||||
|
||||
if not can_edit_changeset:
|
||||
return APIHybridMessageRedirectResponse(
|
||||
level='error', message=_('You can not edit changes on this changeset.'),
|
||||
redirect_to=request.path, status_code=403,
|
||||
)
|
||||
messages.error(request, _('You can not edit changes on this changeset.'))
|
||||
return redirect(request.path)
|
||||
|
||||
if not new and ((request.POST.get('delete') == '1' and delete is not False) or delete):
|
||||
# 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
|
||||
obj.delete()
|
||||
else:
|
||||
return APIHybridMessageRedirectResponse(
|
||||
level='error',
|
||||
message=_('You can not edit changes on this changeset.'),
|
||||
redirect_to=request.path, status_code=403,
|
||||
)
|
||||
messages.error(request, _('You can not edit changes on this changeset.'))
|
||||
return redirect(request.path)
|
||||
|
||||
if model == Level:
|
||||
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})
|
||||
else:
|
||||
redirect_to = ctx['back_url']
|
||||
return APIHybridMessageRedirectResponse(
|
||||
level='success',
|
||||
message=_('Object was successfully deleted.'),
|
||||
redirect_to=redirect_to
|
||||
)
|
||||
messages.success(request, _('Object was successfully deleted.'))
|
||||
return redirect(redirect_to)
|
||||
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)
|
||||
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:
|
||||
obj.save()
|
||||
except IntegrityError:
|
||||
error = APIHybridError(status_code=400, message=_('Duplicate entry.'))
|
||||
messages.error(request, _('Duplicate entry.'))
|
||||
else:
|
||||
if form.redirect_slugs is not None:
|
||||
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:
|
||||
graph_form.save()
|
||||
form.save_m2m()
|
||||
return APIHybridMessageRedirectResponse(
|
||||
level='success',
|
||||
message=_('Object was successfully saved.'),
|
||||
redirect_to=ctx['back_url']
|
||||
)
|
||||
messages.success(request, _('Object was successfully saved.'))
|
||||
return redirect(ctx['back_url'])
|
||||
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:
|
||||
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,
|
||||
})
|
||||
|
||||
return APIHybridFormTemplateResponse('editor/edit.html', ctx, form=form, error=error)
|
||||
return render(request, 'editor/edit.html', ctx)
|
||||
|
||||
|
||||
def get_visible_spaces(request):
|
||||
|
@ -448,7 +432,7 @@ def get_visible_spaces_kwargs(model, request):
|
|||
|
||||
@etag(editor_etag_func)
|
||||
@accesses_mapdata
|
||||
@sidebar_view(api_hybrid=True)
|
||||
@sidebar_view
|
||||
def list_objects(request, model=None, level=None, space=None, explicit_edit=False):
|
||||
if isinstance(model, str):
|
||||
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,
|
||||
})
|
||||
|
||||
return APIHybridTemplateContextResponse('editor/list.html', ctx,
|
||||
fields=('can_create', 'create_url', 'objects'))
|
||||
return render(request, 'editor/list.html', ctx)
|
||||
|
||||
|
||||
def connect_nodes(request, active_node, clicked_node, edge_settings_form):
|
||||
|
|
|
@ -1,23 +1,21 @@
|
|||
from c3nav.editor.forms import get_editor_form
|
||||
from c3nav.editor.views.base import (APIHybridError, APIHybridFormTemplateResponse,
|
||||
APIHybridMessageRedirectResponse, APIHybridTemplateContextResponse,
|
||||
editor_etag_func, sidebar_view, accesses_mapdata)
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.views.decorators.http import etag
|
||||
from django.contrib import messages
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import IntegrityError
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
||||
from django.contrib import messages
|
||||
from django.db import IntegrityError
|
||||
from django.views.decorators.http import etag
|
||||
|
||||
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.mapdata.models import DataOverlay, Level, DataOverlayFeature
|
||||
|
||||
|
||||
@etag(editor_etag_func)
|
||||
@accesses_mapdata
|
||||
@sidebar_view(api_hybrid=True)
|
||||
@sidebar_view
|
||||
def overlays_list(request, level):
|
||||
queryset = DataOverlay.objects.all().order_by('id')
|
||||
if hasattr(DataOverlay, 'q_for_request'):
|
||||
|
@ -34,11 +32,11 @@ def overlays_list(request, level):
|
|||
'overlays': queryset,
|
||||
}
|
||||
|
||||
return APIHybridTemplateContextResponse('editor/overlays.html', ctx, fields=('overlays',))
|
||||
return render(request, 'editor/overlays.html', ctx)
|
||||
|
||||
@etag(editor_etag_func)
|
||||
@accesses_mapdata
|
||||
@sidebar_view(api_hybrid=True)
|
||||
@sidebar_view
|
||||
def overlay_features(request, level, pk):
|
||||
ctx = {
|
||||
'path': request.path,
|
||||
|
@ -79,12 +77,11 @@ def overlay_features(request, level, pk):
|
|||
},
|
||||
})
|
||||
|
||||
return APIHybridTemplateContextResponse('editor/overlay_features.html', ctx,
|
||||
fields=('can_create', 'create_url', 'objects'))
|
||||
return render(request, 'editor/overlay_features.html', ctx)
|
||||
|
||||
@etag(editor_etag_func)
|
||||
@accesses_mapdata
|
||||
@sidebar_view(api_hybrid=True)
|
||||
@sidebar_view
|
||||
def overlay_feature_edit(request, level=None, overlay=None, pk=None):
|
||||
changeset_exceeded = get_changeset_exceeded(request)
|
||||
|
||||
|
@ -142,10 +139,8 @@ def overlay_feature_edit(request, level=None, overlay=None, pk=None):
|
|||
nosave = False
|
||||
if changeset_exceeded:
|
||||
if new:
|
||||
return APIHybridMessageRedirectResponse(
|
||||
level='error', message=_('You can not create new objects because your changeset is full.'),
|
||||
redirect_to=ctx['back_url'], status_code=409,
|
||||
)
|
||||
messages.error(request, _('You can not create new objects because your changeset is full.'))
|
||||
return redirect(ctx['back_url'])
|
||||
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.'))
|
||||
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 nosave:
|
||||
return APIHybridMessageRedirectResponse(
|
||||
level='error', message=_('You can not edit this object because your changeset is full.'),
|
||||
redirect_to=request.path, status_code=409,
|
||||
)
|
||||
messages.error(request, _('You can not edit this object because your changeset is full.'))
|
||||
return redirect(request.path)
|
||||
|
||||
if not can_edit_changeset:
|
||||
return APIHybridMessageRedirectResponse(
|
||||
level='error', message=_('You can not edit changes on this changeset.'),
|
||||
redirect_to=request.path, status_code=403,
|
||||
)
|
||||
messages.error(request, _('You can not edit changes on this changeset.'))
|
||||
return redirect(request.path)
|
||||
|
||||
if not new and ((request.POST.get('delete') == '1' and delete is not False) or delete):
|
||||
# Delete this mapitem!
|
||||
|
@ -182,20 +173,14 @@ def overlay_feature_edit(request, level=None, overlay=None, pk=None):
|
|||
if changeset.can_edit(request):
|
||||
obj.delete()
|
||||
else:
|
||||
return APIHybridMessageRedirectResponse(
|
||||
level='error',
|
||||
message=_('You can not edit changes on this changeset.'),
|
||||
redirect_to=request.path, status_code=403,
|
||||
)
|
||||
messages.error(request, _('You can not edit changes on this changeset.'))
|
||||
return redirect(request.path)
|
||||
|
||||
redirect_to = ctx['back_url']
|
||||
return APIHybridMessageRedirectResponse(
|
||||
level='success',
|
||||
message=_('Object was successfully deleted.'),
|
||||
redirect_to=redirect_to
|
||||
)
|
||||
messages.success(request, _('Object was successfully deleted.'))
|
||||
return redirect(redirect_to)
|
||||
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)
|
||||
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:
|
||||
obj.save()
|
||||
except IntegrityError as e:
|
||||
error = APIHybridError(status_code=400, message=_('Duplicate entry.'))
|
||||
messages.error(request, _('Duplicate entry.'))
|
||||
else:
|
||||
if form.redirect_slugs is not None:
|
||||
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()
|
||||
|
||||
form.save_m2m()
|
||||
return APIHybridMessageRedirectResponse(
|
||||
level='success',
|
||||
message=_('Object was successfully saved.'),
|
||||
redirect_to=ctx['back_url']
|
||||
)
|
||||
messages.success(request, _('Object was successfully saved.'))
|
||||
return redirect(ctx['back_url'])
|
||||
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:
|
||||
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