URL Plugins

URL plugins allows a plugin to extend the URLS schema used by Portal to enable extra functionality. You can then invoke your own code and views from inside your plugin.

IPluginURL

class portal.generic.plugin_interfaces.IPluginURL

IPluginURL plugins are loaded by the URL dispatcher, adding to the URL configuration of Portal.

Requires:
  • __init__() method that defines:

  • name - human readable name for the plugin

  • urls - string pointing to a urls.py package

  • urlpattern - root URL to use for the end-points

  • namespace - Django view namespace for the registered endpoints

  • include_in_api_docs (optional) - if set to True the end-points are included in REST API Reference

A simple example in a file called plugin.py that would matchup to a urls.py file in your plugin package:

from portal.pluginbase.core import Plugin
from portal.generic.plugin_interfaces import IPluginURL

class MyURLPlugIn(Plugin):
    """ Adds extra URLS for the testapp plugin.
    """
    implements(IPluginURL)

    def __init__(self):
        self.name = "My URL Plugin"
        self.urls = 'portal.plugins.testapp.urls'
        self.urlpattern = r'^testapp/'
        self.namespace = 'testapp'

pluginurls = MyURLPlugIn()

That plugin would load urls from portal.plugins.testapp.urls, a urls.py file in a folder called testapp within the plugins installation directory. As with normal python packages an __init__.py should be created in the testapp folder. Within this, import the file containing the plugin so that the plugin system is aware of the Plugin contained in plugin.py:

from portal.plugin import *

urls.py is a normal Django URLconf, for example:

from django.conf.urls.defaults import *
portal.plugins.testapp.views import test_view

 urlpatterns = [
    url(r'^/$', test_view, {'message': 'sample root page'}, name='sample-root'),
    url(r'^test/$', test_view, kwargs={'message': 'sample test page'}, name='sample-settings'),
]

These urlpatterns use testapp.views (views.py in your testapp folder) to dispatch the HttpResponse. More information on views and urls can be obtained from the Django documentation, https://docs.djangoproject.com/

Including plugin end-points in REST API Reference

Note

This feature is a beta, meaning backwards incompatible changes may be introduced in future releases.

The optional include_in_api_docs parameter in IPluginURL makes the registered URLs show up in Cantemo REST API Reference (e.g. https://<your Cantemo URL>/APIdoc/). The end-points can also be interacted with from that documentation view.

Note that only portal.generic.baseviews.CView or rest_framework.views.APIView based views are included in the documentation.

An example, plugin.py:

from portal.generic.plugin_interfaces import IPluginURL
from portal.pluginbase.core import Plugin
from portal.pluginbase.core import implements


class ApiDocTestPluginURL(Plugin):
    implements(IPluginURL)

    def __init__(self):
        self.name = "APIdoc test URL plugin"
        self.urls = "portal.plugins.api_doc_test.urls"
        self.urlpattern = "^API/v2/api_doc_test/"
        self.namespace = "api_doc_test"
        self.plugin_guid = "372e47a5-74a2-4821-a388-d3c4458ddda2"
        self.include_in_api_docs = True


ApiDocTestPluginURL()

This add alls the URLs from the Python package portal.plugins.api_doc_test.urls to the system, available under API/v2/api_doc_test/, and also includes them in the documentation.

This urls.py registers two views. Note the use of the more modern path() instead of url():

from django.urls import path

from .views import IndexView
from .views import InstanceView

urlpatterns = [
    path("", IndexView.as_view(), name="index"),
    path("instance/<instance_id>/", InstanceView.as_view(), name="instance"),
]

This is views.py implementation:

from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response

from portal.generic.baseviews import CView


class IndexView(CView):
    renderer_classes = (JSONRenderer,)

    def get(self, request):
        """
        Get the API doc test index
        """
        return Response({"detail": "This is the index"})


class InstanceView(CView):
    renderer_classes = (JSONRenderer,)

    def get(self, request, instance_id):
        """
        Get "data" for a single instance.
        """
        return Response({"data": f"This is data for instance {instance_id}", "id": instance_id})

    def put(self, request, instance_id):
        """
        Update data on an instance

        Does not actually store the data. Any JSON sent in the request is returned.

        ---
        parameters:
          - name: body
            description: A JSON object for updated data on the instance
            type: object
            required: true
            paramType: body
        """
        return Response({"detail": f"Updated data for instance {instance_id}", "data": request.data, "id": instance_id})

Docstrings from the get/put functions are included in the documentation. Path parameters are included in the documentation as arguments automatically, others (body, query) can be described in the docstring as shown above.