[damned-lies/api_reserve_upload] Allowed reserve/upload translation by API




commit aa8ce42e6d755438806e21d4cf5c66ca9d1b0c30
Author: Claude Paroz <claude 2xlibre net>
Date:   Sat Jan 30 23:16:25 2021 +0100

    Allowed reserve/upload translation by API
    
    Thanks Amanda Shafack for the initial patch.

 api/tests.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 api/urls.py  | 15 +++++++++++----
 api/views.py | 49 ++++++++++++++++++++++++++++++++++++++++++-------
 3 files changed, 105 insertions(+), 11 deletions(-)
---
diff --git a/api/tests.py b/api/tests.py
index fb7d67de..5997098b 100644
--- a/api/tests.py
+++ b/api/tests.py
@@ -1,6 +1,16 @@
+from pathlib import Path
+
+from django.core.files import File
 from django.test import TestCase
 from django.urls import reverse
 
+from languages.models import Language
+from people.models import Person
+from stats.models import Branch, Domain
+from teams.models import Role, Team
+from vertimus.models import State, StateTranslating
+from vertimus.views import get_vertimus_state
+
 
 class APITests(TestCase):
     fixtures = ['sample_data.json']
@@ -93,3 +103,45 @@ class APITests(TestCase):
         self.assertEqual(result['statistics'], {'untrans': 0, 'fuzzy': 0, 'trans': 47})
         self.assertEqual(result['pot_file'], '/POT/gnome-hello.master/gnome-hello.master.pot')
         self.assertEqual(result['po_file'], '/POT/gnome-hello.master/gnome-hello.master.fr.po')
+
+    def test_reserve_translation(self):
+        translator = Person.objects.create(
+            first_name='John', last_name='Translator',
+            email='jt devnull com', username='translator'
+        )
+        Role.objects.create(team=Team.objects.get(name='fr'), person=translator)
+        url = reverse('api-reserve', args=['gnome-hello', 'master', 'po', 'fr'])
+        response = self.client.post(url, data={})
+        self.assertEqual(response.status_code, 302)
+        self.client.force_login(translator)
+        response = self.client.post(url, data={})
+        self.assertEqual(response.json(), {'result': 'OK'})
+        state = State.objects.get(person=translator)
+        self.assertEqual(state.name, 'Translating')
+
+    def test_upload_translation(self):
+        translator = Person.objects.create(
+            first_name='John', last_name='Translator',
+            email='jt devnull com', username='translator'
+        )
+        Role.objects.create(team=Team.objects.get(name='fr'), person=translator)
+        _, _, state = get_vertimus_state(
+            Branch.objects.get(module__name='gnome-hello'),
+            Domain.objects.get(module__name='gnome-hello', name='po'),
+            Language.objects.get(locale='fr')
+        )
+        url = reverse('api-upload', args=['gnome-hello', 'master', 'po', 'fr'])
+        test_po = Path(__file__).parent.parent / "stats" / "tests" / "test.po"
+        with (test_po).open('rb') as fh:
+            response = self.client.post(url, data={'file': File(fh)})
+            self.assertEqual(response.status_code, 302)
+
+        self.client.force_login(translator)
+        with (test_po).open('rb') as fh:
+            response = self.client.post(url, data={'file': File(fh)})
+            self.assertEqual(response.status_code, 403)
+
+        state.change_state(StateTranslating, person=translator)
+        with (test_po).open('rb') as fh:
+            response = self.client.post(url, data={'file': File(fh)})
+        self.assertEqual(response.json(), {'result': 'OK'})
diff --git a/api/urls.py b/api/urls.py
index 734be31f..93ea78bf 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -2,6 +2,11 @@ from django.urls import path
 
 from . import views
 
