1 .. MediaGoblin Documentation
3 Written in 2013 by MediaGoblin contributors
5 To the extent possible under law, the author(s) have dedicated all
6 copyright and related and neighboring rights to this software to
7 the public domain worldwide. This software is distributed without
10 You should have received a copy of the CC0 Public Domain
11 Dedication along with this software. If not, see
12 <http://creativecommons.org/publicdomain/zero/1.0/>.
14 .. _plugin-api-chapter:
20 This documents the general plugin API.
22 Please note, at this point OUR PLUGIN HOOKS MAY AND WILL CHANGE.
23 Authors are encouraged to develop plugins and work with the
24 MediaGoblin community to keep them up to date, but this API will be a
25 moving target for a few releases.
27 Please check the :ref:`release-notes` for updates!
30 How are hooks added? Where do I find them?
31 -------------------------------------------
33 Much of this document talks about hooks, both as in terms of regular
34 hooks and template hooks. But where do they come from, and how can
35 you find a list of them?
37 For the moment, the best way to find available hooks is to check the
38 source code itself. (Yes, we should start a more official hook
39 listing with descriptions soon.) But many hooks you may need do not
40 exist yet: what to do then?
42 The plan at present is that we are adding hooks as people need them,
43 with community discussion. If you find that you need a hook and
44 MediaGoblin at present doesn't provide it at present, please
45 `http://mediagoblin.org/pages/join.html <talk to us>`_! We'll
46 evaluate what to do from there.
49 :mod:`pluginapi` Module
50 -----------------------
52 .. automodule:: mediagoblin.tools.pluginapi
53 :members: get_config, register_routes, register_template_path,
54 register_template_hooks, get_hook_templates,
55 hook_handle, hook_runall, hook_transform
60 Your plugin may define its own configuration defaults.
62 Simply add to the directory of your plugin a config_spec.ini file. An
63 example might look like::
66 some_string = string(default="blork")
67 some_int = integer(default=50)
69 This means that when people enable your plugin in their config you'll
70 be able to provide defaults as well as type validation.
72 You can access this via the app_config variables in mg_globals, or you
73 can use a shortcut to get your plugin's config section::
75 >>> from mediagoblin.tools import pluginapi
76 # Replace with the path to your plugin.
77 # (If an external package, it won't be part of mediagoblin.plugins)
78 >>> floobie_config = pluginapi.get_config('mediagoblin.plugins.floobifier')
79 >>> floobie_dir = floobie_config['floobie_dir']
80 # This is the same as the above
81 >>> from mediagoblin import mg_globals
82 >>> config = mg_globals.global_config['plugins']['mediagoblin.plugins.floobifier']
83 >>> floobie_dir = floobie_config['floobie_dir']
85 A tip: you have access to the `%(here)s` variable in your config,
86 which is the directory that the user's mediagoblin config is running
87 out of. So for example, your plugin may need a "floobie" directory to
88 store floobs in. You could give them a reasonable default that makes
89 use of the default `user_dev` location, but allow users to override
93 floobie_dir = string(default="%(here)s/user_dev/floobs/")
95 Note, this is relative to the user's mediagoblin config directory,
96 *not* your plugin directory!
105 You can hook up to almost any template called by any specific view
106 fairly easily. As long as the view directly or indirectly uses the
107 method ``render_to_response`` you can access the context via a hook
108 that has a key in the format of the tuple::
110 (view_symbolic_name, view_template_path)
112 Where the "view symbolic name" is the same parameter used in
113 ``request.urlgen()`` to look up the view. So say we're wanting to add
114 something to the context of the user's homepage. We look in
115 mediagoblin/user_pages/routing.py and see::
117 add_route('mediagoblin.user_pages.user_home',
119 'mediagoblin.user_pages.views:user_home')
121 Aha! That means that the name is ``mediagoblin.user_pages.user_home``.
122 Okay, so then we look at the view at the
123 ``mediagoblin.user_pages.user_home`` method::
126 def user_home(request, page):
127 # [...] whole bunch of stuff here
128 return render_to_response(
130 'mediagoblin/user_pages/user.html',
132 'user_gallery_url': user_gallery_url,
133 'media_entries': media_entries,
134 'pagination': pagination})
136 Nice! So the template appears to be
137 ``mediagoblin/user_pages/user.html``. Cool, that means that the key
140 ("mediagoblin.user_pages.user_home",
141 "mediagoblin/user_pages/user.html")
143 The context hook uses ``hook_transform()`` so that means that if we're
144 hooking into it, our hook will both accept one argument, ``context``,
145 and should return that modified object, like so::
147 def add_to_user_home_context(context):
148 context['foo'] = 'bar'
152 ("mediagoblin.user_pages.user_home",
153 "mediagoblin/user_pages/user.html"): add_to_user_home_context}
159 If you need to add something to the context of *every* view, it is not
160 hard; there are two hooks hook that also uses hook_transform (like the
161 above) but make available what you are providing to *every* view.
163 Note that there is a slight, but critical, difference between the two.
165 The most general one is the ``'template_global_context'`` hook. This
166 one is run only once, and is read into the global context... all views
167 will get access to what are in this dict.
169 The slightly more expensive but more powerful one is
170 ``'template_context_prerender'``. This one is not added to the global
171 context... it is added to the actual context of each individual
172 template render right before it is run! Because of this you also can
173 do some powerful and crazy things, such as checking the request object
174 or other parts of the context before passing them on.
177 Adding static resources
178 -----------------------
180 It's possible to add static resources for your plugin. Say your
181 plugin needs some special javascript and images... how to provide
182 them? Then how to access them? MediaGoblin has a way!
185 Attaching to the hook
186 +++++++++++++++++++++
188 First, you need to register your plugin's resources with the hook.
189 This is pretty easy actually: you just need to provide a function that
190 passes back a PluginStatic object.
192 .. autoclass:: mediagoblin.tools.staticdirect.PluginStatic
195 Running plugin assetlink
196 ++++++++++++++++++++++++
198 In order for your plugin assets to be properly served by MediaGoblin,
199 your plugin's asset directory needs to be symlinked into the directory
200 that plugin assets are served from. To set this up, run::
208 Once you have this, you will want to be able to of course link to your
209 assets! MediaGoblin has a "staticdirect" tool; you want to use this
210 like so in your templates::
212 staticdirect("css/monkeys.css", "mystaticname")
214 Replace "mystaticname" with the name you passed to PluginStatic. The
215 staticdirect method is, for convenience, attached to the request
216 object, so you can access this in your templates like:
220 <img alt="A funny bunny"
221 src="{{ request.staticdirect('images/funnybunny.png', 'mystaticname') }}" />
227 This section aims to explain some tips in regards to adding hooks to
228 the MediaGoblin repository.
233 We haven't totally settled on a way to tranform wtforms form objects,
234 but here's one way. In your view::
236 from mediagoblin.foo.forms import SomeForm
238 def some_view(request)
239 form_class = hook_transform('some_form_transform', SomeForm)
240 form = form_class(request.form)
242 Then to hook into this form, do something in your plugin like::
246 class SomeFormAdditions(wtforms.Form):
247 new_datefield = wtforms.DateField()
249 def transform_some_form(orig_form):
250 class ModifiedForm(orig_form, SomeFormAdditions)
254 'some_form_transform': transform_some_form}
260 If you want to add a pseudo-interface, it's not difficult to do so.
261 Just write the interface like so::
263 class FrobInterface(object):
265 Interface for Frobbing.
267 Classes implementing this interface should provide defrob and frob.
268 They may also implement double_frob, but it is not required; if
269 not provided, we will use a general technique.
272 def defrob(self, frobbed_obj):
274 Take a frobbed_obj and defrob it. Returns the defrobbed object.
276 raise NotImplementedError()
278 def frob(self, normal_obj):
280 Take a normal object and frob it. Returns the frobbed object.
282 raise NotImplementedError()
284 def double_frob(self, normal_obj):
286 Frob this object and return it multiplied by two.
288 return self.frob(normal_obj) * 2
291 def some_frob_using_method():
292 # something something something
293 frobber = hook_handle(FrobInterface)
296 # alternately you could have a default
297 frobber = hook_handle(FrobInterface) or DefaultFrobber
301 It's fine to use your interface as the key instead of a string if you
302 like. (Usually this is messy, but since interfaces are public and
303 since you need to import them into your plugin anyway, interfaces
304 might as well be keys.)
306 Then a plugin providing your interface can be like::
308 from mediagoblin.foo.frobfrogs import FrobInterface
309 from frogfrobber import utils
311 class FrogFrobber(FrobInterface):
313 Takes a frogputer science approach to frobbing.
315 def defrob(self, frobbed_obj):
316 return utils.frog_defrob(frobbed_obj)
318 def frob(self, normal_obj):
319 return utils.frog_frob(normal_obj)
322 FrobInterface: lambda: return FrogFrobber}