Apps and Plugins

Portal Apps allows anyone to extend the features and capabilities of Portal. We have enabled areas in Portal to be extended by implementing interfaces so that you can override or enhance the functionality of Portal to suit your needs. You create Apps by using the Plugin system.

../_images/plugin_searchresults.png ../_images/plugin_collections.png

The Portal plugin system is based upon a component architecture which creates functional interfaces and services which allow components to easily extend each others’ functionality. Each component can offer its own API by declaring extension points.

We have created extension points in Portal which are detailed in this documentation, we also create extension points in the theme to allow you to inject basic information and functionality into a page.

As well as this, Portal raises many events which can be used to trigger functionality decoupled from the main Portal code base. Signals are raised at key moments such as pre-saving of a record or post-saving, and Receivers are notified of such events to trigger extra functionality.

Creating Apps

Apps are created as Python modules (single .py files), or as Python packages (folders with __init__.py files) and are placed within the plugins/ directory. On startup Portal reads the plugin directory for modules and packages and loads them. Any code that is using Plugin Extensions will be placed into the PluginEnvironment.

It is important to note that the apps do have an impact on the running of the Portal framework, any code should raise the appropriate exceptions and die safely, otherwise the performance or running of Portal can be directly affected.

My First Portal App

Starting from Portal 2.0 there is a Portal App skeleton creator for easily creating Portal Apps. We will use this to create a basic App.

After logging into a system containing Portal we change to the directory that contains Portal and run a management command which creates the basic App skeleton from a template. We will call it myfirstportalapp:

cd /opt/cantemo/portal
./manage.py start_portal_app myfirstportalapp

Now there is a portal app in /opt/cantemo/portal/portal/plugins/myfirstportalapp

The app you created will contain the following files:

/opt/cantemo/portal/portal/plugins/myfirstportalapp
/opt/cantemo/portal/portal/plugins/myfirstportalapp/plugin.py
/opt/cantemo/portal/portal/plugins/myfirstportalapp/templates
/opt/cantemo/portal/portal/plugins/myfirstportalapp/templates/myfirstportalapp
/opt/cantemo/portal/portal/plugins/myfirstportalapp/templates/myfirstportalapp/navigation.html
/opt/cantemo/portal/portal/plugins/myfirstportalapp/templates/myfirstportalapp/index.html
/opt/cantemo/portal/portal/plugins/myfirstportalapp/urls.py
/opt/cantemo/portal/portal/plugins/myfirstportalapp/views.py
/opt/cantemo/portal/portal/plugins/myfirstportalapp/__init__.py
Now restart Portal Web::

systemctl restart portal-web.service

Now your app should be registered and the admin overview should look like this

../_images/myfirstapp.png

Now you may edit the source in /opt/cantemo/portal/portal/plugins/myfirstportalapp/ and start developing your app! You will see there are files there which use the plugin infrastructure to register the app, create some basic templates which add to the navigation and create a page.

Example code

As part of our commitment to developers we supply example code to github.

Please look at https://github.com/Cantemo

App Packaging

Simple Apps can be placed in a single file (python module) and placed within the plugins directory, and the plugin system will import each file and register the Plugin interface directly. More complex Apps, such as those adding functionality with additional pages can be placed in their own directory as a python package. The start_portal_app command creates an own directory for the basic files needed for a package.

The python package is also placed within the plugins directory, and must contain an __init__.py file which imports any module within the package so that the Plugin loader can register the plugin.

Both modules and packages can reference any library that the python library has access too as well as the Portal framework.

App Package Structure

The plugins will be stored in a directory called plugins, and under this each plugin will have its own directory.

  • plugins/

    • 050_plugin_name_example/

      • context_processors/

      • middleware_classes/

      • templates/

      • tests/

      • urls.py

      • models.py

    • 051_plugin_name_example/..

These directories can have a set structure so that the plugin architecture can find the plugins it needs for each section of the site.

The naming of the plugins will decide the order that they will be included.

App Registration

Optionally Apps can be registered so that they show up in the admin menu. This will allow the administrators to see what version of your App they are running, and for you to display your company name and optionally the website. For example in a plugins.py write the following:

import logging

from portal.pluginbase.core import Plugin, implements
from portal.generic.plugin_interfaces import IPluginURL, IPluginBlock, \
                                             IAppRegister

