plugins.rst 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. .. _plugins:
  2. Plugins
  3. =======
  4. .. module:: flaskbb.plugins
  5. FlaskBB provides a full featured plugin system. This system allows you to
  6. easily extend or modify FlaskBB without touching any FlaskBB code. Under the
  7. hood it uses the `pluggy plugin system`_ which does most of the heavy lifting
  8. for us.
  9. .. _`pluggy plugin system`: https://pluggy.readthedocs.io/en/latest/
  10. Structure
  11. ---------
  12. A plugin has it's own folder where all the plugin specific files are living.
  13. For example, the structure of a plugin could look like this
  14. .. sourcecode:: text
  15. my_plugin_package
  16. |-- setup.py
  17. |-- my_plugin
  18. |-- __init__.py
  19. |-- views.py
  20. |-- models.py
  21. |-- forms.py
  22. |-- static
  23. | |-- style.css
  24. |-- templates
  25. |-- myplugin.html
  26. |-- migrations
  27. |-- 59f7c49b6289_init.py
  28. Metadata
  29. ~~~~~~~~
  30. A proper plugin should have at least put the following metadata into
  31. the ``setup.py`` file.
  32. .. sourcecode:: python
  33. setup(
  34. name="myproject",
  35. version='1.0',
  36. url=<url to your project>,
  37. license=<your license>,
  38. author=<you>,
  39. author_email=<your email>,
  40. description=<your short description>,
  41. long_description=__doc__,
  42. packages=find_packages('.'),
  43. include_package_data=True,
  44. zip_safe=False,
  45. platforms='any',
  46. entry_points={
  47. 'flaskbb_plugin': [
  48. 'unique_name_of_plugin = myproject.pluginmodule', # most important part
  49. ]
  50. }
  51. )
  52. The most important part is the ``entry_point``. Here you tell FlaskBB the
  53. unique name of your plugin and where your plugin module is located inside
  54. your project. Entry points are a feature that is provided by setuptools.
  55. FlaskBB looks up the ``flaskbb_plugin`` entrypoint to discover its plugins.
  56. Have a look at the `setup script`_ documentation and the `sample setup.py`_
  57. file to get a better idea what the ``setup.py`` file is all about it.
  58. To get a better idea how a plugin looks like, checkout the `Portal Plugin`_.
  59. .. _`setup script`: https://docs.python.org/3.6/distutils/setupscript.html#additional-meta-data
  60. .. _`sample setup.py`: https://github.com/pypa/sampleproject/blob/master/setup.py
  61. .. _`Portal Plugin`: https://github.com/sh4nks/flaskbb-plugins/tree/master/portal
  62. Settings
  63. --------
  64. Plugins can create settings which integrate with the 'Settings' tab of
  65. the Admin Panel.
  66. The settings are stored in a dictionary with a given structure. The name of
  67. the dictionary must be ``SETTINGS`` and be placed in the plugin module.
  68. The structure of the ``SETTINGS`` dictionary is best explained via an
  69. example::
  70. SETTINGS = {
  71. # This key has to be unique across FlaskBB.
  72. # Using a prefix is recommended.
  73. 'forum_ids': {
  74. # Default Value. The type of the default value depends on the
  75. # SettingValueType.
  76. 'value': [1],
  77. # The Setting Value Type.
  78. 'value_type': SettingValueType.selectmultiple,
  79. # The human readable name of your configuration variable
  80. 'name': "Forum IDs",
  81. # A short description of what the settings variable does
  82. 'description': ("The forum ids from which forums the posts "
  83. "should be displayed on the portal."),
  84. # extra stuff like the 'choices' in a select field or the
  85. # validators are defined in here
  86. 'extra': {"choices": available_forums, "coerce": int}
  87. }
  88. }
  89. .. currentmodule:: flaskbb.utils.forms
  90. .. table:: Available Setting Value Types
  91. :widths: auto
  92. ======================================== =================
  93. Setting Value Type Parsed & Saved As
  94. ======================================== =================
  95. :attr:`SettingValueType.string` :class:`str`
  96. :attr:`SettingValueType.integer` :class:`int`
  97. :attr:`SettingValueType.float` :class:`float`
  98. :attr:`SettingValueType.boolean` :class:`bool`
  99. :attr:`SettingValueType.select` :class:`list`
  100. :attr:`SettingValueType.selectmultiple` :class:`list`
  101. ======================================== =================
  102. .. table:: Available Additional Options via the ``extra`` Keyword
  103. =========== ====================== ========================================
  104. Options Applicable Types Description
  105. =========== ====================== ========================================
  106. ``min`` string, integer, float **Optional.** The minimum required
  107. length of the setting value. If used on
  108. a numeric type, it will check the
  109. minimum value.
  110. ``max`` string, integer, float **Optional.** The maximum required
  111. length of the setting value. If used on
  112. a numeric type, it will check the
  113. maximum value.
  114. ``choices`` select, selectmultiple **Required.** A callable which returns
  115. a sequence of (value, label) pairs.
  116. ``coerce`` select, selectmultiple **Optional.** Coerces the select values
  117. to the given type.
  118. =========== ====================== ========================================
  119. Validating the size of the integer/float and the length of the string fields
  120. is also possible via the ``min`` and ``max`` keywords::
  121. 'recent_topics': {
  122. ...
  123. 'extra': {"min": 1},
  124. },
  125. The ``select`` and ``selectmultiple`` fields have to provide a callback which
  126. lists all the available choices. This is done via the ``choices`` keyword.
  127. In addition to that they can also specify the ``coerce`` keyword which will
  128. coerce the input value into the specified type.::
  129. 'forum_ids': {
  130. ...
  131. 'extra': {"choices": available_forums, "coerce": int}
  132. }
  133. For more information see the :doc:`settings` chapter.
  134. Database
  135. --------
  136. Upgrading, downgrading and generating database revisions is all handled
  137. via alembic. We make use of alembic's branching feature to manage seperate
  138. migrations for the plugins. Each plugin will have it's own branch in alembic
  139. where migrations can be managed. Following commands are used for generaring,
  140. upgrading and downgrading your plugins database migrations:
  141. * (Auto-)Generating revisions
  142. ``flaskbb db revision --branch <plugin_name> "<YOUR_MESSAGE>"``
  143. Replace <YOUR_MESSAGE> with something like "initial migration" if it's
  144. the first migration or with just a few words that will describe the
  145. changes of the revision.
  146. * Applying revisions
  147. ``flaskbb db upgrade <plugin_name>@head``
  148. If you want to upgrade to specific revision, replace ``head`` with the
  149. revision id.
  150. * Downgrading revisions
  151. ``flaskbb db downgrade <plugin_name>@-1``
  152. If you just want to revert the latest revision, just use ``-1``.
  153. To downgrade all database migrations, use ``base``.
  154. Management
  155. ----------
  156. Before plugins can be used in FlaskBB, they have to be downloaded, installed
  157. and activated.
  158. Plugins can be very minimalistic with nothing to install at all (just enabling
  159. and disabling) to be very complex where you have to run migrations and add
  160. some additional settings.
  161. Download
  162. ~~~~~~~~
  163. Downloading a Plugin is as easy as::
  164. $ pip install flaskbb-plugin-MYPLUGIN
  165. if the plugin has been uploaded to PyPI. If you haven't uploaded your plugin
  166. to PyPI or are in the middle of developing one, you can just::
  167. $ pip install -e .
  168. in your plugin's package directory to install it.
  169. Remove
  170. ~~~~~~
  171. Removing a plugin is a little bit more tricky. By default, FlaskBB does not
  172. remove the settings of a plugin by itself because this could lead to some
  173. unwanted dataloss.
  174. `Disable`_ and `Uninstall`_ the plugin first before continuing.
  175. After taking care of this and you are confident that you won't need the
  176. plugin anymore you can finally remove it::
  177. $ pip uninstall flaskbb-plugin-MYPLUGIN
  178. There is a setting in FlaskBB which lets you control the deletion of settings
  179. of a plugin. If ``REMOVE_DEAD_PLUGINS`` is set to ``True``, all not available
  180. plugins (not available on the filesystem) are constantly removed. Only change
  181. this if you know what you are doing.
  182. Install
  183. ~~~~~~~
  184. In our context, by installing a plugin, we mean, to install the settings
  185. and apply the migrations. Personal Note: I can't think of a better name and
  186. I am open for suggestions.
  187. The migrations have to be applied this way (if any, check the plugins docs)::
  188. flaskbb db upgrade <plugin_name>@head
  189. The plugin can be installed via the Admin Panel (in tab 'Plugins') or by
  190. running::
  191. flaskbb plugins install <plugin_name>
  192. Uninstall
  193. ~~~~~~~~~
  194. Removing a plugin involves two steps. The first one is to check if the plugin
  195. has applied any migrations on FlaskBB and if so you can
  196. undo them via::
  197. $ flaskbb db downgrade <plugin_name>@base
  198. The second step is to wipe the settings from FlaskBB which can be done in the
  199. Admin Panel or by running::
  200. $ flaskbb plugins uninstall <plugin_name>
  201. Disable
  202. ~~~~~~~
  203. Disabling a plugin has the benefit of keeping all the data of the plugin but
  204. not using the functionality it provides. A plugin can either be deactivated
  205. via the Admin Panel or by running::
  206. flaskbb plugins disable <plugin_name>
  207. .. important:: Restart the server.
  208. You must restart the wsgi/in-built server in order to make the changes
  209. effect your forum.
  210. Enable
  211. ~~~~~~
  212. All plugins are activated by default. To activate a deactivated plugin you
  213. either have to activate it via the Admin Panel again or by running the
  214. activation command::
  215. flaskbb plugins enable <plugin_name>
  216. Hooks
  217. -----
  218. Hooks are invoked based on an event occurring within FlaskBB. This makes it
  219. possible to change the behavior of certain actions without modifying the
  220. actual source code of FlaskBB.
  221. For your plugin to actually do something useful, you probably want to 'hook'
  222. your code into FlaskBB. This can be done throughout a lot of places in the
  223. code. FlaskBB loads and calls the hook calls hook functions from registered
  224. plugins for any given hook specification.
  225. Each hook specification has a corresponding hook implementation. By default,
  226. all hooks that are prefix with ``flaskbb_`` will be marked as a standard
  227. hook implementation. It is possible to modify the behavior of hooks.
  228. For example, default hooks are called in LIFO registered order.
  229. A hookimpl can influence its call-time invocation position using special
  230. attributes. If marked with a "tryfirst" or "trylast" option it will be executed
  231. first or last respectively in the hook call loop::
  232. hookimpl = HookimplMarker('flaskbb')
  233. @hookimpl(trylast=True)
  234. def flaskbb_additional_setup(app):
  235. return "save the best for last"
  236. In order to extend FlaskBB with your Plugin you will need to connect your
  237. callbacks to the hooks.
  238. Let's look at an actually piece of `used code`_.
  239. .. sourcecode:: python
  240. def flaskbb_load_blueprints(app):
  241. app.register_blueprint(portal, url_prefix="/portal")
  242. By defining a function called ``flaskbb_load_blueprints``, which has a
  243. corresponding hook specification under the same name. FlaskBB will pass
  244. in an ``app`` object as specified in the hook spec, which we will use to
  245. register a new blueprint. It is also possible to completely omit the ``app``
  246. argument from the function where it is **not possible** to add new arguments to
  247. the hook implemention.
  248. For a complete list of all available hooks in FlaskBB see the :doc:`hooks`
  249. section.
  250. pytest and pluggy are good resources to get better understanding on how to
  251. write `hook functions`_ using `pluggy`_.
  252. .. _`used code`: https://github.com/sh4nks/flaskbb-plugins/blob/master/portal/portal/__init__.py#L31
  253. .. _`hook functions`: https://docs.pytest.org/en/latest/writing_plugins.html#writing-hook-functions
  254. .. _`pluggy`: https://pluggy.readthedocs.io/en/latest/#defining-and-collecting-hooks