diff --git a/CHANGES.rst b/CHANGES.rst
index 667fe1d535..6fa5da40c2 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -138,6 +138,9 @@ Bugfixes:
New Features:
+- Add @translations endpoint
+ [erral]
+
- Reorder children in a item using the content endpoint.
[jaroel]
diff --git a/docs/source/_json/translations_delete.req b/docs/source/_json/translations_delete.req
new file mode 100644
index 0000000000..a753cb69be
--- /dev/null
+++ b/docs/source/_json/translations_delete.req
@@ -0,0 +1,6 @@
+DELETE /plone/en/test-document/@translations HTTP/1.1
+Accept: application/json
+Authorization: Basic YWRtaW46c2VjcmV0
+Content-Type: application/json
+
+{"language": "es"}
\ No newline at end of file
diff --git a/docs/source/_json/translations_delete.resp b/docs/source/_json/translations_delete.resp
new file mode 100644
index 0000000000..0074ded3bc
--- /dev/null
+++ b/docs/source/_json/translations_delete.resp
@@ -0,0 +1,2 @@
+HTTP/1.1 204 No Content
+
diff --git a/docs/source/_json/translations_get.req b/docs/source/_json/translations_get.req
new file mode 100644
index 0000000000..e17d071bdc
--- /dev/null
+++ b/docs/source/_json/translations_get.req
@@ -0,0 +1,3 @@
+GET /plone/en/test-document/@translations HTTP/1.1
+Accept: application/json
+Authorization: Basic YWRtaW46c2VjcmV0
diff --git a/docs/source/_json/translations_get.resp b/docs/source/_json/translations_get.resp
new file mode 100644
index 0000000000..f27b50ee45
--- /dev/null
+++ b/docs/source/_json/translations_get.resp
@@ -0,0 +1,13 @@
+HTTP/1.1 200 OK
+Content-Type: application/json
+
+{
+ "@id": "http://localhost:55001/plone/en/test-document",
+ "language": "en",
+ "translations": [
+ {
+ "@id": "http://localhost:55001/plone/es/test-document",
+ "language": "es"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/docs/source/_json/translations_post.req b/docs/source/_json/translations_post.req
new file mode 100644
index 0000000000..708288fd65
--- /dev/null
+++ b/docs/source/_json/translations_post.req
@@ -0,0 +1,6 @@
+POST /plone/en/test-document/@translations HTTP/1.1
+Accept: application/json
+Authorization: Basic YWRtaW46c2VjcmV0
+Content-Type: application/json
+
+{"id": "http://localhost:55001/plone/es/test-document"}
\ No newline at end of file
diff --git a/docs/source/_json/translations_post.resp b/docs/source/_json/translations_post.resp
new file mode 100644
index 0000000000..b0d80491db
--- /dev/null
+++ b/docs/source/_json/translations_post.resp
@@ -0,0 +1,5 @@
+HTTP/1.1 201 Created
+Content-Type: application/json
+Location: http://localhost:55001/plone/en/test-document
+
+{}
\ No newline at end of file
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 99a2ec55c5..0463af0777 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -45,6 +45,7 @@ Contents
controlpanels
customization
conventions
+ translations
.. include:: ../../README.rst
@@ -58,4 +59,3 @@ Appendix, Indices and tables
glossary
* :ref:`genindex`
-
diff --git a/docs/source/translations.rst b/docs/source/translations.rst
new file mode 100644
index 0000000000..4544c6ca79
--- /dev/null
+++ b/docs/source/translations.rst
@@ -0,0 +1,70 @@
+Translations
+============
+
+.. note::
+ This is only available on Plone 5.
+
+Since Plone 5 the product `plone.app.multilingual`_ is included in the base
+Plone installation although it is not enabled by default.
+
+Multilingualism in Plone not only allows the managers of the site to configure
+the site interface texts to be in one language or another (such as the
+configuration menus, error messages, information messages or other static
+text) but also to configure Plone to handle multilingual content. To achieve
+that it provides the user interface for managing content translations.
+
+You can get additional information about the multilingual capabilities of Plone
+in the `documentation`_.
+
+In connection with that capabilities, plone.restapi provides a `@translations`
+endpoint to handle the translation information of the content objects.
+
+Once we have installed `plone.app.multilingual`_ and enabled more than one
+language we can link two content-items of different languages to be the
+translation of each other issuing a `POST` query to the `@translations`
+endpoint including the `id` of the content which should be linked to. The
+`id` of the content must be a full URL of the content object:
+
+
+.. http:example:: curl httpie python-requests
+ :request: _json/translations_post.req
+
+
+.. note::
+ "id" is a required field and needs to point to an existing content on the site.
+
+The API will return a `201 Created` response if the linking was successful.
+
+
+.. literalinclude:: _json/translations_post.resp
+ :language: http
+
+
+After linking the contents we can get the list of the translations of that
+content item by issuing a ``GET`` request on the `@translations` endpoint of
+that content item.:
+
+.. http:example:: curl httpie python-requests
+ :request: _json/translations_get.req
+
+.. literalinclude:: _json/translations_get.resp
+ :language: http
+
+
+To unlink the content, issue a ``DELETE`` request on the `@translations`
+endpoint of the content item and provide the language code you want to unlink.:
+
+
+.. http:example:: curl httpie python-requests
+ :request: _json/translations_delete.req
+
+.. note::
+ "language" is a required field.
+
+.. literalinclude:: _json/translations_delete.resp
+ :language: http
+
+
+.. _`plone.app.multilingual`: https://pypi.python.org/pypi/plone.app.multilingual
+.. _`Products.LinguaPlone`: https://pypi.python.org/pypi/Products.LinguaPlone.
+.. _`documentation`: https://docs.plone.org/develop/plone/i18n/translating_content.html
\ No newline at end of file
diff --git a/src/plone/restapi/services/configure.zcml b/src/plone/restapi/services/configure.zcml
index 0ef3143b57..a567103add 100644
--- a/src/plone/restapi/services/configure.zcml
+++ b/src/plone/restapi/services/configure.zcml
@@ -26,5 +26,7 @@
+
diff --git a/src/plone/restapi/services/multilingual/__init__.py b/src/plone/restapi/services/multilingual/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/plone/restapi/services/multilingual/configure.zcml b/src/plone/restapi/services/multilingual/configure.zcml
new file mode 100644
index 0000000000..c3ca9f36c7
--- /dev/null
+++ b/src/plone/restapi/services/multilingual/configure.zcml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/plone/restapi/services/multilingual/pam.py b/src/plone/restapi/services/multilingual/pam.py
new file mode 100644
index 0000000000..618c837ad6
--- /dev/null
+++ b/src/plone/restapi/services/multilingual/pam.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+from zope.interface import alsoProvides
+from Products.CMFCore.utils import getToolByName
+from plone.app.multilingual.interfaces import ITranslationManager
+from plone.restapi.services import Service
+from plone.restapi.deserializer import json_body
+from Products.CMFPlone.interfaces import ILanguage
+
+import plone.protect.interfaces
+
+
+class TranslationInfo(Service):
+ """ Get translation information
+ """
+
+ def reply(self):
+ manager = ITranslationManager(self.context)
+ info = {
+ '@id': self.context.absolute_url(),
+ 'language': ILanguage(self.context).get_language(),
+ 'translations': []}
+ for language, translation in manager.get_translations().items():
+ if language != ILanguage(self.context).get_language():
+ info['translations'].append({
+ '@id': translation.absolute_url(),
+ 'language': language,
+ })
+
+ return info
+
+
+class LinkTranslations(Service):
+ """ Link two content objects as translations of each other
+ """
+
+ def reply(self):
+ # Disable CSRF protection
+ if 'IDisableCSRFProtection' in dir(plone.protect.interfaces):
+ alsoProvides(self.request,
+ plone.protect.interfaces.IDisableCSRFProtection)
+
+ data = json_body(self.request)
+ id_ = data.get('id', None)
+ if id_ is None:
+ self.request.response.setStatus(400)
+ return dict(error=dict(
+ type='BadRequest',
+ message='Missing content id to link to'))
+
+ target = self._traverse(id_)
+ if target is None:
+ self.request.response.setStatus(400)
+ return dict(error=dict(
+ type='BadRequest',
+ message='Content does not exist'))
+
+ target_language = ILanguage(target).get_language()
+ manager = ITranslationManager(self.context)
+ current_translation = manager.get_translation(target_language)
+ if current_translation is not None:
+ self.request.response.setStatus(400)
+ return dict(error=dict(
+ type='BadRequest',
+ message='Already translated into language {}'.format(
+ target_language)))
+
+ manager.register_translation(target_language, target)
+ self.request.response.setStatus(201)
+ self.request.response.setHeader(
+ 'Location', self.context.absolute_url())
+ return {}
+
+ def _traverse(self, url):
+ purl = getToolByName(self.context, 'portal_url')
+ portal = purl.getPortalObject()
+ portal_url = portal.absolute_url()
+ if url.startswith(portal_url):
+ content_path = url[len(portal_url)+1:]
+ content_path = content_path.split('/')
+ content_item = portal.restrictedTraverse(content_path)
+ return content_item
+
+ return None
+
+
+class UnlinkTranslations(Service):
+ """ Unlink the translations for a content object
+ """
+
+ def reply(self):
+ # Disable CSRF protection
+ if 'IDisableCSRFProtection' in dir(plone.protect.interfaces):
+ alsoProvides(self.request,
+ plone.protect.interfaces.IDisableCSRFProtection)
+
+ data = json_body(self.request)
+ manager = ITranslationManager(self.context)
+ language = data.get('language', None)
+ if language is None:
+ self.request.response.setStatus(400)
+ return dict(error=dict(
+ type='BadRequest',
+ message='You need to provide the language to unlink'))
+
+ if language not in manager.get_translations().keys():
+ self.request.response.setStatus(400)
+ return dict(error=dict(
+ type='BadRequest',
+ message='This objects is not translated into {}'.format(
+ language)))
+
+ manager.remove_translation(language)
+ self.request.response.setStatus(204)
+ return {}
diff --git a/src/plone/restapi/testing.py b/src/plone/restapi/testing.py
index a14087687e..a4d062f6f7 100644
--- a/src/plone/restapi/testing.py
+++ b/src/plone/restapi/testing.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+from Products.CMFCore.utils import getToolByName
# pylint: disable=E1002
# E1002: Use of super on an old style class
@@ -31,6 +32,14 @@
import requests
import collective.MockMailHost
+import pkg_resources
+
+
+try:
+ pkg_resources.get_distribution('plone.app.multilingual')
+ PAM_INSTALLED = True
+except pkg_resources.DistributionNotFound:
+ PAM_INSTALLED = False
def set_available_languages():
@@ -88,6 +97,52 @@ def setUpPloneSite(self, portal):
)
+class PloneRestApiDXPAMLayer(PloneSandboxLayer):
+ defaultBases = (PLONE_APP_CONTENTTYPES_FIXTURE,)
+
+ def setUpZope(self, app, configurationContext):
+ import plone.restapi
+ xmlconfig.file(
+ 'configure.zcml',
+ plone.restapi,
+ context=configurationContext
+ )
+ xmlconfig.file(
+ 'testing.zcml',
+ plone.restapi,
+ context=configurationContext
+ )
+
+ z2.installProduct(app, 'plone.restapi')
+
+ def setUpPloneSite(self, portal):
+ portal.acl_users.userFolderAddUser(
+ SITE_OWNER_NAME, SITE_OWNER_PASSWORD, ['Manager'], [])
+ login(portal, SITE_OWNER_NAME)
+ setRoles(portal, TEST_USER_ID, ['Manager'])
+ language_tool = getToolByName(portal, 'portal_languages')
+ language_tool.addSupportedLanguage('en')
+ language_tool.addSupportedLanguage('es')
+ if portal.portal_setup.profileExists(
+ 'plone.app.multilingual:default'):
+ applyProfile(portal, 'plone.app.multilingual:default')
+ applyProfile(portal, 'plone.restapi:default')
+ applyProfile(portal, 'plone.restapi:testing')
+ add_catalog_indexes(portal, DX_TYPES_INDEXES)
+ set_available_languages()
+
+
+PLONE_RESTAPI_DX_PAM_FIXTURE = PloneRestApiDXPAMLayer()
+PLONE_RESTAPI_DX_PAM_INTEGRATION_TESTING = IntegrationTesting(
+ bases=(PLONE_RESTAPI_DX_PAM_FIXTURE,),
+ name="PloneRestApiDXPAMLayer:Integration"
+)
+PLONE_RESTAPI_DX_PAM_FUNCTIONAL_TESTING = FunctionalTesting(
+ bases=(PLONE_RESTAPI_DX_PAM_FIXTURE, z2.ZSERVER_FIXTURE),
+ name="PloneRestApiDXPAMLayer:Functional"
+)
+
+
class PloneRestApiATLayer(PloneSandboxLayer):
defaultBases = (PLONE_FIXTURE,)
diff --git a/src/plone/restapi/tests/test_documentation.py b/src/plone/restapi/tests/test_documentation.py
index b8183c17d5..d152d07073 100644
--- a/src/plone/restapi/tests/test_documentation.py
+++ b/src/plone/restapi/tests/test_documentation.py
@@ -8,20 +8,23 @@
from plone.app.discussion.interfaces import IConversation
from plone.app.discussion.interfaces import IDiscussionSettings
from plone.app.discussion.interfaces import IReplies
-from plone.app.testing import SITE_OWNER_NAME
-from plone.app.testing import SITE_OWNER_PASSWORD
-from plone.app.testing import TEST_USER_ID
+from plone.app.testing import applyProfile
from plone.app.testing import popGlobalRegistry
from plone.app.testing import pushGlobalRegistry
from plone.app.testing import setRoles
+from plone.app.testing import SITE_OWNER_NAME
+from plone.app.testing import SITE_OWNER_PASSWORD
+from plone.app.testing import TEST_USER_ID
from plone.app.textfield.value import RichTextValue
from plone.locking.interfaces import ITTWLockable
from plone.namedfile.file import NamedBlobFile
from plone.namedfile.file import NamedBlobImage
from plone.registry.interfaces import IRegistry
+from plone.restapi.testing import PAM_INSTALLED
from plone.restapi.testing import PLONE_RESTAPI_DX_FUNCTIONAL_TESTING
-from plone.restapi.testing import RelativeSession
+from plone.restapi.testing import PLONE_RESTAPI_DX_PAM_FUNCTIONAL_TESTING
from plone.restapi.testing import register_static_uuid_utility
+from plone.restapi.testing import RelativeSession
from plone.testing.z2 import Browser
from zope.component import createObject
from zope.component import getUtility
@@ -35,6 +38,10 @@
import transaction
import unittest
+if PAM_INSTALLED:
+ from plone.app.multilingual.interfaces import ITranslationManager
+
+
TUS_HEADERS = [
'upload-offset',
@@ -1225,3 +1232,82 @@ def test_controlpanels_get_item(self):
'/@controlpanels/editing'
)
save_request_and_response_for_docs('controlpanels_get_item', response)
+
+
+@unittest.skipUnless(PAM_INSTALLED, 'plone.app.multilingual is installed by default only in Plone 5')
+class TestPAMDocumentation(unittest.TestCase):
+
+ layer = PLONE_RESTAPI_DX_PAM_FUNCTIONAL_TESTING
+
+ def setUp(self):
+ self.app = self.layer['app']
+ self.request = self.layer['request']
+ self.portal = self.layer['portal']
+ self.portal_url = self.portal.absolute_url()
+
+ self.time_freezer = freeze_time("2016-10-21 19:00:00")
+ self.frozen_time = self.time_freezer.start()
+
+ self.api_session = RelativeSession(self.portal_url)
+ self.api_session.headers.update({'Accept': 'application/json'})
+ self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD)
+
+ setRoles(self.portal, TEST_USER_ID, ['Manager'])
+
+ language_tool = api.portal.get_tool('portal_languages')
+ language_tool.addSupportedLanguage('en')
+ language_tool.addSupportedLanguage('es')
+ applyProfile(self.portal, 'plone.app.multilingual:default')
+ en_id = self.portal['en'].invokeFactory(
+ 'Document',
+ id='test-document',
+ title='Test document'
+ )
+ self.en_content = self.portal['en'].get(en_id)
+ es_id = self.portal['es'].invokeFactory(
+ 'Document',
+ id='test-document',
+ title='Test document'
+ )
+ self.es_content = self.portal['es'].get(es_id)
+
+ import transaction
+ transaction.commit()
+ self.browser = Browser(self.app)
+ self.browser.handleErrors = False
+ self.browser.addHeader(
+ 'Authorization',
+ 'Basic %s:%s' % (SITE_OWNER_NAME, SITE_OWNER_PASSWORD,)
+ )
+
+ def tearDown(self):
+ self.time_freezer.stop()
+
+ def test_documentation_translations_post(self):
+ response = self.api_session.post(
+ '{}/@translations'.format(self.en_content.absolute_url()),
+ json={
+ 'id': self.es_content.absolute_url()
+ }
+ )
+ save_request_and_response_for_docs('translations_post', response)
+
+ def test_documentation_translations_get(self):
+ ITranslationManager(self.en_content).register_translation(
+ 'es', self.es_content)
+ transaction.commit()
+ response = self.api_session.get(
+ '{}/@translations'.format(self.en_content.absolute_url()))
+
+ save_request_and_response_for_docs('translations_get', response)
+
+ def test_documentation_translations_delete(self):
+ ITranslationManager(self.en_content).register_translation(
+ 'es', self.es_content)
+ transaction.commit()
+ response = self.api_session.delete(
+ '{}/@translations'.format(self.en_content.absolute_url()),
+ json={
+ "language": "es"
+ })
+ save_request_and_response_for_docs('translations_delete', response)
diff --git a/src/plone/restapi/tests/test_translations.py b/src/plone/restapi/tests/test_translations.py
new file mode 100644
index 0000000000..09992676b1
--- /dev/null
+++ b/src/plone/restapi/tests/test_translations.py
@@ -0,0 +1,184 @@
+# -*- coding: utf-8 -*-
+from plone.app.testing import login
+from plone.app.testing import SITE_OWNER_NAME
+from plone.app.testing import SITE_OWNER_PASSWORD
+from plone.dexterity.utils import createContentInContainer
+from plone.restapi.testing import PAM_INSTALLED
+from plone.restapi.testing import PLONE_RESTAPI_DX_PAM_FUNCTIONAL_TESTING
+from plone.restapi.testing import PLONE_RESTAPI_DX_PAM_INTEGRATION_TESTING
+from zope.component import getMultiAdapter
+from zope.interface import alsoProvides
+
+import requests
+import transaction
+import unittest
+
+
+if PAM_INSTALLED:
+ from Products.CMFPlone.interfaces import ILanguage
+ from plone.app.multilingual.interfaces import IPloneAppMultilingualInstalled # noqa
+ from plone.app.multilingual.interfaces import ITranslationManager
+
+
+
+@unittest.skipUnless(PAM_INSTALLED, 'plone.app.multilingual is installed by default only in Plone 5')
+class TestTranslationInfo(unittest.TestCase):
+
+ layer = PLONE_RESTAPI_DX_PAM_INTEGRATION_TESTING
+
+ def setUp(self):
+ self.portal = self.layer['portal']
+ self.request = self.layer['request']
+ alsoProvides(self.layer['request'], IPloneAppMultilingualInstalled)
+ login(self.portal, SITE_OWNER_NAME)
+ self.en_content = createContentInContainer(
+ self.portal['en'], 'Document', title='Test document')
+ self.es_content = createContentInContainer(
+ self.portal['es'], 'Document', title='Test document')
+ ITranslationManager(self.en_content).register_translation(
+ 'es', self.es_content)
+
+ def test_translation_info_includes_translations(self):
+ tinfo = getMultiAdapter(
+ (self.en_content, self.request),
+ name=u'GET_application_json_@translations')
+
+ info = tinfo.reply()
+ self.assertIn('translations', info)
+ self.assertEqual(1, len(info['translations']))
+
+ def test_correct_translation_information(self):
+ tinfo = getMultiAdapter(
+ (self.en_content, self.request),
+ name=u'GET_application_json_@translations')
+
+ info = tinfo.reply()
+ tinfo_es = info['translations'][0]
+ self.assertEqual(
+ self.es_content.absolute_url(),
+ tinfo_es['@id'])
+ self.assertEqual(
+ ILanguage(self.es_content).get_language(),
+ tinfo_es['language'])
+
+@unittest.skipUnless(PAM_INSTALLED, 'plone.app.multilingual is installed by default only in Plone 5')
+class TestLinkContentsAsTranslations(unittest.TestCase):
+ layer = PLONE_RESTAPI_DX_PAM_FUNCTIONAL_TESTING
+
+ def setUp(self):
+ self.portal = self.layer['portal']
+ self.request = self.layer['request']
+ alsoProvides(self.layer['request'], IPloneAppMultilingualInstalled)
+ login(self.portal, SITE_OWNER_NAME)
+ self.en_content = createContentInContainer(
+ self.portal['en'], 'Document', title='Test document')
+ self.es_content = createContentInContainer(
+ self.portal['es'], 'Document', title='Test document')
+ transaction.commit()
+
+ def test_translation_linking_succeeds(self):
+ response = requests.post(
+ '{}/@translations'.format(self.en_content.absolute_url()),
+ headers={'Accept': 'application/json'},
+ auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),
+ json={
+ "id": self.es_content.absolute_url(),
+ },
+ )
+ self.assertEqual(201, response.status_code)
+ transaction.begin()
+ manager = ITranslationManager(self.en_content)
+ for language, translation in manager.get_translations():
+ if language == ILanguage(self.es_content).get_language():
+ self.assertEqual(translation, self.es_content)
+
+ def test_calling_endpoint_without_id_gives_400(self):
+ response = requests.post(
+ '{}/@translations'.format(self.en_content.absolute_url()),
+ headers={'Accept': 'application/json'},
+ auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),
+ json={
+ },
+ )
+ self.assertEqual(400, response.status_code)
+
+ def test_calling_with_an_already_translated_content_gives_400(self):
+ ITranslationManager(self.en_content).register_translation(
+ 'es', self.es_content)
+ transaction.commit()
+ response = requests.post(
+ '{}/@translations'.format(self.en_content.absolute_url()),
+ headers={'Accept': 'application/json'},
+ auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),
+ json={
+ 'id': self.es_content.absolute_url()
+ },
+ )
+ self.assertEqual(400, response.status_code)
+
+ def test_calling_with_inexistent_content_gives_400(self):
+ response = requests.post(
+ '{}/@translations'.format(self.en_content.absolute_url()),
+ headers={'Accept': 'application/json'},
+ auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),
+ json={
+ 'id': 'http://this-content-does-not-exist',
+ },
+ )
+ self.assertEqual(400, response.status_code)
+
+@unittest.skipUnless(PAM_INSTALLED, 'plone.app.multilingual is installed by default only in Plone 5')
+class TestUnLinkContentTranslations(unittest.TestCase):
+ layer = PLONE_RESTAPI_DX_PAM_FUNCTIONAL_TESTING
+
+ def setUp(self):
+ self.portal = self.layer['portal']
+ self.request = self.layer['request']
+ alsoProvides(self.layer['request'], IPloneAppMultilingualInstalled)
+ login(self.portal, SITE_OWNER_NAME)
+ self.en_content = createContentInContainer(
+ self.portal['en'], 'Document', title='Test document')
+ self.es_content = createContentInContainer(
+ self.portal['es'], 'Document', title='Test document')
+ ITranslationManager(self.en_content).register_translation(
+ 'es', self.es_content)
+ transaction.commit()
+
+ def test_translation_unlinking_succeeds(self):
+ response = requests.delete(
+ '{}/@translations'.format(self.en_content.absolute_url()),
+ headers={'Accept': 'application/json'},
+ auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),
+ json={
+ "language": "es",
+ },
+ )
+ self.assertEqual(204, response.status_code)
+ transaction.begin()
+ manager = ITranslationManager(self.en_content)
+ self.assertNotIn(
+ ILanguage(self.es_content).get_language(),
+ manager.get_translations().keys())
+
+ def test_calling_endpoint_without_language_gives_400(self):
+ response = requests.delete(
+ '{}/@translations'.format(self.en_content.absolute_url()),
+ headers={'Accept': 'application/json'},
+ auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),
+ json={
+ },
+ )
+ self.assertEqual(400, response.status_code)
+
+ def test_calling_with_an_untranslated_content_gives_400(self):
+ ITranslationManager(self.en_content).remove_translation("es")
+ transaction.commit()
+ response = requests.delete(
+ '{}/@translations'.format(self.en_content.absolute_url()),
+ headers={'Accept': 'application/json'},
+ auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),
+ json={
+ "language": "es",
+ },
+ )
+ self.assertEqual(400, response.status_code)