An author writing their newest novel using a vintage typewriter
By Érico Andrei

Addressing the Missing Author Information in Plone 6 with Volto

One of the limitations I've noticed in Plone 6 when using Volto is the absence of detailed author information in the content returned by plone.restapi. Currently, the content GET service only provides the author's ID, making it challenging to display a comprehensive author "signature" on the content. In this blog post, I'll walk you through a simple workaround I've implemented to solve this issue.

Introducing the Authors Service

To address this limitation, I've introduced a new service called Authors. This service is accessible via the @authors endpoint and returns essential details about the content's author.

Create the Authors Service

Firstly, in the backend code, create a new folder named services/authors. Inside this folder, add a new Python file called get.py with the following content:

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 implementer
from zope.interface import Interface


DEFAULT_USER = "Érico Andrei"


@implementer(IExpandableElement)
@adapter(Interface, Interface)
class Authors:
    def __init__(self, context, request):
        self.context = context
        self.request = request

    def __call__(self, expand=True):
        result = {"authors": {"@id": f"{self.context.absolute_url()}/@authors"}}
        if not expand:
            return result

        portal_url = api.portal.get().absolute_url()
        data = []
        authors = self.context.creators
        for author_username in authors:
            user = api.user.get(username=author_username)
            author_info = {
                "@id": f"{portal_url}/author/{author_username}",
                "fullname": DEFAULT_USER,
            }
            if user:
                fullname = user.getProperty("fullname")
                author_info["fullname"] = fullname or DEFAULT_USER
            data.append(author_info)
        return {"authors": data}


class AuthorsGet(Service):
    def reply(self):
        authors = Authors(self.context, self.request)
        return authors(expand=True)["authors"]

Register the New Endpoint

Next, you'll need to register this new endpoint. Add the following code to a new file services/authors/configure.zcml:

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

  <adapter
      factory=".get.Authors"
      name="authors"
      />

  <plone:service
      method="GET"
      factory=".get.AuthorsGet"
      for="zope.interface.Interface"
      permission="zope2.View"
      name="@authors"
      />

  <cache:ruleset
      for=".get.AuthorsGet"
      ruleset="plone.content.dynamic"
      />

</configure>

Also, you need to register the authors package in the services/configure.zcml file

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

  <include package=".authors" />

</configure>

After restarting your Plone instance, you can test the new service endpoint by making a request to mysite.com/++api++/<posturl>/@authors. You should receive a response like this:

[
  {
    "@id": "https://ericof.com/author/ericof", 
    "fullname": "Erico Andrei"
  }
]

Voila! It's that simple.

Enhancements

While this approach works, it does require an additional HTTP request to fetch the author's information, which is not ideal. Fortunately, plone.restapi offers a feature called Content Expanders to mitigate this.

In your services/authors/get.py file, line 12, we register the Authors class as an IExpandableElement. This allows the class to be invoked internally by the content endpoint. Now, a request to mysite.com/++api++/<posturl>?expand=authors will also include the author's information under @components.authors in the content response.

{
  "@components": {
    "authors": [
      {
        "@id": "https://ericof.com/author/ericof", 
        "fullname": "Erico Andrei"
      }
    ], 
  }, 
  "@id": "https://ericof.com/en/posts/2023/addressing-the-missing-author-information-in-plone-6-with-volto", 
  "@type": "Post"
  }

To make Volto utilize this content expander by default, register it under config.settings.apiExpanders like so:

const applyConfig = (config) => {
  config.settings = {
    ...config.settings,
    apiExpanders: [
      ...config.settings.apiExpanders,
      {
        match: '',
        GET_CONTENT: [
          'breadcrumbs',
          'navigation',
          'actions',
          'authors',
          'types',
        ],
        querystring: {
          'expand.navigation.depth': 2,
        },
      },
    ]
  };
}

Conclusion

While I anticipate that the Volto team will likely introduce a more elegant solution in the future, this workaround has served me well in numerous projects. It's a practical way to include author information without waiting for an official update.