+complete_translatable_path = (
+    'modules/<name:module_name>/branches/<path:branch_name>/domains/<name:domain_name>'
+    '/languages/<locale:lang>'
+)
+
 urlpatterns = [
     path('', views.APIHomeView.as_view(), name='api-home'),
     path('modules/', views.ModulesView.as_view(), name='api-modules'),
@@ -14,12 +19,14 @@ urlpatterns = [
     path('releases/<name:release>/stats', views.ReleaseStatsView.as_view(), name='api-release-stats'),
     path('releases/<name:release>/languages/<locale:lang>', views.ReleaseLanguageView.as_view(),
         name='api-release-language'),
-    path(
-        'modules/<name:module_name>/branches/<path:branch_name>/domains/<name:domain_name>'
-        '/languages/<locale:lang>',
-        views.ModuleLangStatsView.as_view(),
+    path(complete_translatable_path, views.ModuleLangStatsView.as_view(),
         name='api-module-lang-stats'
     ),
+    # APIs to reserve translation and upload translation, as could be used by an external
+    # translation tool.
+    path(f'{complete_translatable_path}/reserve', views.ReserveTranslationView.as_view(), 
name='api-reserve'),
+    path(f'{complete_translatable_path}/upload', views.UploadTranslationView.as_view(), name='api-upload'),
+
     # Used by a GitLab webhook to signal a commit for that module/branch
     path('modules/<name:module_name>/branches/<path:branch_name>/ping', views.rebuild_branch,
         name='api-module-rebuild'),
diff --git a/api/views.py b/api/views.py
index c99c3bea..1f2d57c7 100644
--- a/api/views.py
+++ b/api/views.py
@@ -1,10 +1,13 @@
 import traceback
 from threading import Thread
 
+from django.contrib.auth.mixins import LoginRequiredMixin
+from django.core.exceptions import PermissionDenied
 from django.core.mail import mail_admins
 from django.http import Http404, HttpResponseForbidden, JsonResponse
 from django.shortcuts import get_object_or_404
 from django.urls import reverse
+from django.utils.decorators import method_decorator
 from django.views.decorators.csrf import csrf_exempt
 from django.views.generic import View
 
@@ -12,6 +15,7 @@ from common.utils import check_gitlab_request
 from languages.models import Language
 from stats.models import Branch, Module, Release
 from teams.models import Team
+from vertimus.models import ActionRT, ActionUT
 from vertimus.views import get_vertimus_state
 
 
@@ -182,20 +186,25 @@ class ReleaseLanguageView(View):
         })
 
 
-class ModuleLangStatsView(View):
-    def get(self, *args, **kwargs):
+class VertimusPageMixin:
+    def get_state_from_kwargs(self):
         module = get_object_or_404(Module, name=self.kwargs['module_name'])
         branch = get_object_or_404(module.branch_set, name=self.kwargs['branch_name'])
         domain = branch.get_domains().get(self.kwargs['domain_name'])
         if not domain:
             raise Http404
         language = get_object_or_404(Language, locale=self.kwargs['lang'])
-        pot_stats, stats, state = get_vertimus_state(branch, domain, language)
+        return get_vertimus_state(branch, domain, language)[1:]
+
+
+class ModuleLangStatsView(VertimusPageMixin, View):
+    def get(self, *args, **kwargs):
+        stats, state = self.get_state_from_kwargs()
         return JsonResponse({
-            'module': module.name,
-            'branch': branch.name,
-            'domain': domain.name,
-            'language': language.locale,
+            'module': state.branch.module.name,
+            'branch': state.branch.name,
+            'domain': state.domain.name,
+            'language': state.language.locale,
             'state': state.name,
             'statistics': {
                 'trans': stats.translated(), 'fuzzy': stats.fuzzy(), 'untrans': stats.untranslated()
@@ -205,6 +214,32 @@ class ModuleLangStatsView(View):
         })
 
 
+@method_decorator(csrf_exempt, name='dispatch')
+class ReserveTranslationView(LoginRequiredMixin, VertimusPageMixin, View):
+    def post(self, request, *args, **kwargs):
+        stats, state = self.get_state_from_kwargs()
+        actions = [x.name for x in state.get_available_actions(request.user.person)]
+        if 'RT' not in actions:
+            raise PermissionDenied('This module cannot be reserved')
+
+        action = ActionRT(person=request.user.person)
+        action.apply_on(state, {'comment': ''})
+        return JsonResponse({'result': 'OK'})
+
+
+@method_decorator(csrf_exempt, name='dispatch')
+class UploadTranslationView(LoginRequiredMixin, VertimusPageMixin, View):
+    def post(self, request, *args, **kwargs):
+        stats, state = self.get_state_from_kwargs()
+        actions = [x.name for x in state.get_available_actions(request.user.person)]
+        if 'UT' not in actions:
+            raise PermissionDenied('You cannot upload a translation to this module')
+
+        action = ActionUT(person=request.user.person, file=request.FILES.get('file'))
+        action.apply_on(state, {'comment': ''})
+        return JsonResponse({'result': 'OK'})
+
+
 # CSRF skipped, verification using a secret token.
 @csrf_exempt
 def rebuild_branch(request, module_name, branch_name):


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]