Skip to content

Commit

Permalink
@Aliases Root endpoints (#1406)
Browse files Browse the repository at this point in the history
* Initial commit.

* Added GET root endpoint.

* Added @Aliases root POST

* Refs #1393 - Add @Aliases endpoint docs

* Flake8 & delete endpoint

* Black

Co-authored-by: Alin Voinea <[email protected]>
  • Loading branch information
Petchesi-Iulian and avoinea authored May 6, 2022
1 parent bb60536 commit 4023828
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 1 deletion.
70 changes: 70 additions & 0 deletions docs/source/aliases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
html_meta:
"description": "Aliases is a mechanism to redirect old URLs to new ones."
"property=og:description": "Aliases is a mechanism to redirect old URLs to new ones."
"property=og:title": "Aliases"
"keywords": "Plone, plone.app.redirector, redirector, REST, API, Aliases"
---

# Aliases

Aliases is a mechanism to redirect old URLs to new ones.

When an object is moved (renamed or cut/pasted into a different location), the redirection storage will remember the old path. It is smart enough to deal with transitive references (if we have a -> b and then add b -> c, it is replaced by a reference a -> c) and circular references (attempting to add a -> a does nothing).

The API consumer can create, read, and delete aliases.


| Verb | URL | Action |
| -------- | ----------- | -------------------------------------- |
| `POST` | `/@aliases` | Add one or more aliases |
| `GET` | `/@aliases` | List all aliases |
| `DELETE` | `/@aliases` | Remove one or more aliases |

## Adding new aliases on a Content Object

By default, Plone automatically creates a new alias when an object is renamed or moved. Still, you can also create aliases manually.

To create a new alias, send a POST request to the `/@aliases` endpoint:

```{eval-rst}
.. http:example:: curl httpie python-requests
:request: ../../src/plone/restapi/tests/http-examples/aliases_add.req
```

Response:

```{literalinclude} ../../src/plone/restapi/tests/http-examples/aliases_add.resp
:language: http
```

## Listing aliases of a Content Object

Listing aliases of a resource you can send a `GET` request to the `/@aliases` endpoint:

```{eval-rst}
.. http:example:: curl httpie python-requests
:request: ../../src/plone/restapi/tests/http-examples/aliases_get.req
```

Response:

```{literalinclude} ../../src/plone/restapi/tests/http-examples/aliases_get.resp
:language: http
```


## Removing aliases of a Content Object

To remove aliases of an object, send a `DELETE` request to the `/@aliases` endpoint:

```{eval-rst}
.. http:example:: curl httpie python-requests
:request: ../../src/plone/restapi/tests/http-examples/aliases_delete.req
```

Response:

```{literalinclude} ../../src/plone/restapi/tests/http-examples/aliases_delete.resp
:language: http
```
1 change: 1 addition & 0 deletions docs/source/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ expansion
actions
workflow
workingcopy
aliases
locking
sharing
registry
Expand Down
45 changes: 44 additions & 1 deletion src/plone/restapi/services/aliases/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
from zope.publisher.interfaces import IPublishTraverse
from zope.component import getUtility
from plone.app.redirector.interfaces import IRedirectionStorage
import plone.protect.interfaces
from Products.CMFPlone.controlpanel.browser.redirects import absolutize_path
from zope.component import getMultiAdapter
from zExceptions import BadRequest
from plone.restapi import _
import plone.protect.interfaces


@implementer(IPublishTraverse)
Expand Down Expand Up @@ -73,3 +75,44 @@ def edit_for_navigation_root(self, alias):
alias = f"{extra}{alias}"
# Finally, return the (possibly edited) redirection
return alias


@implementer(IPublishTraverse)
class AliasesRootPost(Service):
"""Creates new aliases via controlpanel"""

def __init__(self, context, request):
super().__init__(context, request)

def reply(self):
data = json_body(self.request)
storage = getUtility(IRedirectionStorage)
aliases = data.get("aliases", [])

# Disable CSRF protection
if "IDisableCSRFProtection" in dir(plone.protect.interfaces):
alsoProvides(self.request, plone.protect.interfaces.IDisableCSRFProtection)

for alias in aliases:
redirection = alias["path"]
target = alias["redirect-to"]
abs_redirection, err = absolutize_path(redirection, is_source=True)
abs_target, target_err = absolutize_path(target, is_source=False)

if err and target_err:
err = f"{err} {target_err}"
elif target_err:
err = target_err
else:
if abs_redirection == abs_target:
err = _(
"Alternative urls that point to themselves will cause"
" an endless cycle of redirects."
)
if err:
raise BadRequest(err)

storage.add(abs_redirection, abs_target, manual=True)

self.request.response.setStatus(201)
return {"message": "Successfully added the aliases %s" % aliases}
25 changes: 25 additions & 0 deletions src/plone/restapi/services/aliases/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@
name="@aliases"
/>

<plone:service
method="GET"
accept="application/json,application/schema+json"
factory=".get.AliasesRootGet"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
permission="zope2.View"
name="@aliases"
/>

<plone:service
method="POST"
factory=".add.AliasesPost"
Expand All @@ -20,6 +29,14 @@
name="@aliases"
/>

<plone:service
method="POST"
factory=".add.AliasesRootPost"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
permission="cmf.ModifyPortalContent"
name="@aliases"
/>

<plone:service
method="DELETE"
factory=".delete.AliasesDelete"
Expand All @@ -28,4 +45,12 @@
name="@aliases"
/>

<plone:service
method="DELETE"
factory=".delete.AliasesDelete"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
permission="cmf.ModifyPortalContent"
name="@aliases"
/>

</configure>
20 changes: 20 additions & 0 deletions src/plone/restapi/services/aliases/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from zope.component import getUtility
from plone.app.redirector.interfaces import IRedirectionStorage
from zope.component.hooks import getSite
from Products.CMFPlone.controlpanel.browser.redirects import RedirectsControlPanel
from plone.restapi.serializer.converters import datetimelike_to_iso


@implementer(IPublishTraverse)
Expand All @@ -17,6 +19,24 @@ def reply(self):
return {"aliases": aliases}


@implementer(IPublishTraverse)
class AliasesRootGet(Service):
def reply(self):
"""
redirect-to - target
path - path
redirect - full path with root
"""
batch = RedirectsControlPanel(self.context, self.request).redirects()
redirects = [entry for entry in batch]

for redirect in redirects:
redirect["datetime"] = datetimelike_to_iso(redirect["datetime"])
self.request.response.setStatus(201)

return {"aliases": redirects}


def deroot_path(path):
"""Remove the portal root from alias"""
portal = getSite()
Expand Down

0 comments on commit 4023828

Please sign in to comment.