diff --git a/src/c3nav/editor/hosters/base.py b/src/c3nav/editor/hosters/base.py index 4e4b12ba..aafe5e14 100644 --- a/src/c3nav/editor/hosters/base.py +++ b/src/c3nav/editor/hosters/base.py @@ -5,7 +5,7 @@ from django.conf import settings from django.urls.base import reverse from django.utils.translation import ugettext_lazy as _ -from c3nav.editor.tasks import check_access_token, request_access_token +from c3nav.editor.tasks import check_access_token, request_access_token, submit_edit from c3nav.mapdata.models import Package @@ -114,6 +114,11 @@ class Hoster(ABC): session_data['checking_progress_id'] = task.id self._handle_checking_task(request, task, session_data) + def submit_edit(self, request, data): + session_data = self._get_session_data(request) + task = submit_edit.apply_async(access_token=session_data['access_token'], data=data) + return task + @abstractmethod def get_auth_uri(self, request): """ @@ -133,7 +138,7 @@ class Hoster(ABC): """ Task method for requesting the access token asynchroniously. Returns a dict with a 'state' key containing the new hoster state, an optional 'error' key containing an - error message and an optional 'access_token' keys containing a new access token. + error message and an optional 'access_token' key containing a new access token. """ pass @@ -144,3 +149,13 @@ class Hoster(ABC): Returns a dict with a 'state' key containing the new hoster state. """ pass + + @abstractmethod + def do_submit_edit(self, access_token, data): + """ + Task method for submitting an edit (e.g. creating a pull request). + + Returns a dict with a 'success' key that contains a boolean, an optional 'error' key containing an error + message and an optional 'url' key containing an URL to the created pull request. + """ + pass diff --git a/src/c3nav/editor/hosters/github.py b/src/c3nav/editor/hosters/github.py index 41b332b6..579b4e32 100644 --- a/src/c3nav/editor/hosters/github.py +++ b/src/c3nav/editor/hosters/github.py @@ -80,3 +80,6 @@ class GithubHoster(Hoster): return {'state': 'missing_permissions'} return {'state': 'logged_in'} + + def do_submit_edit(self, access_token, data): + raise NotImplementedError diff --git a/src/c3nav/editor/hosters/gitlab.py b/src/c3nav/editor/hosters/gitlab.py index d414cb1f..2ebd1947 100644 --- a/src/c3nav/editor/hosters/gitlab.py +++ b/src/c3nav/editor/hosters/gitlab.py @@ -75,3 +75,6 @@ class GitlabHoster(Hoster): return {'state': 'logged_out'} return {'state': 'logged_in'} + + def do_submit_edit(self, access_token, data): + raise NotImplementedError diff --git a/src/c3nav/editor/tasks.py b/src/c3nav/editor/tasks.py index 634c62d2..278f6da4 100644 --- a/src/c3nav/editor/tasks.py +++ b/src/c3nav/editor/tasks.py @@ -11,3 +11,9 @@ def request_access_token(hoster, *args, **kwargs): def check_access_token(hoster, access_token): from c3nav.editor.hosters import hosters return hosters[hoster].do_check_access_token(access_token) + + +@app.task() +def submit_edit(hoster, access_token, data): + from c3nav.editor.hosters import hosters + return hosters[hoster].do_submit_edit(access_token, data) diff --git a/src/c3nav/editor/templates/editor/finalize.html b/src/c3nav/editor/templates/editor/finalize.html index 083b1ada..87dda68b 100644 --- a/src/c3nav/editor/templates/editor/finalize.html +++ b/src/c3nav/editor/templates/editor/finalize.html @@ -3,58 +3,78 @@ {% load bootstrap3 %} {% block content %} {% if hoster %} - {% if hoster_error %} -
{{ hoster_error }}
-Please provide a short helpful title for your change.
- - - {% elif hoster_state == 'checking' %} -c3nav is missing permissions that it needs to propose your edit.
-Please click the button below to grant the missing permissions.
- {% else %} -Please sign in to continue and propose your edit.
+ {% if hoster_error %} +{{ hoster_error }}
+Please provide a short helpful title for your change.
+ + + {% elif hoster_state == 'checking' %} +c3nav is missing permissions that it needs to propose your edit.
+Please click the button below to grant the missing permissions.
+ {% else %} +Please sign in to continue and propose your edit.
+ {% endif %} + + {% endif %} +Alternatively, you can copy your edit below and send it to the maps maintainer.
+ {% else %} + {% if not task.ready or redirect %} +You can find it here:
{% endif %} - {% endif %} -Alternatively, you can copy your edit below and send it to the maps maintainer.
{% else %}In order to propose your edit, please copy it and send it to the maps maintainer.
diff --git a/src/c3nav/editor/views.py b/src/c3nav/editor/views.py index e1aa214e..9dd7c584 100644 --- a/src/c3nav/editor/views.py +++ b/src/c3nav/editor/views.py @@ -1,12 +1,16 @@ +import string + from django.conf import settings from django.core import signing from django.core.exceptions import PermissionDenied, SuspiciousOperation from django.http.response import Http404 from django.shortcuts import get_object_or_404, redirect, render +from django.utils.crypto import get_random_string from django.views.decorators.http import require_POST from c3nav.editor.forms import CommitForm, FeatureForm from c3nav.editor.hosters import get_hoster_for_package, hosters +from c3nav.editor.tasks import submit_edit from c3nav.mapdata.models.feature import FEATURE_TYPES, Feature from c3nav.mapdata.models.package import Package from c3nav.mapdata.packageio.write import json_encode @@ -112,22 +116,41 @@ def finalize(request): hoster = get_hoster_for_package(package) action = request.POST.get('action') + + if 'commit_msg' in request.POST or action == 'submit': + form = CommitForm(request.POST) + else: + form = CommitForm({'commit_msg': data['commit_msg']}) + + task = None + new_submit_token = False if action == 'check': hoster.check_state(request) elif action == 'oauth': hoster.set_tmp_data(request, raw_data) return redirect(hoster.get_auth_uri(request)) + elif action == 'submit' and hoster.get_state(request) == 'logged_in': + if request.POST.get('editor_submit_token', '') != request.session.get('editor_submit_token', None): + raise SuspiciousOperation('Invalid submit token.') + if form.is_valid(): + new_submit_token = True + data['commit_msg'] = form.cleaned_data['commit_msg'] + task = hoster.submit_edit(request, data) + elif action == 'result': + if 'task' not in request.POST: + raise SuspiciousOperation('Missing task id.') + task = submit_edit.AsyncResult(task_id=request.POST['task']) + try: + task.ready() + except: + raise Http404() + + if 'editor_submit_token' not in request.session or new_submit_token: + request.session['editor_submit_token'] = get_random_string(42, string.ascii_letters + string.digits) hoster_state = hoster.get_state(request) hoster_error = hoster.get_error(request) if hoster_state == 'logged_out' else None - if request.method == 'POST' and 'commit_msg' in request.POST: - form = CommitForm(request.POST) - if form.is_valid() and hoster_state == 'logged_in': - pass - else: - form = CommitForm({'commit_msg': data['commit_msg']}) - return render(request, 'editor/finalize.html', { 'data': raw_data, 'action': data['action'], @@ -137,6 +160,9 @@ def finalize(request): 'hoster': hoster, 'hoster_state': hoster_state, 'hoster_error': hoster_error, + 'redirect': action == 'submit' and not settings.CELERY_ALWAYS_EAGER, + 'editor_submit_token': request.session['editor_submit_token'], + 'task': {'id': task.id, 'ready': task.ready(), 'result': task.result} if task is not None else None, 'file_path': data['file_path'], 'file_contents': data.get('content') })