Commit | Line | Data |
---|---|---|
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 | ========== | |
17 | Plugin API | |
18 | ========== | |
19 | ||
4d0191dc CAW |
20 | This documents the general plugin API. |
21 | ||
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. | |
26 | ||
b21220e9 | 27 | Please check the :ref:`release-notes` for updates! |
4d0191dc | 28 | |
8ae5d20f CAW |
29 | |
30 | How are hooks added? Where do I find them? | |
31 | ------------------------------------------- | |
32 | ||
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? | |
36 | ||
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? | |
41 | ||
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. | |
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 | |
57 | Configuration | |
58 | ------------- | |
59 | ||
60 | Your plugin may define its own configuration defaults. | |
61 | ||
62 | Simply add to the directory of your plugin a config_spec.ini file. An | |
63 | example might look like:: | |
64 | ||
65 | [plugin_spec] | |
66 | some_string = string(default="blork") | |
67 | some_int = integer(default=50) | |
68 | ||
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. | |
71 | ||
b312d2cd CAW |
72 | |
73 | Context Hooks | |
74 | ------------- | |
75 | ||
76 | View specific hooks | |
77 | +++++++++++++++++++ | |
78 | ||
79 | You can hook up to almost any template called by any specific view | |
80 | fairly easily. As long as the view directly or indirectly uses the | |
81 | method ``render_to_response`` you can access the context via a hook | |
82 | that has a key in the format of the tuple:: | |
83 | ||
84 | (view_symbolic_name, view_template_path) | |
85 | ||
86 | Where 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 |
88 | something to the context of the user's homepage. We look in |
89 | mediagoblin/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 | ||
95 | Aha! That means that the name is ``mediagoblin.user_pages.user_home``. | |
96 | Okay, 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 | ||
110 | Nice! So the template appears to be | |
111 | ``mediagoblin/user_pages/user.html``. Cool, that means that the key | |
112 | is:: | |
113 | ||
1344c561 | 114 | ("mediagoblin.user_pages.user_home", |
b312d2cd CAW |
115 | "mediagoblin/user_pages/user.html") |
116 | ||
117 | The context hook uses ``hook_transform()`` so that means that if we're | |
118 | hooking into it, our hook will both accept one argument, ``context``, | |
119 | and 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 |
130 | Global context hooks |
131 | ++++++++++++++++++++ | |
b312d2cd | 132 | |
1344c561 CAW |
133 | If you need to add something to the context of *every* view, it is not |
134 | hard; there are two hooks hook that also uses hook_transform (like the | |
135 | above) but make available what you are providing to *every* view. | |
b312d2cd | 136 | |
1344c561 CAW |
137 | Note that there is a slight, but critical, difference between the two. |
138 | ||
139 | The most general one is the ``'template_global_context'`` hook. This | |
140 | one is run only once, and is read into the global context... all views | |
141 | will get access to what are in this dict. | |
142 | ||
143 | The slightly more expensive but more powerful one is | |
144 | ``'template_context_prerender'``. This one is not added to the global | |
145 | context... it is added to the actual context of each individual | |
146 | template render right before it is run! Because of this you also can | |
147 | do some powerful and crazy things, such as checking the request object | |
148 | or other parts of the context before passing them on. | |
d6d2c771 CAW |
149 | |
150 | ||
151 | Adding static resources | |
152 | ----------------------- | |
153 | ||
154 | It's possible to add static resources for your plugin. Say your | |
155 | plugin needs some special javascript and images... how to provide | |
156 | them? Then how to access them? MediaGoblin has a way! | |
157 | ||
158 | ||
159 | Attaching to the hook | |
160 | +++++++++++++++++++++ | |
161 | ||
162 | First, you need to register your plugin's resources with the hook. | |
163 | This is pretty easy actually: you just need to provide a function that | |
164 | passes back a PluginStatic object. | |
165 | ||
5de40278 | 166 | .. autoclass:: mediagoblin.tools.staticdirect.PluginStatic |
505b4b39 CAW |
167 | |
168 | ||
169 | Running plugin assetlink | |
170 | ++++++++++++++++++++++++ | |
171 | ||
505b4b39 CAW |
172 | In order for your plugin assets to be properly served by MediaGoblin, |
173 | your plugin's asset directory needs to be symlinked into the directory | |
174 | that plugin assets are served from. To set this up, run:: | |
175 | ||
24ede044 | 176 | ./bin/gmg assetlink |
505b4b39 CAW |
177 | |
178 | ||
179 | Using staticdirect | |
180 | ++++++++++++++++++ | |
181 | ||
182 | Once you have this, you will want to be able to of course link to your | |
183 | assets! MediaGoblin has a "staticdirect" tool; you want to use this | |
184 | like so in your templates:: | |
185 | ||
186 | staticdirect("css/monkeys.css", "mystaticname") | |
187 | ||
188 | Replace "mystaticname" with the name you passed to PluginStatic. The | |
189 | staticdirect method is, for convenience, attached to the request | |
190 | object, 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 | ||
198 | Additional hook tips | |
199 | -------------------- | |
200 | ||
201 | This section aims to explain some tips in regards to adding hooks to | |
202 | the MediaGoblin repository. | |
203 | ||
204 | WTForms hooks | |
40019095 | 205 | +++++++++++++ |
baf2c1c9 CAW |
206 | |
207 | We haven't totally settled on a way to tranform wtforms form objects, | |
208 | but 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 | ||
216 | Then 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 | ||
231 | Interfaces | |
232 | ++++++++++ | |
233 | ||
234 | If you want to add a pseudo-interface, it's not difficult to do so. | |
235 | Just 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 | ||
275 | It's fine to use your interface as the key instead of a string if you | |
ea49f378 CAW |
276 | like. (Usually this is messy, but since interfaces are public and |
277 | since you need to import them into your plugin anyway, interfaces | |
278 | might as well be keys.) | |
9d881aee CAW |
279 | |
280 | Then 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} |