editor API: activate, edit, propose, accept, etc…
This commit is contained in:
parent
671d138d03
commit
1594e09189
7 changed files with 181 additions and 13 deletions
18
src/c3nav/api/middleware.py
Normal file
18
src/c3nav/api/middleware.py
Normal 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)
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -227,6 +227,7 @@ MIDDLEWARE = [
|
|||
'c3nav.mapdata.middleware.UserDataMiddleware',
|
||||
'c3nav.site.middleware.MobileclientMiddleware',
|
||||
'c3nav.control.middleware.UserPermissionsMiddleware',
|
||||
'c3nav.api.middleware.JsonRequestBodyMiddleware',
|
||||
]
|
||||
|
||||
with suppress(ImportError):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue