Translation API - bounswe/2021SpringGroup9 GitHub Wiki
-
Story: A model for stories that users will publish :
- id: Story's id for identification
- title: Title of the story with maximum char length 200.
- story: Main content of the story with maximum char length 1000. It will be referred to as "body" to avoid ambiguity.
- name: A nickname for the user who posts the story.
- longitude: Longitude of story location.
- latitude: Latitude of story location.
- location: Location of a story, expressed as in everyday language.
- tag: A semantic tag that gives information about the main topic of a story.
- date: The date the story was published.
- notifyAdmin: A marker that identifies if the admin should be notified about the content story.
-
Translation: A model to store translation of title and body of a story. :
- story_id: Story's id for identification, a foreign key from Story model.
- language: The language to which the story was translated. Stored as a language code such as "tr", "fr", "ne"
- title_trans: Translated version of the title.
- story_trans: Translated version of the body.
-
IBMTranslator: An external API that can translate texts. The full name of the API is "IBM Watson Language Translator API".
Translation API is a REST API that supports translating a story's title and body to another language.
The API sends requests to IBMTranslator to retrieve translations. For documentation of IBMTranslator, click here. IBMTranslator can automatically detect the source language of a text unless it is too short. For this reason, Translation API does not allow translation of a story if it has any of the following:
- Title shorter than 2 words.
- Title shorter than 6 characters.
- Body shorter than 2 words.
- Body shorter than 10 characters.
The API only supports GET requests, but superusers can store translations to the database. If a translated story is already present in the database, the translation is retrieved from the database without sending a request to IBMTranslator.
Get a story with the title and body translated to a specific language.
Request:
GET /api/story/<int:id>/translate_<str:target>/
Explanation of parameters:
id: The id of the Story
target: Target language for translation. Specified as a language code such as "tr", "fr", "it"
Successful Request
Request:GET http://3.129.194.233/api/story/1/translate_ita/
Response:
{
"id": 1,
"date": "2021-06-10T10:26:00.117830+03:00",
"name": "zcanfes",
"location": "Rome",
"tag": "happy",
"title": "Primo giorno a Roma",
"story": "Questo è stato il mio primo giorno a Roma dopo essersi trasferito. Sono uscito da casa e ho iniziato a camminare verso il centro città. Ovunque ci sono edifici da vedere e ammirare. Solo essere in città può riprenderti in tempo per sperimentare la vecchia Roma. Oggi ho intenzione di visitare il Colosseo e non vedo l'ora di vedere uno dei monumenti più importanti di Roma. Mi prenderò un sacco di foto e condividerò la mia storia qui.",
"notifyAdmin": false,
"latitude": 41.9027835,
"longitude": 12.4963655
}
Status Code: 200 OK
Send Wrong Request Method
Request:POST http://3.129.194.233/api/story/1/translate_de/
Response:
{
"detail": "Method \"POST\" not allowed."
}
Status Code: 405 Method Not Allowed
Request Translation of Non-Existent Story
Request:GET http://3.129.194.233/api/story/65421/translate_tr/
Response:
"Story does not exist."
Status Code: 404 Not Found
Translation API does not require any authentication, but IBMTranslator has a key for authentication. The key is stored in the environment file and can be managed from the corresponding IBM Cloud account.
Name | Meaning |
---|---|
HTTP_200_OK | The request is successful. |
HTTP_400_BAD_REQUEST | The selected story is too short to translate. |
HTTP_404_NOT_FOUND | The story does not exist or IBMTranslator is not able to translate it at the moment. |
HTTP_405_METHOD_NOT_ALLOWED | Wrong request method is used. |
Models
class Story(models.Model):
title = models.CharField(max_length=200)
story = models.CharField(max_length=1000)
name = models.CharField(max_length=200)
longitude = models.FloatField()
latitude = models.FloatField()
location = models.CharField(max_length=200)
tag = models.CharField(max_length=200)
date = models.DateTimeField(auto_now_add=True)
notifyAdmin = models.BooleanField(default=False)
def __str__(self):
return self.title
class Translation(models.Model):
story_id = models.ForeignKey('Story', on_delete=models.CASCADE)
language = models.CharField(max_length=3)
title_trans = models.CharField(max_length=200)
story_trans = models.CharField(max_length=1000)
class Meta:
unique_together = (("story_id", "language"),)
def __str__(self):
return self.title_trans
Note: Story model is common for all APIs.
Views and Helper Methods for Views
from django.http.response import HttpResponse
from rest_framework.response import Response
from ..models import *
import requests
import environ
import json
from rest_framework import status
from ..serializers import *
from rest_framework.generics import GenericAPIView
env = environ.Env()
environ.Env.read_env('.env')
class TranslationView(GenericAPIView):
"""
Retrieves translation of a story
Target is specified as a language code: 'de', 'en','tr' etc.
"""
queryset = Story.objects.all()
serializer_class = StorySerializer
def retrieve_translation(story_id, target):
""" Retrieves translation if it exists in the database."""
try:
translation = Translation.objects.get(story_id = story_id, language = target)
except:
return False
return translation
def get(self, request, pk, target):
try:
story = Story.objects.get(pk=pk)
story_dict = StorySerializer(story).data
except:
return Response("Story does not exist.",status=status.HTTP_404_NOT_FOUND)
check_translation = TranslationView.retrieve_translation(pk,target)
if check_translation:
# translation is retrieved from the database
story_dict['title'] = check_translation.title_trans
story_dict['story'] = check_translation.story_trans
return Response(story_dict)
else:
new_trans = translate(story,target) # request translation from external API
if 'error' in new_trans:
return Response(new_trans['error'],new_trans['status'])
else:
story_dict['title'] = new_trans['title_trans']
story_dict['story'] = new_trans['story_trans']
return Response(story_dict) # return translated story
def check_story_length(title,body):
""" Returns false for short stories. """
if len(title) < 6 or len(title.split()) < 2:
return False
if len(body) < 10 or len(body.split()) < 2:
return False
return True
def translate(story, target):
""" Sends post request to IBM Watson Translator API. The source language is automatically detected.
The API may not be able to identify the source language if the string is too short (1 word)
Returns a JSON of the translated story if the request is successful.
target : language code, e.g. "de","es","tr"
"""
headers = {
'Content-Type': 'application/json',
'charset' : 'utf-8'
}
URL = 'https://api.eu-gb.language-translator.watson.cloud.ibm.com/instances/b2d14a2d-97d0-4b45-8f44-b3b993729db1'
version = '2018-05-01'
apikey = env('TRANSLATE_KEY')
auth = ('apikey', apikey)
if not check_story_length(story.title,story.story):
return {'error':"Too short to translate.",'status' : status.HTTP_400_BAD_REQUEST}
data = json.dumps({"text": [story.title,story.story],"target":target})
response = requests.post(f'{URL}/v3/translate?version={version}', headers=headers, data=data, auth=auth)
if response.status_code == 200:
response_json = response.json()
title_trans = response_json['translations'][0]['translation'] # translation of title
story_trans = response_json['translations'][1]['translation']
translated_fields = {'title_trans':title_trans, 'story_trans':story_trans}
return translated_fields
else :
return {'error':response.json()['error'], 'status':status.HTTP_404_NOT_FOUND} # return the error response of IBM Translator API
Tests
from django.test import TestCase,Client
from ..views.view_translationAPI_niyazi import *
class CheckStoryLengthTest(TestCase):
def test_short_title(self):
title = "hey"
body = "It was a sunny day."
self.assertFalse(check_story_length(title,body))
def test_short_body(self):
title = "We went to the cinema."
body = "beautiful"
self.assertFalse(check_story_length(title,body))
def test_false_title_type(self):
title = 1957
body = "What a nice year!"
self.assertRaises(TypeError, check_story_length, title, body)
def test_false_body_type(self):
title = "We went to the cinema."
body = 23.948429
self.assertRaises(TypeError, check_story_length, title, body)
def test_correct_input(self):
title = "We went to the cinema."
body = "It was a sunny day."
self.assertTrue(check_story_length(title,body))
class TranslationTest(TestCase):
def setUp(self):
Story.objects.create(id = 1,
title = "Title",
story = "Story",
name = "User1",
longitude = 41,
latitude = 28,
location = "Istanbul",
tag = "Tag1",
notifyAdmin = False
)
Story.objects.create(id = 2,
title = "Hello World!",
story = "I am very happy today!",
name = "User2",
longitude = 33,
latitude = 40,
location = "Ankara",
tag = "Tag2",
notifyAdmin = True
)
def test_only_get_requests(self):
c=Client()
resp_1=c.post("/api/story/2/translate_es/")
resp_2=c.put("/api/story/2/translate_es/")
resp_3=c.delete("/api/story/2/translate_es/")
self.assertEqual(resp_1.status_code,405)
self.assertEqual(resp_2.status_code,405)
self.assertEqual(resp_3.status_code,405)
def test_when_story_does_not_exist(self):
c=Client()
resp=c.get("/api/story/3/translate_ne/")
self.assertEqual(resp.status_code,404)
def test_when_translator_api_fails(self):
c=Client()
resp=c.get("/api/story/2/translate_nosuchlanguage/")
self.assertEqual(resp.status_code,404)
def test_short_story(self):
c=Client()
resp=c.get("/api/story/1/translate_jp/")
self.assertEqual(resp.status_code,400)
def test_works_correct(self):
c=Client()
resp=c.get("/api/story/2/translate_tr/")
self.assertEqual(resp.status_code,200)