Merge remote-tracking branch 'upstream/master' into basic_auth
[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
b312d2cd
CAW
72
73Context Hooks
74-------------
75
76View specific hooks
77+++++++++++++++++++
78
79You can hook up to almost any template called by any specific view
80fairly easily. As long as the view directly or indirectly uses the
81method ``render_to_response`` you can access the context via a hook
82that has a key in the format of the tuple::
83
84 (view_symbolic_name, view_template_path)
85
86Where the "view symbolic name" is the same parameter used in
38ebd05d 87``request.urlgen()`` to look up the view. So say we're wanting to add
b312d2cd
CAW
88something to the context of the user's homepage. We look in
89mediagoblin/user_pages/routing.py and see::
90
91 add_route('mediagoblin.user_pages.user_home',
92 '/u/<string:user>/',
93 'mediagoblin.user_pages.views:user_home')
94
95Aha! That means that the name is ``mediagoblin.user_pages.user_home``.
96Okay, so then we look at the view at the
1344c561 97``mediagoblin.user_pages.user_home`` method::
b312d2cd
CAW
98
99 @uses_pagination
100 def user_home(request, page):
101 # [...] whole bunch of stuff here
102 return render_to_response(
103 request,
104 'mediagoblin/user_pages/user.html',
105 {'user': user,
106 'user_gallery_url': user_gallery_url,
107 'media_entries': media_entries,
108 'pagination': pagination})
109
110Nice! So the template appears to be
111``mediagoblin/user_pages/user.html``. Cool, that means that the key
112is::
113
1344c561 114 ("mediagoblin.user_pages.user_home",
b312d2cd
CAW
115 "mediagoblin/user_pages/user.html")
116
117The context hook uses ``hook_transform()`` so that means that if we're
118hooking into it, our hook will both accept one argument, ``context``,
119and should return that modified object, like so::
120
121 def add_to_user_home_context(context):
122 context['foo'] = 'bar'
123 return context
124
125 hooks = {
1344c561 126 ("mediagoblin.user_pages.user_home",
b312d2cd
CAW
127 "mediagoblin/user_pages/user.html"): add_to_user_home_context}
128
129
05539761
CAW
130Global context hooks
131++++++++++++++++++++
b312d2cd 132
1344c561
CAW
133If you need to add something to the context of *every* view, it is not
134hard; there are two hooks hook that also uses hook_transform (like the
135above) but make available what you are providing to *every* view.
b312d2cd 136
1344c561
CAW
137Note that there is a slight, but critical, difference between the two.
138
139The most general one is the ``'template_global_context'`` hook. This
140one is run only once, and is read into the global context... all views
141will get access to what are in this dict.
142
143The slightly more expensive but more powerful one is
144``'template_context_prerender'``. This one is not added to the global
145context... it is added to the actual context of each individual
146template render right before it is run! Because of this you also can
147do some powerful and crazy things, such as checking the request object
148or other parts of the context before passing them on.
d6d2c771
CAW
149
150
151Adding static resources
152-----------------------
153
154It's possible to add static resources for your plugin. Say your
155plugin needs some special javascript and images... how to provide
156them? Then how to access them? MediaGoblin has a way!
157
158
159Attaching to the hook
160+++++++++++++++++++++
161
162First, you need to register your plugin's resources with the hook.
163This is pretty easy actually: you just need to provide a function that
164passes back a PluginStatic object.
165
5de40278 166.. autoclass:: mediagoblin.tools.staticdirect.PluginStatic
505b4b39
CAW
167
168
169Running plugin assetlink
170++++++++++++++++++++++++
171
505b4b39
CAW
172In order for your plugin assets to be properly served by MediaGoblin,
173your plugin's asset directory needs to be symlinked into the directory
174that plugin assets are served from. To set this up, run::
175
24ede044 176 ./bin/gmg assetlink
505b4b39
CAW
177
178
179Using staticdirect
180++++++++++++++++++
181
182Once you have this, you will want to be able to of course link to your
183assets! MediaGoblin has a "staticdirect" tool; you want to use this
184like so in your templates::
185
186 staticdirect("css/monkeys.css", "mystaticname")
187
188Replace "mystaticname" with the name you passed to PluginStatic. The
189staticdirect method is, for convenience, attached to the request
190object, so you can access this in your templates like:
191
192.. code-block:: html
193
194 <img alt="A funny bunny"
195 src="{{ request.staticdirect('images/funnybunny.png', 'mystaticname') }}" />
baf2c1c9
CAW
196
197
198Additional hook tips
199--------------------
200
201This section aims to explain some tips in regards to adding hooks to
202the MediaGoblin repository.
203
204WTForms hooks
40019095 205+++++++++++++
baf2c1c9
CAW
206
207We haven't totally settled on a way to tranform wtforms form objects,
208but here's one way. In your view::
209
210 from mediagoblin.foo.forms import SomeForm
211
212 def some_view(request)
213 form_class = hook_transform('some_form_transform', SomeForm)
214 form = form_class(request.form)
215
216Then to hook into this form, do something in your plugin like::
217
218 import wtforms
219
220 class SomeFormAdditions(wtforms.Form):
221 new_datefield = wtforms.DateField()
222
223 def transform_some_form(orig_form):
224 class ModifiedForm(orig_form, SomeFormAdditions)
225 return ModifiedForm
226
227 hooks = {
228 'some_form_transform': transform_some_form}
9d881aee
CAW
229
230
231Interfaces
232++++++++++
233
234If you want to add a pseudo-interface, it's not difficult to do so.
235Just write the interface like so::
236
237 class FrobInterface(object):
238 """
239 Interface for Frobbing.
240
241 Classes implementing this interface should provide defrob and frob.
242 They may also implement double_frob, but it is not required; if
243 not provided, we will use a general technique.
244 """
245
246 def defrob(self, frobbed_obj):
247 """
248 Take a frobbed_obj and defrob it. Returns the defrobbed object.
249 """
250 raise NotImplementedError()
251
252 def frob(self, normal_obj):
253 """
254 Take a normal object and frob it. Returns the frobbed object.
255 """
256 raise NotImplementedError()
257
258 def double_frob(self, normal_obj):
259 """
260 Frob this object and return it multiplied by two.
261 """
262 return self.frob(normal_obj) * 2
263
264
265 def some_frob_using_method():
266 # something something something
267 frobber = hook_handle(FrobInterface)
268 frobber.frob(blah)
269
270 # alternately you could have a default
271 frobber = hook_handle(FrobInterface) or DefaultFrobber
272 frobber.defrob(foo)
273
274
275It's fine to use your interface as the key instead of a string if you
ea49f378
CAW
276like. (Usually this is messy, but since interfaces are public and
277since you need to import them into your plugin anyway, interfaces
278might as well be keys.)
9d881aee
CAW
279
280Then a plugin providing your interface can be like::
281
282 from mediagoblin.foo.frobfrogs import FrobInterface
283 from frogfrobber import utils
284
285 class FrogFrobber(FrobInterface):
286 """
287 Takes a frogputer science approach to frobbing.
288 """
289 def defrob(self, frobbed_obj):
290 return utils.frog_defrob(frobbed_obj)
291
292 def frob(self, normal_obj):
293 return utils.frog_frob(normal_obj)
294
295 hooks = {
296 FrobInterface: lambda: return FrogFrobber}