MediaDrop Plugin Basics


This post should explain the core principles of building a new MediaDrop plugin to (potential) developers. The contents here require only very little Python knowledge though for implementing any interesting functionality afterwards you'll need to write some Python code.

If you only want to install a pre-existing plugin as an administrator, please read the post Operators Guide to MediaDrop Plugins which explains all the necessary steps.

What is a plugin?

A "plugin" is a collection of code (Python, JavaScript), templates and static resources (CSS, images and other static files) which extend the workings/looks of MediaDrop. Depending on what the plugin should do exactly it might contain only some of the parts above. For example a new player plugin might only have a bit of Python Code, some static CSS/JavaScript but not templates.

The advantage of building a plugin (over modifying the MediaDrop source code itself) is that your changes are more isolated. Therefore it's usually easier to understand (and hence debug/develop) your plugin but usually it is also much easier to update MediaDrop source code to the latest version as the plugin API is more stable than the internal workings of the main software.


Basic Ideas of MediaDrop plugins

Plugins usually follow certain conventions with regards to their directory structure. If you follow these implicit rules you can save yourself some boilerplate code. When it comes to Python your code usually observes events (e.g. HTTP request to a certain page, media status changed, ...) or registers components which implements some well-known interface (e.g. a new player or storage engine).

Events and components will be explained more in detail in a later blog post.

Directory Structure

Each plugin has something like a "main plugin directory". Basically that's just a Python package (a folder with a file named __init__.py). Where this "main plugin directory" is exactly, depends on your setup.py (see "Plugin Discovery / Activation") but for now let just start with any folder.

MediaDrop Plugin Directory Structure

The structure above is pretty typical for a plugin. Please note that you can omit any of these files and folders if your plugin does not need them. Usually plugins need only some of the items. Instead of using a Python package for "forms" you can also use a simple .py file (if you have only few forms).

Please note that this structure is only a convention. In very complex situations you can also provide your own Plugin class which tells MediaDrop to look for controllers, templates, and static files in different places. However that is outside the scope of this introductory blog post. Use the source, Luke!

controllers.py contains one or more controllers which you can use to add completely new pages to MediaDrop. If you build a big plugin with multiple controllers you can also create a package named "controllers" and omit the controllers.py instead.

The "public" directory contains all your static resources, for example JavaScript, CSS, images, flash files. Of course you can also create subfolders like "js" or "css" to organize your stuff.

The "templates" folder contains – surprise – template files (Genshi templating language). If you want to create additional pages in MediaDrop you can reference these templates from your controllers. When you create a template with the exact same path + file name as a preexisting template in MediaDrop this will be treated as "match template". It'll be loaded in addition to MediaDrop's own template and can tweak the default template by adding or removing stuff from the DOM. If you're interested in that please read up on Genshi's py:match templates until we have further documentation on that topic.

If you want to add new tables to the database you'll need also a "migrations" folder. "migrations" are Python scripts (using the alembic library) to alter the database schema. If you use migrations you can keep your DB structure in sync with your plugin code. Also MediaDrop itself relies on alembic in case the database needs to be altered between versions in any way. The easiest way to create these files is using the "alembic init" command. Please resort to the alembic documentation and the "SEO" plugin until we create more detailed documentation.

Technically mediadrop_plugin.py it not really needed. I use it for the main plugin entry point (see below). Importing this module will start all the necessary event observers and component registrations. The reason why I add that to mediadrop_plugin.py (instead of myplugin/__init__.py) is that in unit tests I can import the "myplugin" package without modifying any observers etc.

How does MediaDrop find your plugin? (Plugin Discovery / Activation)

As explained above one goal of using MediaDrop plugins is not to touch the actual source code. Therefore the plugin code is not copied into your application (like it is done for Django "apps"). Instead MediaDrop utilizes setuptools and entry points to find plugins.

First you create a file named setup.py in your root folder (alongside with the "myplugin" directory). That file is just a standard setup.py (with a special entry point declaration) which you might know from other Python libraries. Just in case you're not familiar with it, here is a complete example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python
from setuptools import setup, find_packages

setup(
    name='SamplePlugin',
    version='1.0.1',

    author='YOUR NAME',
    author_email='you@site.example',
    license='GPL v3 or later',
    install_requires = [
        'MediaDrop >= 0.11dev', # use whatever version you support
    ],

    include_package_data=True,
    # only necessary if you use namespace packages
    # namespace_packages = ['mediadropext'],
    packages=find_packages(),
    entry_points = {
        'mediadrop.plugin': [
            'myplugin = myplugin.mediadrop_plugin',
        ],
    }
)

Most of the information can be changed to whatever you like. One important thing is the entry point declaration. MediaDrop can only find your plugin if you use the entry point "mediadrop.plugin". The second important thing is the plugin id. In the example above this is myplugin because of the line myplugin =.

On startup MediaDrop will import the referenced package (myplugin.mediadrop_plugin) and you should ensure that this module imports all the packages which register observers and components.

For MediaDrop plugins I really like to use namespace packages because they reduce the likelihood of name clashes and avoid the need of long package names with underscores (e.g. mediadropext.offline_sitemaps instead of mediadrop_offline_sitemaps). It is totally optional to use namespace packages but I consider them a "best practice". Just one warning: Do not use the "mediadropext" package name if you don't use namespace packages!

See it in action

In order to actually use your custom plugin you need to install it which basically means you run python setup.py develop when your virtualenv is active. For more information on installation and debugging please read the Operators Guide to MediaDrop Plugins.

Plugin Skeleton

To help you getting started I created a plugin skeleton (download) which you can use to get your plugin started. Just one question: Please replace the references to "myplugin" with a name of your own.