api.rst 10 KB

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