aboutsummaryrefslogtreecommitdiffstats
path: root/docs/source/pluginwriter
diff options
context:
space:
mode:
Diffstat (limited to 'docs/source/pluginwriter')
-rw-r--r--docs/source/pluginwriter/api.rst274
-rw-r--r--docs/source/pluginwriter/database.rst9
-rw-r--r--docs/source/pluginwriter/media_type_hooks.rst38
-rw-r--r--docs/source/pluginwriter/quickstart.rst6
-rw-r--r--docs/source/pluginwriter/tests.rst64
5 files changed, 385 insertions, 6 deletions
diff --git a/docs/source/pluginwriter/api.rst b/docs/source/pluginwriter/api.rst
index df933511..29adb691 100644
--- a/docs/source/pluginwriter/api.rst
+++ b/docs/source/pluginwriter/api.rst
@@ -11,6 +11,7 @@
Dedication along with this software. If not, see
<http://creativecommons.org/publicdomain/zero/1.0/>.
+.. _plugin-api-chapter:
==========
Plugin API
@@ -23,7 +24,27 @@ Authors are encouraged to develop plugins and work with the
MediaGoblin community to keep them up to date, but this API will be a
moving target for a few releases.
-Please check the release notes for updates!
+Please check the :ref:`release-notes` for updates!
+
+
+How are hooks added? Where do I find them?
+-------------------------------------------
+
+Much of this document talks about hooks, both as in terms of regular
+hooks and template hooks. But where do they come from, and how can
+you find a list of them?
+
+For the moment, the best way to find available hooks is to check the
+source code itself. (Yes, we should start a more official hook
+listing with descriptions soon.) But many hooks you may need do not
+exist yet: what to do then?
+
+The plan at present is that we are adding hooks as people need them,
+with community discussion. If you find that you need a hook and
+MediaGoblin at present doesn't provide it at present, please
+`http://mediagoblin.org/pages/join.html <talk to us>`_! We'll
+evaluate what to do from there.
+
:mod:`pluginapi` Module
-----------------------
@@ -48,3 +69,254 @@ example might look like::
This means that when people enable your plugin in their config you'll
be able to provide defaults as well as type validation.
+You can access this via the app_config variables in mg_globals, or you
+can use a shortcut to get your plugin's config section::
+
+ >>> from mediagoblin.tools import pluginapi
+ # Replace with the path to your plugin.
+ # (If an external package, it won't be part of mediagoblin.plugins)
+ >>> floobie_config = pluginapi.get_config('mediagoblin.plugins.floobifier')
+ >>> floobie_dir = floobie_config['floobie_dir']
+ # This is the same as the above
+ >>> from mediagoblin import mg_globals
+ >>> config = mg_globals.global_config['plugins']['mediagoblin.plugins.floobifier']
+ >>> floobie_dir = floobie_config['floobie_dir']
+
+A tip: you have access to the `%(here)s` variable in your config,
+which is the directory that the user's mediagoblin config is running
+out of. So for example, your plugin may need a "floobie" directory to
+store floobs in. You could give them a reasonable default that makes
+use of the default `user_dev` location, but allow users to override
+it, like so::
+
+ [plugin_spec]
+ floobie_dir = string(default="%(here)s/user_dev/floobs/")
+
+Note, this is relative to the user's mediagoblin config directory,
+*not* your plugin directory!
+
+
+Context Hooks
+-------------
+
+View specific hooks
++++++++++++++++++++
+
+You can hook up to almost any template called by any specific view
+fairly easily. As long as the view directly or indirectly uses the
+method ``render_to_response`` you can access the context via a hook
+that has a key in the format of the tuple::
+
+ (view_symbolic_name, view_template_path)
+
+Where the "view symbolic name" is the same parameter used in
+``request.urlgen()`` to look up the view. So say we're wanting to add
+something to the context of the user's homepage. We look in
+mediagoblin/user_pages/routing.py and see::
+
+ add_route('mediagoblin.user_pages.user_home',
+ '/u/<string:user>/',
+ 'mediagoblin.user_pages.views:user_home')
+
+Aha! That means that the name is ``mediagoblin.user_pages.user_home``.
+Okay, so then we look at the view at the
+``mediagoblin.user_pages.user_home`` method::
+
+ @uses_pagination
+ def user_home(request, page):
+ # [...] whole bunch of stuff here
+ return render_to_response(
+ request,
+ 'mediagoblin/user_pages/user.html',
+ {'user': user,
+ 'user_gallery_url': user_gallery_url,
+ 'media_entries': media_entries,
+ 'pagination': pagination})
+
+Nice! So the template appears to be
+``mediagoblin/user_pages/user.html``. Cool, that means that the key
+is::
+
+ ("mediagoblin.user_pages.user_home",
+ "mediagoblin/user_pages/user.html")
+
+The context hook uses ``hook_transform()`` so that means that if we're
+hooking into it, our hook will both accept one argument, ``context``,
+and should return that modified object, like so::
+
+ def add_to_user_home_context(context):
+ context['foo'] = 'bar'
+ return context
+
+ hooks = {
+ ("mediagoblin.user_pages.user_home",
+ "mediagoblin/user_pages/user.html"): add_to_user_home_context}
+
+
+Global context hooks
+++++++++++++++++++++
+
+If you need to add something to the context of *every* view, it is not
+hard; there are two hooks hook that also uses hook_transform (like the
+above) but make available what you are providing to *every* view.
+
+Note that there is a slight, but critical, difference between the two.
+
+The most general one is the ``'template_global_context'`` hook. This
+one is run only once, and is read into the global context... all views
+will get access to what are in this dict.
+
+The slightly more expensive but more powerful one is
+``'template_context_prerender'``. This one is not added to the global
+context... it is added to the actual context of each individual
+template render right before it is run! Because of this you also can
+do some powerful and crazy things, such as checking the request object
+or other parts of the context before passing them on.
+
+
+Adding static resources
+-----------------------
+
+It's possible to add static resources for your plugin. Say your
+plugin needs some special javascript and images... how to provide
+them? Then how to access them? MediaGoblin has a way!
+
+
+Attaching to the hook
++++++++++++++++++++++
+
+First, you need to register your plugin's resources with the hook.
+This is pretty easy actually: you just need to provide a function that
+passes back a PluginStatic object.
+
+.. autoclass:: mediagoblin.tools.staticdirect.PluginStatic
+
+
+Running plugin assetlink
+++++++++++++++++++++++++
+
+In order for your plugin assets to be properly served by MediaGoblin,
+your plugin's asset directory needs to be symlinked into the directory
+that plugin assets are served from. To set this up, run::
+
+ ./bin/gmg assetlink
+
+
+Using staticdirect
+++++++++++++++++++
+
+Once you have this, you will want to be able to of course link to your
+assets! MediaGoblin has a "staticdirect" tool; you want to use this
+like so in your templates::
+
+ staticdirect("css/monkeys.css", "mystaticname")
+
+Replace "mystaticname" with the name you passed to PluginStatic. The
+staticdirect method is, for convenience, attached to the request
+object, so you can access this in your templates like:
+
+.. code-block:: html
+
+ <img alt="A funny bunny"
+ src="{{ request.staticdirect('images/funnybunny.png', 'mystaticname') }}" />
+
+
+Additional hook tips
+--------------------
+
+This section aims to explain some tips in regards to adding hooks to
+the MediaGoblin repository.
+
+WTForms hooks
++++++++++++++
+
+We haven't totally settled on a way to tranform wtforms form objects,
+but here's one way. In your view::
+
+ from mediagoblin.foo.forms import SomeForm
+
+ def some_view(request)
+ form_class = hook_transform('some_form_transform', SomeForm)
+ form = form_class(request.form)
+
+Then to hook into this form, do something in your plugin like::
+
+ import wtforms
+
+ class SomeFormAdditions(wtforms.Form):
+ new_datefield = wtforms.DateField()
+
+ def transform_some_form(orig_form):
+ class ModifiedForm(orig_form, SomeFormAdditions)
+ return ModifiedForm
+
+ hooks = {
+ 'some_form_transform': transform_some_form}
+
+
+Interfaces
+++++++++++
+
+If you want to add a pseudo-interface, it's not difficult to do so.
+Just write the interface like so::
+
+ class FrobInterface(object):
+ """
+ Interface for Frobbing.
+
+ Classes implementing this interface should provide defrob and frob.
+ They may also implement double_frob, but it is not required; if
+ not provided, we will use a general technique.
+ """
+
+ def defrob(self, frobbed_obj):
+ """
+ Take a frobbed_obj and defrob it. Returns the defrobbed object.
+ """
+ raise NotImplementedError()
+
+ def frob(self, normal_obj):
+ """
+ Take a normal object and frob it. Returns the frobbed object.
+ """
+ raise NotImplementedError()
+
+ def double_frob(self, normal_obj):
+ """
+ Frob this object and return it multiplied by two.
+ """
+ return self.frob(normal_obj) * 2
+
+
+ def some_frob_using_method():
+ # something something something
+ frobber = hook_handle(FrobInterface)
+ frobber.frob(blah)
+
+ # alternately you could have a default
+ frobber = hook_handle(FrobInterface) or DefaultFrobber
+ frobber.defrob(foo)
+
+
+It's fine to use your interface as the key instead of a string if you
+like. (Usually this is messy, but since interfaces are public and
+since you need to import them into your plugin anyway, interfaces
+might as well be keys.)
+
+Then a plugin providing your interface can be like::
+
+ from mediagoblin.foo.frobfrogs import FrobInterface
+ from frogfrobber import utils
+
+ class FrogFrobber(FrobInterface):
+ """
+ Takes a frogputer science approach to frobbing.
+ """
+ def defrob(self, frobbed_obj):
+ return utils.frog_defrob(frobbed_obj)
+
+ def frob(self, normal_obj):
+ return utils.frog_frob(normal_obj)
+
+ hooks = {
+ FrobInterface: lambda: return FrogFrobber}
diff --git a/docs/source/pluginwriter/database.rst b/docs/source/pluginwriter/database.rst
index 58edf3a0..603a19eb 100644
--- a/docs/source/pluginwriter/database.rst
+++ b/docs/source/pluginwriter/database.rst
@@ -12,9 +12,12 @@
<http://creativecommons.org/publicdomain/zero/1.0/>.
-========
-Database
-========
+.. _plugin-database-chapter:
+
+
+===========================
+Database models for plugins
+===========================
Accessing Existing Data
diff --git a/docs/source/pluginwriter/media_type_hooks.rst b/docs/source/pluginwriter/media_type_hooks.rst
new file mode 100644
index 00000000..498b0b54
--- /dev/null
+++ b/docs/source/pluginwriter/media_type_hooks.rst
@@ -0,0 +1,38 @@
+==================
+ Media Type hooks
+==================
+
+This documents the hooks that are currently available for ``media_type`` plugins.
+
+What hooks are available?
+=========================
+
+'sniff_handler'
+---------------
+
+This hook is used by ``sniff_media`` in ``mediagoblin.media_types.__init__``.
+Your media type should return its ``sniff_media`` method when this hook is
+called.
+
+.. Note::
+ Your ``sniff_media`` method should return either the ``media_type`` or
+ ``None``.
+
+'get_media_type_and_manager'
+----------------------------
+
+This hook is used by ``get_media_type_and_manager`` in
+``mediagoblin.media_types.__init__``. When this hook is called, your media type
+plugin should check if it can handle the given extension. If so, your media
+type plugin should return the media type and media manager.
+
+('media_manager', MEDIA_TYPE)
+-----------------------------
+
+If you already know the string representing the media type of a type
+of media, you can pull down the manager specifically. Note that this
+hook is not a string but a tuple of two strings, the latter being the
+name of the media type.
+
+This is used by media entries to pull down their media managers, and
+so on.
diff --git a/docs/source/pluginwriter/quickstart.rst b/docs/source/pluginwriter/quickstart.rst
index b5a63f79..6d45ea36 100644
--- a/docs/source/pluginwriter/quickstart.rst
+++ b/docs/source/pluginwriter/quickstart.rst
@@ -178,8 +178,10 @@ That's it for the quick start!
Where to go from here
=====================
-See the documentation on the plugin API for code samples and other
-things you can use when building your plugin.
+See the documentation on the :ref:`plugin-api-chapter` for code
+samples and other things you can use when building your plugin. If
+your plugin needs its own database models, see
+:ref:`plugin-database-chapter`.
See `Hitchhiker's Guide to Packaging
<http://guide.python-distribute.org/>`_ for more information on
diff --git a/docs/source/pluginwriter/tests.rst b/docs/source/pluginwriter/tests.rst
new file mode 100644
index 00000000..fe99688f
--- /dev/null
+++ b/docs/source/pluginwriter/tests.rst
@@ -0,0 +1,64 @@
+.. MediaGoblin Documentation
+
+ Written in 2013 by MediaGoblin contributors
+
+ To the extent possible under law, the author(s) have dedicated all
+ copyright and related and neighboring rights to this software to
+ the public domain worldwide. This software is distributed without
+ any warranty.
+
+ You should have received a copy of the CC0 Public Domain
+ Dedication along with this software. If not, see
+ <http://creativecommons.org/publicdomain/zero/1.0/>.
+
+==============================
+Writing unit tests for plugins
+==============================
+
+Here's a brief guide to writing unit tests for plugins. However, it
+isn't really ideal. It also hasn't been well tested... yes, there's
+some irony there :)
+
+Some notes: we're using py.test and webtest for unit testing stuff.
+Keep that in mind.
+
+My suggestion is to mime the behavior of `mediagoblin/tests/` and put
+that in your own plugin, like `myplugin/tests/`. Copy over
+`conftest.py` and `pytest.ini` to your tests directory, but possibly
+change the `test_app` fixture to match your own tests' config needs.
+For example::
+
+ import pkg_resources
+ # [...]
+
+ @pytest.fixture()
+ def test_app(request):
+ return get_app(
+ request,
+ mgoblin_config=pkg_resources.resource_filename(
+ 'myplugin.tests', 'myplugin_mediagoblin.ini'))
+
+In any test module in your tests directory you can then do::
+
+ def test_somethingorother(test_app):
+ # real code goes here
+ pass
+
+And you'll get a mediagoblin application wrapped in webtest passed in
+to your environment.
+
+If your plugin needs to define multiple configuration setups, you can
+actually set up multiple fixtures very easily for this. You can just
+set up multiple fixtures with different names that point to different
+configs and pass them in as that named argument.
+
+To run the tests, from mediagoblin's directory (make sure that your
+plugin has been added to your mediagoblin checkout's virtualenv!) do::
+
+ ./runtests.sh /path/to/myplugin/tests/
+
+replacing `/path/to/myplugin/` with the actual path to your plugin.
+
+NOTE: again, the above is untested, but it should probably work. If
+you run into trouble, `contact us
+<http://mediagoblin.org/pages/join.html>`_, preferably on IRC!