--- html_meta: "description": "" "property=og:description": "" "property=og:title": "" "keywords": "" --- (endpoints-mastering-label)= # Endpoints ````{sidebar} Plone Frontend Chapter ```{figure} _static/plone-training-logo-for-frontend.svg :alt: Plone frontend :align: left :class: logo ``` Get the code! ({doc}`More info `) Code for the beginning of this chapter: ```shell git checkout TODO tag to checkout ``` Code for the end of this chapter: ```shell git checkout TODO tag to checkout ``` ```` 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 {ref}`viewlets2-label`). But neither the adapter nor the viewlet is directly accessible by a react frontend. In {file}`backend/src/starzel.votable_behavior/starzel/votable_behavior/` create a folder {file}`restapi` with a empty {file}`__init__.py`. In that new folder create a {file}`configure.zcml` where you will register the endpoints. Don't forget to register the new file in the packages' main {file}`configure.zcml`: ```{code-block} xml :emphasize-lines: 2 :linenos: ``` Now register the endpoints you plan to write in {file}`restapi/configure.zcml`: ```{code-block} xml :linenos: ``` 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 {file}`voting.py` and write the services that together make the endpoint `@votes`: ```python # -*- 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`: