diff --git a/docs/source/endpoints/aliases.md b/docs/source/endpoints/aliases.md
index 46f68342e..da841c2e0 100644
--- a/docs/source/endpoints/aliases.md
+++ b/docs/source/endpoints/aliases.md
@@ -70,9 +70,10 @@ Response:
:language: http
```
-## Adding URL aliases in bulk
+## Adding URL aliases in bulk via JSON
-You can add multiple URL aliases for multiple pages by sending a `POST` request to the `/@aliases` endpoint on site `root`. **datetime** parameter is optional:
+You can add multiple URL aliases for multiple pages by sending a `POST` request to the `/@aliases` endpoint on site `root` using a JSON payload.
+**datetime** parameter is optional:
```{eval-rst}
.. http:example:: curl httpie python-requests
@@ -85,10 +86,26 @@ Response:
:language: http
```
+## Adding URL aliases in bulk via CSV
-## Listing all available aliases
+You can add multiple URL aliases for multiple pages by sending a `POST` request to the `/@aliases` endpoint on site `root` using a CSV file.
+**datetime** parameter is optional:
-To list all aliases, send a `GET` request to the `/@aliases` endpoint on site `root`:
+```{eval-rst}
+.. http:example:: curl httpie python-requests
+ :request: ../../../src/plone/restapi/tests/http-examples/aliases_root_add_csv_format.req
+```
+
+Response:
+
+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/aliases_root_add_csv_format.resp
+:language: http
+```
+
+
+## Listing all available aliases via JSON
+
+To list all aliases in JSON format, send a `GET` request to the `/@aliases` endpoint on site `root`:
```{eval-rst}
.. http:example:: curl httpie python-requests
@@ -101,6 +118,21 @@ Response:
:language: http
```
+## Listing all available aliases via CSV
+
+To download all aliases as a CSV file, send a `GET` request to the `/@aliases` endpoint on site `root`:
+
+```{eval-rst}
+.. http:example:: curl httpie python-requests
+ :request: ../../../src/plone/restapi/tests/http-examples/aliases_root_get_csv_format.req
+```
+
+Response:
+
+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/aliases_root_get_csv_format.resp
+:language: http
+```
+
## Filter aliases
To search for specific aliases, send a `GET` request to the `/@aliases` endpoint on site `root` with a `q` parameter:
diff --git a/news/1812.feature b/news/1812.feature
new file mode 100644
index 000000000..85727b674
--- /dev/null
+++ b/news/1812.feature
@@ -0,0 +1 @@
+Added create and fetch aliases in CSV format. @Faakhir30
diff --git a/src/plone/restapi/services/aliases/add.py b/src/plone/restapi/services/aliases/add.py
index 555fbdbe0..ab8af1ed2 100644
--- a/src/plone/restapi/services/aliases/add.py
+++ b/src/plone/restapi/services/aliases/add.py
@@ -1,17 +1,24 @@
from DateTime import DateTime
+from DateTime.interfaces import DateTimeError
from plone.app.redirector.interfaces import IRedirectionStorage
from plone.restapi import _
from plone.restapi.deserializer import json_body
from plone.restapi.services import Service
from Products.CMFPlone.controlpanel.browser.redirects import absolutize_path
+from Products.CMFPlone.controlpanel.browser.redirects import RedirectsControlPanel
+from Products.statusmessages.interfaces import IStatusMessage
from zExceptions import BadRequest
from zope.component import getMultiAdapter
+from zope.component.hooks import getSite
from zope.component import getUtility
from zope.interface import alsoProvides
from zope.interface import implementer
from zope.publisher.interfaces import IPublishTraverse
import plone.protect.interfaces
+import logging
+
+logger = logging.getLogger("Plone")
@implementer(IPublishTraverse)
@@ -83,15 +90,35 @@ def edit_for_navigation_root(self, alias):
class AliasesRootPost(Service):
"""Creates new aliases via controlpanel"""
- def reply(self):
- data = json_body(self.request)
+ def _reply_csv(self):
+ form = self.request.form
+ if not form.get("file"):
+ raise BadRequest("No file uploaded")
+ controlpanel = RedirectsControlPanel(self.context, self.request)
storage = getUtility(IRedirectionStorage)
- aliases = data.get("items", [])
+ status = IStatusMessage(self.request)
+ portal = getSite()
+ file = form["file"]
+ controlpanel.upload(file, portal, storage, status)
+ file.close()
+
+ if err := status.show():
+ if err[0].type == "error":
+ raise BadRequest(err[0].message)
+ elif err[0].type == "info":
+ logger.info(err[0].message)
+ return self.reply_no_content()
+ def reply(self):
# Disable CSRF protection
if "IDisableCSRFProtection" in dir(plone.protect.interfaces):
alsoProvides(self.request, plone.protect.interfaces.IDisableCSRFProtection)
+ if "multipart/form-data" in self.request.getHeader("Content-Type"):
+ return self._reply_csv()
+ storage = getUtility(IRedirectionStorage)
+ data = json_body(self.request)
+ aliases = data.get("items", [])
for alias in aliases:
redirection = alias.get("path")
target = alias.get("redirect-to")
@@ -113,7 +140,11 @@ def reply(self):
date = alias.get("datetime", None)
if date:
- date = DateTime(date)
+ try:
+ date = DateTime(date)
+ except DateTimeError:
+ logger.warning("Failed to parse as DateTime: %s", date)
+ date = None
storage.add(abs_redirection, abs_target, now=date, manual=True)
diff --git a/src/plone/restapi/services/aliases/configure.zcml b/src/plone/restapi/services/aliases/configure.zcml
index e3291cf16..c6499d117 100644
--- a/src/plone/restapi/services/aliases/configure.zcml
+++ b/src/plone/restapi/services/aliases/configure.zcml
@@ -12,6 +12,15 @@
name="@aliases"
/>
+
+