Writing a custom service for Plone REST API

Plone REST API expand

Plone REST API services allow you to use the Expansion functionionality, to reduce the request on the client side. This way you can include additional informations, you want to show together with your main content in one single response. The Plone REST API already provides some useful services like the Breadcrumb service. Together with the Expansion flag, you can easily expand specific service informations into the main response.

For details about the Expension function see here: https://plonerestapi.readthedocs.io/en/latest/expansion.html

Implementation

We create the following structure in our Plone package:

services/
├── configure.zcml
├── __init__.py
└── related_articles
    ├── configure.zcml
    ├── get.py
    ├── __init__.py

In the services folder, we have this configure.zcml

<configure xmlns="http://namespaces.zope.org/zope">

  <include package=".related_articles" />

</configure>

also add this line to the parent configure.zcml:

<include package=".services" />

to register the actual service, we need the following zcml config inside the related_articles folder:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:plone="http://namespaces.plone.org/plone">

  <adapter factory=".get.RelatedArticles" name="related-articles"/>

  <plone:service
    method="GET"
    for="zope.interface.Interface"
    factory=".get.RelatedArticlesGet"
    name="@related-articles"
    permission="zope2.View"
    />

</configure>

The service code looks as follow:

get.py

# -*- coding: utf-8 -*-
from plone import api
from plone.restapi.interfaces import IExpandableElement
from plone.restapi.services import Service
from zope.component import adapter
from zope.interface import Interface
from zope.interface import implementer


@implementer(IExpandableElement)
@adapter(Interface, Interface)
class RelatedArticles(object):

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

    def __call__(self, expand=False):
        result = {
            'related-articles': {
                '@id': '{}/@related-articles'.format(
                    self.context.absolute_url(),
                ),
            },
        }
        if not expand:
            return result

        solution_categories = [c for c in self.context.solution_category]
        article_brains = api.content.find(
            portal_type="Chapter",
            solution_category={
                'query': solution_categories,
                'operator': 'and',
            }
        )
        items = []
        for article in article_brains:
            items.append({
                'title': article.Title,
                '@id': article.getURL(),
            })

        result['related-articles']['items'] = items
        return result


class RelatedArticlesGet(Service):

    def reply(self):
        related_articles = RelatedArticles(self.context, self.request)
        return related_articles(expand=True)['related-articles']

The service is using the plone.api to find all related objects of the portal_type Chapter and createds a list of dicts with the @id and title of each Chapter. Together with the Expansion flag, the client can expand this as follow:

http://localhost:7080/Plone/some-content?expand=related-articles

You will find the expanded service info inside of the @component section:

"@components": {
    "actions": {
        "@id": "http://localhost:7080/Plone/some-content/@actions"
    },
    "breadcrumbs": {
        "@id": "http://localhost:7080/Plone/some-content/@breadcrumbs"
    },
    "navigation": {
        "@id": "http://localhost:7080/Plone/some-content/@navigation"
    },
    "related-articles": {
        "@id": "http://localhost:7080/Plone/some-content/@related-articles",
        "items": [{
            "@id": "http://localhost:7080/Plone/articles/harvesting",
            "title": "Harvesting"
        }]
    },
    "workflow": {
        "@id": "http://localhost:7080/Plone/some-content/@workflow"
    }
},
By @MrTango in