log = logging.getLogger(__name__)

class MyNewAppRegister(Plugin):

    implements(IAppRegister)

    def __init__(self):
        self.name = "My New App"
        self.plugin_guid = "1ff0ada4-a3a2-1aea-a7b1-6e5371975c56"
        log.debug('Registered My New App')

    def __call__(self):
        _app_dict = {
                'name': self.name,
                'version': versionnumber,
                'author': 'Codemill AB',
                'author_url': 'www.cantemo.com',
                'notes': 'Copyright 2012. All Rights Reserved'}
        return _app_dict

mynewappplugin = MyNewAppRegister()

This registers a new IAppRegister type plugin. It then returns information to the system. If you are copying the above then you want to change some of the information:

  • self.name - Change to the name of your plugin

  • self.plugin_guide - Change to a unique number.

  • version - The version number of this plugin

  • author - Your company name

  • author_url - Your website address.

  • notes - Any other text information you want to show up.

App Removal

If you are removing apps, make sure to also remove all the compiled .pyc files from the plugin directory.

Alternatively bundle a script which cleans the package away, removes the .pyc files and removes the database models (using database migrations.

App Database Integration

You can use the Django models functionality to create database tables for storing and retrieving data for you App. The Django documentation contains very good documentation on how to do this.

Database Migration

For Database migration you should use Django’s migration framework. Creating a migration is done using: manage.py makemigrations <app name> More information can be found here: https://docs.djangoproject.com/en/1.11/topics/migrations/

Apps and avoiding import errors

The Apps can be imported before the rest of the Portal code, therefore we need to put in place measures to stop any cyclic import problems from happen. It is recommended that imports are not put at the head of python modules, but imported within functions that need them:

from portal.pluginbase.core import *

from portal.utils.plugin_interfaces import IPluginBlock
from portal.generic.plugin_interfaces import ITemplateChooser

class MyBlockPlugIn(Plugin):
    """ PLUGIN EXAMPLE
        This plugin will return the first user in the system:
    """

    implements(IPluginBlock)

    def __init__(self):
        self.name = "myblock"

    def return_string(self, tagname, *args):
        from django.contrib.auth.models import User
        u = User.objects.get(pk=1)
        return {'guid':'9748872c-3310-11e1-9d7c-27a06e9d671c', \
                'template':u.username }

pluginblock1 = MyBlockPlugIn()

This App is potentially unsafe and might cause import errors:

from portal.pluginbase.core import *

from portal.utils.plugin_interfaces import IPluginBlock
from portal.generic.plugin_interfaces import ITemplateChooser
from django.contrib.auth.models import User

class MyBlockPlugIn(Plugin):
    """ PLUGIN EXAMPLE
        This plugin will return the first user in the system:
    """

    implements(IPluginBlock)

    def __init__(self):
        self.name = "myblock"

    def return_string(self, tagname, *args):
        u = User.objects.get(pk=1)
        return {'guid':'9748872c-3310-11e1-9d7c-27a06e9d671c', \
                'template':u.username }

pluginblock1 = MyBlockPlugIn()

Please make sure to follow that convention to make Apps import safely. A better version that checks for the existence of the user would be:

from portal.pluginbase.core import *

from portal.utils.plugin_interfaces import IPluginBlock
from portal.generic.plugin_interfaces import ITemplateChooser

class MyBlockPlugIn(Plugin):
    """ PLUGIN EXAMPLE
        This plugin will return the first user in the system:
    """

    implements(IPluginBlock)

    def __init__(self):
        self.name = "myblock"
        self.guid = "a85a9ec4-3310-11e1-92de-efb93402adcb"

    def return_string(self, tagname, *args):
        from django.contrib.auth.models import User
        try:
            u = User.objects.get(pk=1)

        except User.DoesNotExist:
            u = None

        return {'guid':self.guid, 'template': u}

pluginblock1 = MyBlockPlugIn()

Apps in other development languages.

This is out of scope for this document, but Python’s support for creating modules in other languages and having inter-operability is very good.

For extended system support please contact us for our integration with messages queues in particular Redis, which can be used by other services using other languages to provide extra functionality.

Development Reference

More information on different areas of Portal for development.

Vidispine Integration