editor API: activate, edit, propose, accept, etc…

This commit is contained in:
Laura Klünder 2018-11-29 01:30:53 +01:00
parent 671d138d03
commit 1594e09189
7 changed files with 181 additions and 13 deletions

View file

@ -0,0 +1,18 @@
import json
class JsonRequestBodyMiddleware:
"""
Enables posting JSON requests.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
is_json = request.META.get('CONTENT_TYPE').lower() == 'application/json'
if is_json:
try:
request.json_body = json.loads(request.body)
except json.JSONDecodeError:
pass
return self.get_response(request)

View file

@ -1,5 +1,3 @@
import json
from rest_framework.exceptions import ParseError
@ -7,10 +5,8 @@ def get_api_post_data(request):
is_json = request.META.get('CONTENT_TYPE').lower() == 'application/json'
if is_json:
try:
data = json.loads(request.body)
except json.JSONDecodeError:
data = request.json_body
except AttributeError:
raise ParseError('Invalid JSON.')
else:
request.json_body = data
return data
return request.POST

View file

@ -6,13 +6,14 @@ from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from rest_framework.authentication import SessionAuthentication
from rest_framework.decorators import action
from rest_framework.exceptions import NotFound, PermissionDenied, ValidationError
from rest_framework.exceptions import NotFound, ParseError, PermissionDenied, ValidationError
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
from shapely.ops import cascaded_union
from c3nav.api.utils import get_api_post_data
from c3nav.editor.forms import ChangeSetForm, RejectForm
from c3nav.editor.models import ChangeSet
from c3nav.editor.views.base import etag_func
from c3nav.mapdata.api import api_etag
@ -328,21 +329,62 @@ class EditorViewSet(ViewSet):
class ChangeSetViewSet(ReadOnlyModelViewSet):
"""
List change sets
List and manipulate changesets. All lists are ordered by last update descending. Use ?offset= to specify an offset.
Don't forget to set X-Csrftoken for POST requests!
/ lists all changesets this user can see.
/user/ lists changesets by this user
/reviewing/ lists changesets this user is currently reviewing.
/pending_review/ lists changesets this user can review.
/current/ returns the current changeset.
/direct_editing/ POST to activate direct editing (if available).
/deactive/ POST to deactivate current changeset or deactivate direct editing
/{id}/changes/ returns the changes of a given changeset.
/{id}/changes/ list all changes of a given changeset.
/{id}/activate/ POST to activate given changeset.
/{id}/edit/ POST to edit given changeset (provide title and description in POST data).
/{id}/delete/ POST to delete given changeset.
/{id}/propose/ POST to propose given changeset.
/{id}/unpropose/ POST to unpropose given changeset.
/{id}/review/ POST to review given changeset.
/{id}/reject/ POST to reject given changeset (provide reject=1 in POST data for final rejection).
/{id}/unreject/ POST to unreject given changeset.
/{id}/apply/ POST to accept and apply given changeset.
"""
queryset = ChangeSet.objects.all()
def get_queryset(self):
return ChangeSet.qs_for_request(self.request).select_related('last_update', 'last_state_update', 'last_change')
def list(self, request, *args, **kwargs):
def _list(self, request, qs):
if not can_access_editor(request):
raise PermissionDenied
return Response([obj.serialize() for obj in self.get_queryset().order_by('id')])
offset = 0
if 'offset' in request.GET:
if not request.GET['offset'].isdigit():
raise ParseError('Offset has to be a positive integer.')
offset = int(request.GET['offset'])
return Response([obj.serialize() for obj in qs.order_by('-last_update')[offset:offset+20]])
def list(self, request, *args, **kwargs):
return self._list(request, self.get_queryset())
@action(detail=False, methods=['get'])
def user(self, request, *args, **kwargs):
return self._list(request, self.get_queryset().filter(author=request.user))
@action(detail=False, methods=['get'])
def reviewing(self, request, *args, **kwargs):
return self._list(request, self.get_queryset().filter(
assigned_to=request.user, state='review'
))
@action(detail=False, methods=['get'])
def pending_review(self, request, *args, **kwargs):
return self._list(request, self.get_queryset().filter(
state__in=('proposed', 'reproposed'),
))
def retrieve(self, request, *args, **kwargs):
if not can_access_editor(request):
@ -401,3 +443,111 @@ class ChangeSetViewSet(ReadOnlyModelViewSet):
changeset = self.get_object()
changeset.fill_changes_cache()
return Response([obj.serialize() for obj in changeset.iter_changed_objects()])
@action(detail=True, methods=['post'])
def activate(self, request, *args, **kwargs):
changeset = self.get_object()
with changeset.lock_to_edit(request) as changeset:
if not changeset.can_activate(request):
raise PermissionDenied(_('You can not activate this change set.'))
changeset.activate(request)
return Response({'success': True})
@action(detail=True, methods=['post'])
def edit(self, request, *args, **kwargs):
changeset = self.get_object()
with changeset.lock_to_edit(request) as changeset:
if not changeset.can_edit(request):
raise PermissionDenied(_('You cannot edit this change set.'))
form = ChangeSetForm(instance=changeset, data=get_api_post_data(request))
if not form.is_valid():
raise ParseError(form.errors)
changeset = form.instance
update = changeset.updates.create(user=request.user,
title=changeset.title, description=changeset.description)
changeset.last_update = update
changeset.save()
return Response({'success': True})
@action(detail=True, methods=['post'])
def propose(self, request, *args, **kwargs):
if not request.user.is_authenticated:
raise PermissionDenied(_('You need to log in to propose changes.'))
changeset = self.get_object()
with changeset.lock_to_edit(request) as changeset:
if not changeset.title or not changeset.description:
raise PermissionDenied(_('You need to add a title an a description to propose this change set.'))
if not changeset.can_propose(request):
raise PermissionDenied(_('You cannot propose this change set.'))
changeset.propose(request.user)
return Response({'success': True})
@action(detail=True, methods=['post'])
def unpropose(self, request, *args, **kwargs):
changeset = self.get_object()
with changeset.lock_to_edit(request) as changeset:
if not changeset.can_unpropose(request):
raise PermissionDenied(_('You cannot unpropose this change set.'))
changeset.unpropose(request.user)
return Response({'success': True})
@action(detail=True, methods=['post'])
def review(self, request, *args, **kwargs):
changeset = self.get_object()
with changeset.lock_to_edit(request) as changeset:
if not changeset.can_start_review(request):
raise PermissionDenied(_('You cannot review these changes.'))
changeset.start_review(request.user)
return Response({'success': True})
@action(detail=True, methods=['post'])
def reject(self, request, *args, **kwargs):
changeset = self.get_object()
with changeset.lock_to_edit(request) as changeset:
if not not changeset.can_end_review(request):
raise PermissionDenied(_('You cannot reject these changes.'))
form = RejectForm(get_api_post_data(request))
if not form.is_valid():
raise ParseError(form.errors)
changeset.reject(request.user, form.cleaned_data['comment'], form.cleaned_data['final'])
return Response({'success': True})
@action(detail=True, methods=['post'])
def unreject(self, request, *args, **kwargs):
changeset = self.get_object()
with changeset.lock_to_edit(request) as changeset:
if not changeset.can_unreject(request):
raise PermissionDenied(_('You cannot unreject these changes.'))
changeset.unreject(request.user)
return Response({'success': True})
@action(detail=True, methods=['post'])
def apply(self, request, *args, **kwargs):
changeset = self.get_object()
with changeset.lock_to_edit(request) as changeset:
if not changeset.can_end_review(request):
raise PermissionDenied(_('You cannot accept and apply these changes.'))
changeset.apply(request.user)
return Response({'success': True})
@action(detail=True, methods=['post'])
def delete(self, request, *args, **kwargs):
changeset = self.get_object()
with changeset.lock_to_edit(request) as changeset:
if not changeset.can_delete(request):
raise PermissionDenied(_('You cannot delete this change set.'))
changeset.delete()
return Response({'success': True})

View file

@ -488,6 +488,9 @@ class ChangeSet(models.Model):
return self.assigned_to == request.user
return False
def can_activate(self, request):
return not self.closed and self.can_edit(request)
def can_delete(self, request):
return self.can_edit(request) and self.state == 'unproposed'

View file

@ -59,7 +59,7 @@ def changeset_detail(request, pk):
elif request.POST.get('activate') == '1':
with changeset.lock_to_edit(request) as changeset:
if not changeset.closed and changeset.can_edit:
if changeset.can_activate(request):
changeset.activate(request)
messages.success(request, _('You activated this change set.'))
else:

View file

@ -34,7 +34,7 @@ def user_detail(request, pk):
ctx = {
'user': user,
'can_direct_edit': ChangeSet.can_direct_edit(request),
'recent_changesets': ChangeSet.objects.filter(author=user).order_by('-last_update')[:15],
'recent_changesets': ChangeSet.qs_for_request(request).filter(author=user).order_by('-last_update')[:15],
}
if request.user_permissions.review_changesets:

View file

@ -227,6 +227,7 @@ MIDDLEWARE = [
'c3nav.mapdata.middleware.UserDataMiddleware',
'c3nav.site.middleware.MobileclientMiddleware',
'c3nav.control.middleware.UserPermissionsMiddleware',
'c3nav.api.middleware.JsonRequestBodyMiddleware',
]
with suppress(ImportError):