diff --git a/src/c3nav/editor/api/endpoints.py b/src/c3nav/editor/api/endpoints.py index e9b95d3d..10734aae 100644 --- a/src/c3nav/editor/api/endpoints.py +++ b/src/c3nav/editor/api/endpoints.py @@ -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}, diff --git a/src/c3nav/editor/views/base.py b/src/c3nav/editor/views/base.py index 74612e65..2f436514 100644 --- a/src/c3nav/editor/views/base.py +++ b/src/c3nav/editor/views/base.py @@ -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 diff --git a/src/c3nav/editor/views/edit.py b/src/c3nav/editor/views/edit.py index f7ccff75..fc3060c0 100644 --- a/src/c3nav/editor/views/edit.py +++ b/src/c3nav/editor/views/edit.py @@ -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): diff --git a/src/c3nav/editor/views/overlays.py b/src/c3nav/editor/views/overlays.py index 129cfcbb..a96f1c6e 100644 --- a/src/c3nav/editor/views/overlays.py +++ b/src/c3nav/editor/views/overlays.py @@ -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)