In the mediagoblin repository, there's a sample plugin under mediagoblin/plugins/sampleplugin that you can use to get started with plugin creation. The simplest way to get up and running is to
- Follow HackingHowto#How_to_set_up_and_maintain_an_environment_for_hacking_with_virtualenv to set up a local virtualenv mediagoblin instance
- run it with ./lazyserver.sh so you get debug output (if you run celeryd separately, you won't see logging.info from stuff run in celery tasks)
- cp -r mediagoblin/plugins/sampleplugin mediagoblin/plugins/myplugin (where "myplugin" is your plugin name)
- edit mediagoblin.ini and add [[mediagoblin.plugins.myplugin]] under the [plugins] section to enable your plugin
Now you can look at e.g. other core or non-core plugins for inspiration. See Available Plugins for non-core plugins.
The way you change things in plugins is by use of hooks. At certain points in the mediagoblin code, a function will say "run all hooks with name XYZ", and if you've defined a hook with such a name in a plugin you've enabled, it'll get run there. The documentation on Plugin API is your friend for defining hooks. The sample plugin shows a use of the 'setup' hook.
Note: templates are hooked with a call to pluginapi.register_template_hooks, instead of adding to the hooks variable.
Making an installable plugin
If you followed the steps above, you can use your plugin by copying it into the plugins folder; however, to get a plugin that is easily installable by users (e.g. with pip install myplugin), it should have a certain folder layout.
A good example is the RDFa plugin. This uses a setup.py file to install the files under the mediagoblin_rdfa folder into the lib/ folder of your mediagoblin installation. If you've checked out both mediagoblin and mg-rdfa in the same folder, e.g. ~/src/, you can do
cd ~/src/mg-rdfa/ ../mediagoblin/bin/python setup.py build ../mediagoblin/bin/python setup.py install
to install it to your mediagoblin instance.
The file layout of the repo is:
setup.py MANIFEST.in README.md mediagoblin_rdfa/ __init.py__ templates/ mediagoblin/ plugins/ rdfa/ metadata.html
The setup.py file defines an option include_package_data=True, which makes it read the file MANIFEST.in; MANIFEST.in contains rules for which files to include when installing. In this case, rule recursive-include mediagoblin_rdfa *.html makes it include the HTML template file (and any other you put under mediagoblin_rdfa), retaining the folder layout.
Referencing the mediagoblin test set in a plugin
The tests in mediagoblin define a nice "test_app" and functions for logging in and posting and so on. You can use this in your installable plugin.
From your plugin (assuming a layout like mg-rdfa described above), doing
cp -r ../mediagoblin/mediagoblin/tests .
lets you run
../mediagoblin/bin/py.test tests --boxed
In fact, the only files you need are `conftest.py` and `pytest.ini`, and then you can copy over a single `test_foo.py` (only making sure import statements are absolute) and it should run fine.
You also need to call setup_plugins() in your setup() function for your plugin to be properly loaded before testing.
An example of this test method is in the "hidden original" plugin, which subclasses the submission tests.
See also Writing unit tests for plugins in the docs.
Translating / Localising plugins
How do we do Translations in plugins?
(At least, a plugin would have to install a .mo file in e.g. lib/python2.7/site-packages/pluginname-0.1.3-py2.7.egg/pluginname/i18n/nn_NO/LC_MESSAGES/pluginname.mo (with domain "pluginname"?). Then mediagoblin would have to either discover that the file exists when setting up the plugin, or the plugin would have to add that to some list in some hook.
mg_globals.py seems to create a gettext variable pointing at mediagoblin/i18n:
thread_scope.translations = gettext.translation( 'mediagoblin', pkg_resources.resource_filename( 'mediagoblin', 'i18n'), ['en'], fallback=True)
which is installed into the jinja template in template.py:
template_env.install_gettext_callables( mg_globals.thread_scope.translations.ugettext, mg_globals.thread_scope.translations.ungettext)
in a function called by app.py:
request.template_env = template.get_jinja_env( self.template_loader, request.locale)
Making database models
See SQLAlchemy Tips