Merge commit 'refs/merge-requests/59' of git://gitorious.org/mediagoblin/mediagoblin...
[mediagoblin.git] / docs / source / pluginwriter / api.rst
CommitLineData
92c597ca
E
1.. MediaGoblin Documentation
2
3 Written in 2013 by MediaGoblin contributors
4
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
8 any warranty.
9
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/>.
13
d861ffc9 14.. _plugin-api-chapter:
92c597ca
E
15
16==========
17Plugin API
18==========
19
4d0191dc
CAW
20This documents the general plugin API.
21
22Please note, at this point OUR PLUGIN HOOKS MAY AND WILL CHANGE.
23Authors are encouraged to develop plugins and work with the
24MediaGoblin community to keep them up to date, but this API will be a
25moving target for a few releases.
26
b21220e9 27Please check the :ref:`release-notes` for updates!
4d0191dc 28
8ae5d20f
CAW
29
30How are hooks added? Where do I find them?
31-------------------------------------------
32
33Much of this document talks about hooks, both as in terms of regular
34hooks and template hooks. But where do they come from, and how can
35you find a list of them?
36
37For the moment, the best way to find available hooks is to check the
38source code itself. (Yes, we should start a more official hook
39listing with descriptions soon.) But many hooks you may need do not
40exist yet: what to do then?
41
42The plan at present is that we are adding hooks as people need them,
43with community discussion. If you find that you need a hook and
44MediaGoblin at present doesn't provide it at present, please
45`http://mediagoblin.org/pages/join.html <talk to us>`_! We'll
46evaluate what to do from there.
47
48
92c597ca
E
49:mod:`pluginapi` Module
50-----------------------
51
52.. automodule:: mediagoblin.tools.pluginapi
cf41e7d7 53 :members: get_config, register_routes, register_template_path,
36748921 54 register_template_hooks, get_hook_templates,
b835e153 55 hook_handle, hook_runall, hook_transform
f65bf898
CAW
56
57Configuration
58-------------
59
60Your plugin may define its own configuration defaults.
61
62Simply add to the directory of your plugin a config_spec.ini file. An
63example might look like::
64
65 [plugin_spec]
66 some_string = string(default="blork")
67 some_int = integer(default=50)
68
69This means that when people enable your plugin in their config you'll
70be able to provide defaults as well as type validation.
71
7fa4e19f
CAW
72You can access this via the app_config variables in mg_globals, or you
73can use a shortcut to get your plugin's config section::
74
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']
84
85A tip: you have access to the `%(here)s` variable in your config,
86which is the directory that the user's mediagoblin config is running
87out of. So for example, your plugin may need a "floobie" directory to
88store floobs in. You could give them a reasonable default that makes
89use of the default `user_dev` location, but allow users to override
90it, like so::
91
92 [plugin_spec]
93 floobie_dir = string(default="%(here)s/user_dev/floobs/")
94
95Note, this is relative to the user's mediagoblin config directory,
96*not* your plugin directory!
97
b312d2cd
CAW
98
99Context Hooks
100-------------
101
102View specific hooks
103+++++++++++++++++++
104
105You can hook up to almost any template called by any specific view
106fairly easily. As long as the view directly or indirectly uses the
107method ``render_to_response`` you can access the context via a hook
108that has a key in the format of the tuple::
109
110 (view_symbolic_name, view_template_path)
111
112Where the "view symbolic name" is the same parameter used in
38ebd05d 113``request.urlgen()`` to look up the view. So say we're wanting to add
b312d2cd
CAW
114something to the context of the user's homepage. We look in
115mediagoblin/user_pages/routing.py and see::
116
117 add_route('mediagoblin.user_pages.user_home',
118 '/u/<string:user>/',
119 'mediagoblin.user_pages.views:user_home')
120
121Aha! That means that the name is ``mediagoblin.user_pages.user_home``.
122Okay, so then we look at the view at the
1344c561 123``mediagoblin.user_pages.user_home`` method::
b312d2cd
CAW
124
125 @uses_pagination
126 def user_home(request, page):
127 # [...] whole bunch of stuff here
128 return render_to_response(
129 request,
130 'mediagoblin/user_pages/user.html',
131 {'user': user,
132 'user_gallery_url': user_gallery_url,
133 'media_entries': media_entries,
134 'pagination': pagination})
135
136Nice! So the template appears to be
137``mediagoblin/user_pages/user.html``. Cool, that means that the key
138is::
139
1344c561 140 ("mediagoblin.user_pages.user_home",
b312d2cd
CAW
141 "mediagoblin/user_pages/user.html")
142
143The context hook uses ``hook_transform()`` so that means that if we're
144hooking into it, our hook will both accept one argument, ``context``,
145and should return that modified object, like so::
146
147 def add_to_user_home_context(context):
148 context['foo'] = 'bar'
149 return context
150
151 hooks = {
1344c561 152 ("mediagoblin.user_pages.user_home",
b312d2cd
CAW
153 "mediagoblin/user_pages/user.html"): add_to_user_home_context}
154
155
05539761
CAW
156Global context hooks
157++++++++++++++++++++
b312d2cd 158
1344c561
CAW
159If you need to add something to the context of *every* view, it is not
160hard; there are two hooks hook that also uses hook_transform (like the
161above) but make available what you are providing to *every* view.
b312d2cd 162
1344c561
CAW
163Note that there is a slight, but critical, difference between the two.
164
165The most general one is the ``'template_global_context'`` hook. This
166one is run only once, and is read into the global context... all views
167will get access to what are in this dict.
168
169The slightly more expensive but more powerful one is
170``'template_context_prerender'``. This one is not added to the global
171context... it is added to the actual context of each individual
172template render right before it is run! Because of this you also can
173do some powerful and crazy things, such as checking the request object
174or other parts of the context before passing them on.
d6d2c771
CAW
175
176
177Adding static resources
178-----------------------
179
180It's possible to add static resources for your plugin. Say your
181plugin needs some special javascript and images... how to provide
182them? Then how to access them? MediaGoblin has a way!
183
184
185Attaching to the hook
186+++++++++++++++++++++
187
188First, you need to register your plugin's resources with the hook.
189This is pretty easy actually: you just need to provide a function that
190passes back a PluginStatic object.
191
5de40278 192.. autoclass:: mediagoblin.tools.staticdirect.PluginStatic
505b4b39
CAW
193
194
195Running plugin assetlink
196++++++++++++++++++++++++
197
505b4b39
CAW
198In order for your plugin assets to be properly served by MediaGoblin,
199your plugin's asset directory needs to be symlinked into the directory
200that plugin assets are served from. To set this up, run::
201
24ede044 202 ./bin/gmg assetlink
505b4b39
CAW
203
204
205Using staticdirect
206++++++++++++++++++
207
208Once you have this, you will want to be able to of course link to your
209assets! MediaGoblin has a "staticdirect" tool; you want to use this
210like so in your templates::
211
212 staticdirect("css/monkeys.css", "mystaticname")
213
214Replace "mystaticname" with the name you passed to PluginStatic. The
215staticdirect method is, for convenience, attached to the request
216object, so you can access this in your templates like:
217
218.. code-block:: html
219
220 <img alt="A funny bunny"
221 src="{{ request.staticdirect('images/funnybunny.png', 'mystaticname') }}" />
baf2c1c9
CAW
222
223
224Additional hook tips
225--------------------
226
227This section aims to explain some tips in regards to adding hooks to
228the MediaGoblin repository.
229
230WTForms hooks
40019095 231+++++++++++++
baf2c1c9
CAW
232
233We haven't totally settled on a way to tranform wtforms form objects,
234but here's one way. In your view::
235
236 from mediagoblin.foo.forms import SomeForm
237
238 def some_view(request)
239 form_class = hook_transform('some_form_transform', SomeForm)
240 form = form_class(request.form)
241
242Then to hook into this form, do something in your plugin like::
243
244 import wtforms
245
246 class SomeFormAdditions(wtforms.Form):
247 new_datefield = wtforms.DateField()
248
249 def transform_some_form(orig_form):
250 class ModifiedForm(orig_form, SomeFormAdditions)
251 return ModifiedForm
252
253 hooks = {
254 'some_form_transform': transform_some_form}
9d881aee
CAW
255
256
257Interfaces
258++++++++++
259
260If you want to add a pseudo-interface, it's not difficult to do so.
261Just write the interface like so::
262
263 class FrobInterface(object):
264 """
265 Interface for Frobbing.
266
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.
270 """
271
272 def defrob(self, frobbed_obj):
273 """
274 Take a frobbed_obj and defrob it. Returns the defrobbed object.
275 """
276 raise NotImplementedError()
277
278 def frob(self, normal_obj):
279 """
280 Take a normal object and frob it. Returns the frobbed object.
281 """
282 raise NotImplementedError()
283
284 def double_frob(self, normal_obj):
285 """
286 Frob this object and return it multiplied by two.
287 """
288 return self.frob(normal_obj) * 2
289
290
291 def some_frob_using_method():
292 # something something something
293 frobber = hook_handle(FrobInterface)
294 frobber.frob(blah)
295
296 # alternately you could have a default
297 frobber = hook_handle(FrobInterface) or DefaultFrobber
298 frobber.defrob(foo)
299
300
301It's fine to use your interface as the key instead of a string if you
ea49f378
CAW
302like. (Usually this is messy, but since interfaces are public and
303since you need to import them into your plugin anyway, interfaces
304might as well be keys.)
9d881aee
CAW
305
306Then a plugin providing your interface can be like::
307
308 from mediagoblin.foo.frobfrogs import FrobInterface
309 from frogfrobber import utils
310
311 class FrogFrobber(FrobInterface):
312 """
313 Takes a frogputer science approach to frobbing.
314 """
315 def defrob(self, frobbed_obj):
316 return utils.frog_defrob(frobbed_obj)
317
318 def frob(self, normal_obj):
319 return utils.frog_frob(normal_obj)
320
321 hooks = {
322 FrobInterface: lambda: return FrogFrobber}