diff --git a/projects/test_views.py b/projects/test_views.py index f222a7a..838ac38 100644 --- a/projects/test_views.py +++ b/projects/test_views.py @@ -1,8 +1,11 @@ +from unittest.mock import Mock, patch + from authlib.little_auth.models import User from django.test import Client, TestCase from django.test.utils import override_settings -from projects.models import Project +from projects.models import Catalog, Project +from projects.translators import TranslationError class ProjectsTest(TestCase): @@ -75,6 +78,11 @@ def test_smoke(self): ) self.assertEqual(r.content.decode("utf-8"), c.pofile) + r = su_client.post( + "/api/pofile/fr/djangojs/", headers={"x-project-token": p.token} + ) + self.assertEqual(r.status_code, 405) + r = su_client.get( "/api/pofile/de/djangojs/", headers={"x-project-token": p.token} ) @@ -160,3 +168,36 @@ def test_smoke(self): self.assertContains(r, '-') self.assertContains(r, 'use***@***.com') + + def test_suggest(self): + c = Client() + + r = c.get("/suggest/") + self.assertEqual(r.status_code, 405) + + r = c.post("/suggest/") + self.assertEqual(r.status_code, 403) + + user = User.objects.create_user("user@example.com", "user") + c.force_login(user) + + r = c.post("/suggest/") + self.assertEqual(r.status_code, 400) + + with patch( + "projects.views.translators.translate_by_deepl", lambda *a: "Bonjour" + ): + r = c.post("/suggest/", {"language_code": "fr", "msgid": "Anything"}) + self.assertEqual(r.status_code, 200) + self.assertEqual(r.json(), {"msgstr": "Bonjour"}) + + mock = Mock() + mock.side_effect = TranslationError("Oops") + with patch("projects.views.translators.translate_by_deepl", mock): + r = c.post("/suggest/", {"language_code": "fr", "msgid": "Anything"}) + self.assertEqual(r.status_code, 200) + self.assertEqual(r.json(), {"error": "Oops"}) + + def test_invalid_catalog(self): + c = Catalog(language_code="it", domain="django", pofile="blub") + self.assertEqual(str(c), "Italian, django (Invalid)") diff --git a/projects/translators.py b/projects/translators.py new file mode 100644 index 0000000..4f1c24e --- /dev/null +++ b/projects/translators.py @@ -0,0 +1,33 @@ +import requests + + +class TranslationError(Exception): + pass + + +def translate_by_deepl(text, to_language, auth_key): + # Copied 1:1 from django-rosetta, thanks! + if auth_key.lower().endswith(":fx"): + endpoint = "https://api-free.deepl.com" + else: + endpoint = "https://api.deepl.com" + + r = requests.post( + f"{endpoint}/v2/translate", + headers={"Authorization": f"DeepL-Auth-Key {auth_key}"}, + data={ + "target_lang": to_language.upper(), + "text": text, + }, + timeout=5, + ) + if r.status_code != 200: + raise TranslationError( + f"Deepl response is {r.status_code}. Please check your API key or try again later." + ) + try: + return r.json().get("translations")[0].get("text") + except Exception as exc: + raise TranslationError( + "Deepl returned a non-JSON or unexpected response." + ) from exc diff --git a/projects/views.py b/projects/views.py index 406d935..d69bb20 100644 --- a/projects/views.py +++ b/projects/views.py @@ -1,5 +1,4 @@ import polib -import requests from django import forms, http from django.conf import settings from django.contrib.auth.decorators import login_required @@ -9,8 +8,10 @@ from django.utils.timezone import localtime from django.utils.translation import gettext_lazy as _ from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_POST from form_rendering import adapt_rendering +from projects import translators from projects.models import Catalog, Project @@ -235,42 +236,12 @@ def catalog(request, project, language_code, domain): ) -class TranslationError(Exception): - pass - - -def translate_by_deepl(text, to_language, auth_key): - if auth_key.lower().endswith(":fx"): - endpoint = "https://api-free.deepl.com" - else: - endpoint = "https://api.deepl.com" - - r = requests.post( - f"{endpoint}/v2/translate", - headers={"Authorization": f"DeepL-Auth-Key {auth_key}"}, - data={ - "target_lang": to_language.upper(), - "text": text, - }, - timeout=5, - ) - if r.status_code != 200: - raise TranslationError( - f"Deepl response is {r.status_code}. Please check your API key or try again later." - ) - try: - return r.json().get("translations")[0].get("text") - except Exception as exc: - raise TranslationError( - "Deepl returned a non-JSON or unexpected response." - ) from exc - - class SuggestForm(forms.Form): language_code = forms.CharField() msgid = forms.CharField() +@require_POST def suggest(request): if not request.user.is_authenticated: return http.HttpResponseForbidden() @@ -279,10 +250,10 @@ def suggest(request): if form.is_valid(): data = form.cleaned_data try: - translation = translate_by_deepl( + translation = translators.translate_by_deepl( data["msgid"], data["language_code"], settings.DEEPL_AUTH_KEY ) - except TranslationError as exc: + except translators.TranslationError as exc: return http.JsonResponse({"error": str(exc)}) return http.JsonResponse({"msgstr": translation})