123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254 |
- .. _plugin_development:
- Developing new Plugins
- ======================
- If you want to write a plugin, it's a very good idea to checkout existing
- plugins. A good starting point for example is the `Portal Plugin`_.
- Also make sure to check out the cookiecutter-flaskbb-plugin project, which is a
- cookiecutter template which helps you to create new plugins.
- For example, the structure of a plugin could look like this:
- .. sourcecode:: text
- your_package_name
- |-- setup.py
- |-- my_plugin
- |-- __init__.py
- |-- views.py
- |-- models.py
- |-- forms.py
- |-- static
- | |-- style.css
- |-- templates
- |-- myplugin.html
- |-- migrations
- |-- 59f7c49b6289_init.py
- Metadata
- --------
- FlaskBB Plugins are usually following the naming scheme of
- ``flaskbb-plugin-YOUR_PLUGIN_NAME`` which should make them better
- distinguishable from other PyPI distributions.
- A proper plugin should have at least put the following metadata into
- the ``setup.py`` file.
- .. sourcecode:: python
- setup(
- name="flaskbb-plugin-YOUR_PLUGIN_NAME", # name on PyPI
- packages=["your_package_name"], # name of the folder your plugin is located in
- version='1.0',
- url=<url to your project>,
- license=<your license>,
- author=<you>,
- author_email=<your email>,
- description=<your short description>,
- long_description=__doc__,
- include_package_data=True,
- zip_safe=False,
- platforms='any',
- entry_points={
- 'flaskbb_plugin': [
- 'unique_name_of_plugin = your_package_name.pluginmodule', # most important part
- ]
- }
- )
- The most important part here is the ``entry_point``. Here you tell FlaskBB the
- unique name of your plugin and where your plugin module is located inside
- your project. Entry points are a feature that is provided by setuptools.
- FlaskBB looks up the ``flaskbb_plugin`` entrypoint to discover its plugins.
- Have a look at the `setup script`_ documentation and the `sample setup.py`_
- file to get a better idea what the ``setup.py`` file is all about it.
- For a full example, checkout the `Portal Plugin`_.
- .. _`setup script`: https://docs.python.org/3.6/distutils/setupscript.html#additional-meta-data
- .. _`sample setup.py`: https://github.com/pypa/sampleproject/blob/master/setup.py
- .. _`Portal Plugin`: https://github.com/sh4nks/flaskbb-plugins/tree/master/portal
- Settings
- --------
- Plugins can create settings which integrate with the 'Settings' tab of
- the Admin Panel.
- The settings are stored in a dictionary with a given structure. The name of
- the dictionary must be ``SETTINGS`` and be placed in the plugin module.
- The structure of the ``SETTINGS`` dictionary is best explained via an
- example::
- SETTINGS = {
- # This key has to be unique across FlaskBB.
- # Using a prefix is recommended.
- 'forum_ids': {
- # Default Value. The type of the default value depends on the
- # SettingValueType.
- 'value': [1],
- # The Setting Value Type.
- 'value_type': SettingValueType.selectmultiple,
- # The human readable name of your configuration variable
- 'name': "Forum IDs",
- # A short description of what the settings variable does
- 'description': ("The forum ids from which forums the posts "
- "should be displayed on the portal."),
- # extra stuff like the 'choices' in a select field or the
- # validators are defined in here
- 'extra': {"choices": available_forums, "coerce": int}
- }
- }
- .. currentmodule:: flaskbb.utils.forms
- .. table:: Available Setting Value Types
- :widths: auto
- ======================================== =================
- Setting Value Type Parsed & Saved As
- ======================================== =================
- :attr:`SettingValueType.string` :class:`str`
- :attr:`SettingValueType.integer` :class:`int`
- :attr:`SettingValueType.float` :class:`float`
- :attr:`SettingValueType.boolean` :class:`bool`
- :attr:`SettingValueType.select` :class:`list`
- :attr:`SettingValueType.selectmultiple` :class:`list`
- ======================================== =================
- .. table:: Available Additional Options via the ``extra`` Keyword
- =========== ====================== ========================================
- Options Applicable Types Description
- =========== ====================== ========================================
- ``min`` string, integer, float **Optional.** The minimum required
- length of the setting value. If used on
- a numeric type, it will check the
- minimum value.
- ``max`` string, integer, float **Optional.** The maximum required
- length of the setting value. If used on
- a numeric type, it will check the
- maximum value.
- ``choices`` select, selectmultiple **Required.** A callable which returns
- a sequence of (value, label) pairs.
- ``coerce`` select, selectmultiple **Optional.** Coerces the select values
- to the given type.
- =========== ====================== ========================================
- Validating the size of the integer/float and the length of the string fields
- is also possible via the ``min`` and ``max`` keywords::
- 'recent_topics': {
- ...
- 'extra': {"min": 1},
- },
- The ``select`` and ``selectmultiple`` fields have to provide a callback which
- lists all the available choices. This is done via the ``choices`` keyword.
- In addition to that they can also specify the ``coerce`` keyword which will
- coerce the input value into the specified type.::
- 'forum_ids': {
- ...
- 'extra': {"choices": available_forums, "coerce": int}
- }
- For more information see the :doc:`settings` chapter.
- Using Hooks
- -----------
- Hooks are invoked based on an event occurring within FlaskBB. This makes it
- possible to change the behavior of certain actions without modifying the
- actual source code of FlaskBB.
- For your plugin to actually do something useful, you probably want to 'hook'
- your code into FlaskBB. This can be done throughout a lot of places in the
- code. FlaskBB loads and calls the hook calls hook functions from registered
- plugins for any given hook specification.
- Each hook specification has a corresponding hook implementation. By default,
- all hooks that are prefix with ``flaskbb_`` will be marked as a standard
- hook implementation. It is possible to modify the behavior of hooks.
- For example, default hooks are called in LIFO registered order.
- Although, registration order might not be deterministic. A hookimpl
- can influence its call-time invocation position using special attributes. If
- marked with a "tryfirst" or "trylast" option it will be executed first or last
- respectively in the hook call loop::
- hookimpl = HookimplMarker('flaskbb')
- @hookimpl(trylast=True)
- def flaskbb_additional_setup(app):
- return "save the best for last"
- In order to extend FlaskBB with your Plugin you will need to connect your
- callbacks to the hooks.
- Let's look at an actually piece of `used code`_.
- .. sourcecode:: python
- def flaskbb_load_blueprints(app):
- app.register_blueprint(portal, url_prefix="/portal")
- By defining a function called ``flaskbb_load_blueprints``, which has a
- corresponding hook specification under the same name. FlaskBB will pass
- in an ``app`` object as specified in the hook spec, which we will use to
- register a new blueprint. It is also possible to completely omit the ``app``
- argument from the function where it is **not possible** to add new arguments to
- the hook implemention.
- For a complete list of all available hooks in FlaskBB see the :doc:`hooks`
- section.
- pytest and pluggy are good resources to get better understanding on how to
- write `hook functions`_ using `pluggy`_.
- .. _`used code`: https://github.com/sh4nks/flaskbb-plugins/blob/master/portal/portal/__init__.py#L31
- .. _`hook functions`: https://docs.pytest.org/en/latest/writing_plugins.html#writing-hook-functions
- .. _`pluggy`: https://pluggy.readthedocs.io/en/latest/#defining-and-collecting-hooks
- Plugin Registry
- ---------------
- The plugin registry holds all available plugins. It shows the plugin's status
- whether it is enabled or disabled, installable or installed. The registry also
- holds a reference to the plugin's instance, provides an interface to access
- the plugins metadata and stores its settings.
- You can query it like any SQLAlchemy Model::
- plugin = PluginRegistry.query.filter_by(name="portal").first()
- .. autoclass:: flaskbb.plugins.models.PluginRegistry
- :members:
- Plugin Manager
- --------------
- FlaskBB overrides the PluginManager from pluggy to provide some additional functionality like accessing the information stored in a setup.py file. The plugin manager will only list the currently enabled plugins and can be used to directly access the plugins instance by its name.
- Accessing a plugins instance is as easy as::
- plugin_instance = current_app.pluggy.get_plugin(name)
- .. autoclass:: flaskbb.plugins.manager.FlaskBBPluginManager
- :members:
- :inherited-members:
|