Endpoints
46. Endpoints#
To be solved task in this part:
Grant access to voting for the Volto frontend
In this part you will:
Register and write a custom endpoint
Topics covered:
Extending plone.restapi
Services and Endpoints
Out of the box Volto has no access to the logic for voting created in the last chapter.
You need to extend a endpoint that can be used by GET, POST and DELETE requests.
The adapter starzel.votable_behavior.behavior.voting.Vote
has the logic needed for voting, the key features are votes
to get the current votes, vote
to actively cast a vote and clear
to clear existing votes.
For the classic frontend this api is exposed in a Viewlet (see chapter A Viewlet for the Votable Behavior). But neither the adapter nor the viewlet is directly accessible by a react frontend.
In backend/src/starzel.votable_behavior/starzel/votable_behavior/
create a folder restapi
with a empty __init__.py
.
In that new folder create a configure.zcml
where you will register the endpoints.
Don't forget to register the new file in the packages' main configure.zcml
:
1<include package=".browser" />
2<include package=".restapi" />
Now register the endpoints you plan to write in restapi/configure.zcml
:
1<configure
2 xmlns="http://namespaces.zope.org/zope"
3 xmlns:plone="http://namespaces.plone.org/plone"
4 xmlns:zcml="http://namespaces.zope.org/zcml">
5
6 <plone:service
7 method="GET"
8 name="@votes"
9 for="starzel.votable_behavior.interfaces.IVotable"
10 factory=".voting.Votes"
11 permission="zope2.View"
12 />
13
14 <plone:service
15 method="POST"
16 name="@votes"
17 for="starzel.votable_behavior.interfaces.IVotable"
18 factory=".voting.Vote"
19 permission="zope2.View"
20 />
21
22 <plone:service
23 method="DELETE"
24 name="@votes"
25 for="starzel.votable_behavior.interfaces.IVotable"
26 factory=".voting.Delete"
27 permission="zope2.View"
28 />
29
30</configure>
Note that are all have the same name @votes
but will provide different functionality depending on the method of the request.
This is not required but a convention many endpoints follow.
We could also name them mnore in sync with their functionality.
In our example the permission-checks are delegated to the services themselves and we use zope2.View
as permission.
The services are all only available on content that provides the marker-interface starzel.votable_behavior.interfaces.IVotable
that we added in the last chapter via a behavior.
Now create the voting.py
and write the services that together make the endpoint @votes
:
# -*- coding: utf-8 -*-
from plone import api
from plone.protect.interfaces import IDisableCSRFProtection
from plone.restapi.deserializer import json_body
from plone.restapi.services import Service
from starzel.votable_behavior import DoVote
from starzel.votable_behavior.interfaces import IVoting
from zope.globalrequest import getRequest
from zExceptions import Unauthorized
from zope.interface import alsoProvides
class Vote(Service):
"""Vote for an object"""
def reply(self):
alsoProvides(self.request, IDisableCSRFProtection)
can_vote = not api.user.is_anonymous() and api.user.has_permission(DoVote, obj=self.context)
if not can_vote:
raise Unauthorized("User not authorized to vote.")
voting = IVoting(self.context)
data = json_body(self.request)
vote = data['rating']
voting.vote(vote, self.request)
return vote_info(self.context, self.request)
class Delete(Service):
"""Unlock an object"""
def reply(self):
alsoProvides(self.request, IDisableCSRFProtection)
can_vote = not api.user.is_anonymous() and api.user.has_permission(DoVote, obj=self.context)
if not can_vote:
raise Unauthorized("User not authorized to delete votes.")
voting = IVoting(self.context)
voting.clear()
return vote_info(self.context, self.request)
class Votes(Service):
"""Voting information about the current object"""
def reply(self):
return vote_info(self.context, self.request)
def vote_info(obj, request=None):
"""Returns voting information about the given object."""
if not request:
request = getRequest()
voting = IVoting(obj)
can_vote = not api.user.is_anonymous() and api.user.has_permission(DoVote, obj=obj)
can_clear_votes = any(role in api.user.get_roles() for role in ['Manager', 'Site Manager'])
info = {
'average_vote': voting.average_vote(),
'total_votes': voting.total_votes(),
'has_votes': voting.has_votes(),
'already_voted': voting.already_voted(request),
'can_vote': can_vote,
'can_clear_votes': can_clear_votes,
}
return info
This endpoint is modeled similar to the locking endpoint of plone.restapi
: https://github.com/plone/plone.restapi/blob/master/src/plone/restapi/services/locking/locking.py