This package is built to be extended. You can either use the Zope Component Architecture and provide an specific Adapter to control what is being returned by the API or you simply write your own route provider.
This section will show how to build a custom route provider for an example content type. It will also show how to write and register a custom data adapter for this content type. It is even possible to customize how the fields of a specific content type can be accessed or modified.
Each route provider shipped with this package, provides the basic CRUD functionality to get, create, delete and update the resource handled.
The same functionality can be used to provide this behavior for custom content types. All neccessary functions are located in the api module within this package.
# CRUD
from plone.jsonapi.routes.api import get_batched
from plone.jsonapi.routes.api import create_items
from plone.jsonapi.routes.api import update_items
from plone.jsonapi.routes.api import delete_items
# route dispatcher
from plone.jsonapi.core.browser.router import add_route
# GET
@add_route("/todos", "todos", methods=["GET"])
@add_route("/todos/<string:uid>", "todos", methods=["GET"])
def get(context, request, uid=None):
""" get all todos
"""
return get_batched("Todo", uid=uid, endpoint="todo")
New in version 0.3: The standard GET route returns now the results for all resoures batched. This behavior is provided by the get_batched function.
Note
The prior get_items function, which returns all items in an array, is still provided, but not recommended due to performance issues.
The upper example registers a function named get with the add_route decorator. This ensures that this function gets called when the /todos route is called, e.g. http://localhost:8080/Plone/@@API/todo.
The second argument of the decorator is the endpoint, which is kind of the registration key for our function. The last argument is the methods we would like to handle here. In this case we’re only interested in GET requests.
All route providers get always the context and the request as the first two arguments. The uid keyword argument is passed in, when a UID was appended to the URL, e.g http://localhost:8080/Plone/@@API/todo/a3f3f9efd0b4df190d16ea63d.
The get_batched function we call inside our function will do all the heavy lifting for us. We simply need to pass in the portal_type as the first argument, the UID and the endpoint.
To be able to create, update and delete our Todo content type, it is neccessary to provide the following functions as well. The behavior is analogue to the upper example but as there is no need for batching, the functions return a Python <list> instead of a complete mapping as above.
# CREATE
@add_route("/todos/create", "todos_create", methods=["POST"])
@add_route("/todos/create/<string:uid>", "todos_create", methods=["POST"])
def create(context, request, uid=None):
""" create todos
"""
items = create_items("Todo", uid=uid, endpoint="todos")
return {
"url": url_for("todos_create"),
"count": len(items),
"items": items,
}
# UPDATE
@add_route("/todos/update", "todos_update", methods=["POST"])
@add_route("/todos/update/<string:uid>", "todos_update", methods=["POST"])
def update(context, request, uid=None):
""" update todos
"""
items = update_items("Todo", uid=uid, endpoint="todos")
return {
"url": url_for("todos_update"),
"count": len(items),
"items": items,
}
# DELETE
@add_route("/todos/delete", "todos_delete", methods=["POST"])
@add_route("/todos/delete/<string:uid>", "todos_delete", methods=["POST"])
def delete(context, request, uid=None):
""" delete todos
"""
items = delete_items("Todo", uid=uid, endpoint="todos")
return {
"url": url_for("todos_delete"),
"count": len(items),
"items": items,
}
The data returned by the API for each content type is extracted by the IInfo Adapter. This Adapter simply extracts all field values from the content.
To customize how the data is extracted from the content, you have to register an adapter for a more specific interface on the content.
This adapter has to implement the IInfo interface.
from plone.jsonapi.routes.interfaces import IInfo
class TodoAdapter(object):
""" A custom adapter for Todo content types
"""
interface.implements(IInfo)
def __init__(self, context):
self.context = context
def to_dict(self):
return {} # whatever data you need
def __call__(self):
# just implement it like this, don't ask x_X
return self.to_dict()
Register the adapter in your configure.zcml file for your special interface:
<configure
xmlns="http://namespaces.zope.org/zope">
<!-- Adapter for my custom content type -->
<adapter
for="plone.todo.interfaces.ITodo"
factory=".adapters.TodoAdapter"
/>
</configure>
The data sent by the API for each content type is set by the IDataManager Adapter. This Adapter has a simple interface:
class IDataManager(interface.Interface):
""" Field Interface
"""
def get(name): """ Get the value of the named field with
"""
def set(name, value):
""" Set the value of the named field
"""
To customize how the data is set to each field of the content, you have to register an adapter for a more specific interface on the content. This adapter has to implement the IDataManager interface.
Important
Please be aware that you have to implement security for field level access by your own.
from zope.annotation import IAnnotations
from persistent.dict import PersistentDict
from plone.jsonapi.routes.interfaces import IDataManager
class TodoDataManager(object):
""" A custom data manager for Todo content types
"""
interface.implements(IDataManager)
def __init__(self, context):
self.context = context
@property
def storage(self):
return IAnnotations(self.context).setdefault('plone.todo', PersistentDict())
def get(self, name):
self.storage.get("name")
def set(self, name, value):
self.storage["name"] = value
Register the adapter in your configure.zcml file for your special interface:
<configure
xmlns="http://namespaces.zope.org/zope">
<!-- Adapter for my custom content type -->
<adapter
for="plone.todo.interfaces.ITodo"
factory=".adapters.TodoDataManager"
/>
</configure>