Browse Source

Allow plugins to register profile settings links

Alec Nikolas Reiter 7 years ago
parent
commit
f267fbcd44

+ 12 - 0
flaskbb/app.py

@@ -11,6 +11,7 @@
 import os
 import logging
 import logging.config
+import sys
 import time
 from functools import partial
 
@@ -406,6 +407,17 @@ def configure_mail_logs(app):
 
 def load_plugins(app):
     app.pluggy.add_hookspecs(spec)
+
+    # have to find all the flaskbb modules that are loaded this way
+    # otherwise sys.modules might change while we're iterating it
+    # because of imports and that makes Python very unhappy
+    flaskbb_modules = [
+        module for name, module in sys.modules.items()
+        if name.startswith('flaskbb')
+    ]
+    for module in flaskbb_modules:
+        app.pluggy.register(module)
+
     try:
         with app.app_context():
             plugins = PluginRegistry.query.all()

+ 29 - 0
flaskbb/plugins/spec.py

@@ -115,3 +115,32 @@ def flaskbb_tpl_after_user_details_form():
 
     in :file:`templates/user/change_user_details.html`.
     """
+
+@spec
+def flaskbb_tpl_profile_settings_menu():
+    """This hook is emitted on the user settings page in order to populate the
+    side bar menu. Implementations of this hook should return a list of tuples
+    that are view name and display text. The display text will be provided to
+    the translation service so it is unnecessary to supply translated text.
+
+    A plugin can declare a new block by setting the view to None. If this is
+    done, consider marking the hook implementation with `trylast=True` to
+    avoid capturing plugins that do not create new blocks.
+
+    For example:
+
+        @impl(trylast=True)
+        def flaskbb_tpl_profile_settings_menu():
+            return [
+                (None, 'Account Settings'),
+                ('user.settings', 'General Settings'),
+                ('user.change_user_details', 'Change User Details'),
+                ('user.change_email', 'Change E-Mail Address'),
+                ('user.change_password', 'Change Password')
+            ]
+
+    Hookwrappers for this spec should not be registered as FlaskBB
+    supplies its own hookwrapper to flatten all the lists into a single list.
+
+    in :file:`templates/user/settings_layout.html`
+    """

+ 6 - 3
flaskbb/plugins/utils.py

@@ -18,7 +18,7 @@ from flaskbb.utils.datastructures import TemplateEventResult
 from flaskbb.plugins.models import PluginRegistry
 
 
-def template_hook(name, silent=True, **kwargs):
+def template_hook(name, silent=True, is_markup=True, **kwargs):
     """Calls the given template hook.
 
     :param name: The name of the hook.
@@ -28,13 +28,16 @@ def template_hook(name, silent=True, **kwargs):
     """
     try:
         hook = getattr(current_app.pluggy.hook, name)
-        result = hook(**kwargs)
+        result = TemplateEventResult(hook(**kwargs))
     except AttributeError:  # raised if hook doesn't exist
         if silent:
             return ""
         raise
 
-    return Markup(TemplateEventResult(result))
+    if is_markup:
+        return Markup(result)
+
+    return result
 
 
 def validate_plugin(name):

+ 7 - 5
flaskbb/templates/user/settings_layout.html

@@ -12,11 +12,13 @@
     <div class="col-sm-3">
         <div class="sidebar">
             <ul class="nav sidenav">
-                <li class="sidenav-header">{% trans %}Account Settings{% endtrans %}</li>
-                {{ navlink('user.settings', _('General Settings')) }}
-                {{ navlink('user.change_user_details', _('Change User Details')) }}
-                {{ navlink('user.change_email', _('Change E-Mail Address')) }}
-                {{ navlink('user.change_password', _('Change Password')) }}
+                {% for view, text in run_hook('flaskbb_tpl_profile_settings_menu', is_markup=False) %}
+                        {% if view == None %}
+                        <li class="sidenav-header">{% trans header=text %}{{ text }}{% endtrans %}</li>
+                        {% else %}
+                        {{ navlink(view, _(text)) }}
+                        {% endif %}
+                {% endfor %}
             </ul>
         </div>
     </div><!--/.col-sm-3 -->

+ 13 - 0
flaskbb/user/__init__.py

@@ -1,3 +1,16 @@
+# -*- coding: utf-8 -*-
+"""
+    flaskbb.user
+    ~~~~~~~~~~~~~~~
+
+    This module contains models, forms and views relevant to Users
+
+    :copyright: (c) 2014 by the FlaskBB Team.
+    :license: BSD, see LICENSE for more details.
+"""
 import logging
 
+# force plugins to be loaded
+from . import plugins
+
 logger = logging.getLogger(__name__)

+ 22 - 0
flaskbb/user/plugins.py

@@ -0,0 +1,22 @@
+from itertools import chain
+from pluggy import HookimplMarker
+
+impl = HookimplMarker('flaskbb')
+
+
+@impl(hookwrapper=True, tryfirst=True)
+def flaskbb_tpl_profile_settings_menu():
+    """
+    Flattens the lists that come back from the hook
+    into a single iterable that can be used to populate
+    the menu
+    """
+    results = [
+        (None, 'Account Settings'),
+        ('user.settings', 'General Settings'),
+        ('user.change_user_details', 'Change User Details'),
+        ('user.change_email', 'Change E-Mail Address'),
+        ('user.change_password', 'Change Password')
+    ]
+    outcome = yield
+    outcome.force_result(chain(results, *outcome.get_result()))