31.2. REST API endpoints [voting story]#
In this part you will:
Register and write a custom REST API service
Topics covered:
Extending plone.restapi
Services and endpoints
Check out mastering-plone-votable-add-on at tag behaviors:
git checkout behaviors
The code at the end of the chapter:
git checkout endpoints
More info in The code for the training
Implement the service#
So far, Volto has no access to the logic of the voting behavior from the previous chapter.
We need to create a REST API endpoint that can be addressed by GET, POST and DELETE requests.
The adapter ploneconf.votable.behaviors.votable.Votable has the logic needed for voting.
The key methods are votes to get the current votes, vote to actively cast a vote and clear to clear existing votes.
In backend/src/ploneconf/votable create a folder structure like the following:
services/
├── __init__.py
├── configure.zcml
├── votes.py
We include the new package services in the package's main configuration file backend/src/ploneconf/votable/configure.zcml:
1<include package=".browser" />
2<include package=".services" />
Now let's implement the services for the endpoint @votes in backend/src/ploneconf/votable/services/votes.py.
1from plone.protect.interfaces import IDisableCSRFProtection
2from plone.restapi.deserializer import json_body
3from plone.restapi.services import Service
4from ploneconf.votable.behaviors.votable import IVotable
5from zExceptions import Unauthorized
6from zope.interface import alsoProvides
7
8
9class VotingGet(Service):
10 """Get voting information about the current object"""
11
12 def reply(self):
13 return vote_info(self.context)
14
15
16class VotingPost(Service):
17 """Vote for an object"""
18
19 def reply(self):
20 alsoProvides(self.request, IDisableCSRFProtection)
21 voting = IVotable(self.context)
22 data = json_body(self.request)
23 vote = data["rating"]
24 voting.vote(vote)
25
26 return vote_info(self.context)
27
28
29class VotingDelete(Service):
30 """Clear votes for an object"""
31
32 def reply(self):
33 alsoProvides(self.request, IDisableCSRFProtection)
34 voting = IVotable(self.context)
35 voting.clear()
36 return vote_info(self.context)
37
38
39def vote_info(obj):
40 """Returns voting information about the given object."""
41 voting = IVotable(obj)
42 info = {
43 "average_vote": voting.average_vote(),
44 "total_votes": voting.total_votes(),
45 "has_votes": voting.has_votes(),
46 "already_voted": voting.already_voted(),
47 "can_vote": True,
48 "can_clear_votes": True,
49 }
50 return info
The GET service is highlighted.
If we look at the code, we see that the service inherits necessary properties from plone.restapi.services.Service by subclassing.
The reply method implements what should be returned on a GET request to endpoint @votes.
It uses the vote_info function to get the vote data from the behavior adapter.
Register the service#
How can the service be published as part of the Plone REST API? We will register the services for the behavior's marker interface.
With a registration in configure.zcml the endpoint is addressable.
1<configure
2 xmlns="http://namespaces.zope.org/zope"
3 xmlns:browser="http://namespaces.zope.org/browser"
4 xmlns:plone="http://namespaces.plone.org/plone"
5 i18n_domain="ploneconf.votable"
6 >
7
8 <plone:service
9 method="GET"
10 factory=".votes.VotingGet"
11 for="ploneconf.votable.behaviors.votable.IVotableMarker"
12 permission="zope2.View"
13 name="@votes"
14 />
15
16 <plone:service
17 method="POST"
18 factory=".votes.VotingPost"
19 for="ploneconf.votable.behaviors.votable.IVotableMarker"
20 permission="zope2.View"
21 name="@votes"
22 />
23
24 <plone:service
25 method="DELETE"
26 factory=".votes.VotingDelete"
27 for="ploneconf.votable.behaviors.votable.IVotableMarker"
28 permission="zope2.View"
29 name="@votes"
30 />
31
32</configure>
Note that all three services have the same name @votes, but will provide different functionality depending on the method of the request (GET, POST, or DELETE).
This is not required but is a convention many REST endpoints follow.
We could also give them different names based on their functionality.
The services are all only available on content that provides the behavior's marker interface, ploneconf.votable.behaviors.votable.IVotableMarker, which we explained in the previous chapter.
At this point we are not enforcing permissions for who can vote. Voting is available to anyone who has permission to view the content. We will add better permission checks in Permissions [voting story].
Test the service#
If you have an API client like Postman installed, you can access the new endpoint for testing.
Be sure to authenticate and add a header to accept application/json.