diff --git a/docs/source/endpoints/vocabularies.md b/docs/source/endpoints/vocabularies.md index 7d6fcda3b..63b9f9b15 100644 --- a/docs/source/endpoints/vocabularies.md +++ b/docs/source/endpoints/vocabularies.md @@ -160,6 +160,18 @@ Use the `tokens` parameter to filter vocabulary terms by a list of tokens: :language: http ``` +### Sort vocabularies by title + +Sort vocabulary terms by title using the `sort_on=title` parameter. +Sorting is applied server-side before results are batched. +```{eval-rst} +.. http:example:: curl httpie python-requests + :request: ../../../src/plone/restapi/tests/http-examples/vocabularies_get_sorted_by_title.req +``` + +```{literalinclude} ../../../src/plone/restapi/tests/http-examples/vocabularies_get_sorted_by_title.resp +:language: http +``` ## Get a source ```{eval-rst} diff --git a/news/1990.feature b/news/1990.feature new file mode 100644 index 000000000..26d19725a --- /dev/null +++ b/news/1990.feature @@ -0,0 +1 @@ +Added support for sorting vocabularies by title before batching for the `@vocabularies` endpoint. @hasansyed107 \ No newline at end of file diff --git a/src/plone/restapi/serializer/vocabularies.py b/src/plone/restapi/serializer/vocabularies.py index 2235ced8a..148715130 100644 --- a/src/plone/restapi/serializer/vocabularies.py +++ b/src/plone/restapi/serializer/vocabularies.py @@ -63,6 +63,15 @@ def __call__(self, vocabulary_id): continue terms.append(term) + # Optional sorting by title (before batching) + sort_on = self.request.form.get("sort_on") + if vocabulary_id and sort_on == "title": + + def sort_key(term): + title = getattr(term, "title", None) or term.token + return translate(title, context=self.request).lower() + + terms.sort(key=sort_key) serialized_terms = [] # Do not batch parameter is set diff --git a/src/plone/restapi/tests/http-examples/vocabularies_get_sorted_by_title.req b/src/plone/restapi/tests/http-examples/vocabularies_get_sorted_by_title.req new file mode 100644 index 000000000..69644a72e --- /dev/null +++ b/src/plone/restapi/tests/http-examples/vocabularies_get_sorted_by_title.req @@ -0,0 +1,4 @@ +GET /plone/@vocabularies/plone.app.vocabularies.ReallyUserFriendlyTypes?sort_on=title HTTP/1.1 +Accept: application/json +Accept-Language: es +Authorization: Basic YWRtaW46c2VjcmV0 diff --git a/src/plone/restapi/tests/http-examples/vocabularies_get_sorted_by_title.resp b/src/plone/restapi/tests/http-examples/vocabularies_get_sorted_by_title.resp new file mode 100644 index 000000000..15dd37b92 --- /dev/null +++ b/src/plone/restapi/tests/http-examples/vocabularies_get_sorted_by_title.resp @@ -0,0 +1,57 @@ +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "@id": "http://localhost:55001/plone/@vocabularies/plone.app.vocabularies.ReallyUserFriendlyTypes", + "items": [ + { + "title": "Archivo", + "token": "File" + }, + { + "title": "Carpeta", + "token": "Folder" + }, + { + "title": "Colecci\u00f3n", + "token": "Collection" + }, + { + "title": "Comentarios", + "token": "Discussion Item" + }, + { + "title": "DX Test Document", + "token": "DXTestDocument" + }, + { + "title": "Enlace", + "token": "Link" + }, + { + "title": "Evento", + "token": "Event" + }, + { + "title": "Imagen", + "token": "Image" + }, + { + "title": "Noticia", + "token": "News Item" + }, + { + "title": "P\u00e1gina", + "token": "Document" + }, + { + "title": "Test Document", + "token": "ATTestDocument" + }, + { + "title": "Test Folder", + "token": "ATTestFolder" + } + ], + "items_total": 12 +} diff --git a/src/plone/restapi/tests/test_documentation.py b/src/plone/restapi/tests/test_documentation.py index 67b3620ba..a5c23c4dc 100644 --- a/src/plone/restapi/tests/test_documentation.py +++ b/src/plone/restapi/tests/test_documentation.py @@ -1911,6 +1911,12 @@ def test_translate_messages_addons(self): response = self.api_session.get("/@addons") save_request_and_response_for_docs("translated_messages_addons", response) + def test_documentation_vocabularies_get_sorted_by_title(self): + response = self.api_session.get( + "/@vocabularies/plone.app.vocabularies.ReallyUserFriendlyTypes?sort_on=title" + ) + save_request_and_response_for_docs("vocabularies_get_sorted_by_title", response) + class TestCommenting(TestDocumentationBase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING diff --git a/src/plone/restapi/tests/test_services_vocabularies.py b/src/plone/restapi/tests/test_services_vocabularies.py index cb0774c3f..e60cb73e9 100644 --- a/src/plone/restapi/tests/test_services_vocabularies.py +++ b/src/plone/restapi/tests/test_services_vocabularies.py @@ -468,6 +468,27 @@ def test_big_vocabulary_not_batched(self): response = response.json() self.assertEqual(len(response["items"]), 100) + def test_get_vocabulary_sorted_by_title(self): + response = self.api_session.get( + "/@vocabularies/plone.restapi.tests.test_vocabulary?sort_on=title&b_size=-1" + ) + + self.assertEqual(200, response.status_code) + response = response.json() + + self.assertEqual( + [item["title"] for item in response["items"]], + [ + "This is a title for the seventh term", + "Title 1", + "Title 2", + "token3", + "token4", + "Tötle 5", + "Tötle 6", + ], + ) + def tearDown(self): self.api_session.close() gsm = getGlobalSiteManager()