Add automatic browser menu integration for modules.

Modules may now include functions that return lists of menu items
that will be included on the main browser window menu. While we're
at it, move the test views into a separate module.
This commit is contained in:
Dave Page 2015-01-27 14:18:27 +00:00
parent 0cff5fca2c
commit d492da3ca1
11 changed files with 176 additions and 25 deletions

View File

@ -17,11 +17,14 @@ from flask_security.utils import login_user
from flask_mail import Mail from flask_mail import Mail
from settings_model import db, Role, User from settings_model import db, Role, User
import inspect, logging, os import inspect, imp, logging, os
# Configuration settings # Configuration settings
import config import config
# Global module list
modules = [ ]
def create_app(app_name=config.APP_NAME): def create_app(app_name=config.APP_NAME):
"""Create the Flask application, startup logging and dynamically load """Create the Flask application, startup logging and dynamically load
additional modules (blueprints) that are found in this directory.""" additional modules (blueprints) that are found in this directory."""
@ -88,6 +91,7 @@ def create_app(app_name=config.APP_NAME):
path = os.path.dirname(os.path.realpath(__file__)) path = os.path.dirname(os.path.realpath(__file__))
files = os.listdir(path) files = os.listdir(path)
for f in files: for f in files:
d = os.path.join(path, f) d = os.path.join(path, f)
if os.path.isdir(d) and os.path.isfile(os.path.join(d, '__init__.py')): if os.path.isdir(d) and os.path.isfile(os.path.join(d, '__init__.py')):
@ -98,10 +102,16 @@ def create_app(app_name=config.APP_NAME):
# Looks like a module, so import it, and register the blueprint if present # Looks like a module, so import it, and register the blueprint if present
# We rely on the ordering of syspath to ensure we actually get the right # We rely on the ordering of syspath to ensure we actually get the right
# module here. # module here. Note that we also try to load the 'browser' module for
# the browser integration hooks.
app.logger.info('Examining potential module: %s' % d) app.logger.info('Examining potential module: %s' % d)
module = __import__(f, globals(), locals(), ['views'], -1) module = __import__(f, globals(), locals(), ['browser', 'views'], -1)
if hasattr(module.views, 'blueprint'):
# Add the module to the global module list
modules.append(module)
# Register the blueprint if present
if 'views' in dir(module) and 'blueprint' in dir(module.views):
app.logger.info('Registering blueprint module: %s' % f) app.logger.info('Registering blueprint module: %s' % f)
app.register_blueprint(module.views.blueprint) app.register_blueprint(module.views.blueprint)

View File

View File

@ -0,0 +1,18 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2014, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""Browser integration functions for the About module."""
from flask import url_for
import config
def get_help_menu_items():
"""Return a (set) of dicts of help menu items, with name, priority and URL."""
return [{'name': 'About %s' % (config.APP_NAME), 'priority': 999, 'url': url_for('about.index')}]

View File

@ -0,0 +1 @@
About {{ config.APP_NAME }}

View File

@ -0,0 +1,27 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2014, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""A blueprint module implementing the about box."""
MODULE_NAME = 'about'
import config
from flask import Blueprint, current_app, render_template
from flask.ext.security import login_required
# Initialise the module
blueprint = Blueprint(MODULE_NAME, __name__, static_folder='static', static_url_path='', template_folder='templates', url_prefix='/' + MODULE_NAME)
##########################################################################
# A test page
##########################################################################
@blueprint.route("/")
@login_required
def index():
"""Render the about box."""
return render_template(MODULE_NAME + '/index.html')

View File

@ -16,18 +16,40 @@
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">File <span class="caret"></span></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">File <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu"> <ul class="dropdown-menu" role="menu">
<li><a href="{{ url_for('utils.test') }}">Test URL</a></li> {% for file_item in file_items %}
<li><a href="{{ url_for('utils.ping') }}">Ping</a></li> <li><a href="{{ file_item.url }}">{{ file_item.name|safe }}</a></li>
{% endfor %}
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Edit <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
{% for edit_item in edit_items %}
<li><a href="{{ edit_item.url }}">{{ edit_item.name|safe }}</a></li>
{% endfor %}
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Tools <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
{% for tools_item in tools_items %}
<li><a href="{{ tools_item.url }}">{{ tools_item.name|safe }}</a></li>
{% endfor %}
</ul> </ul>
</li> </li>
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Help <span class="caret"></span></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Help <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu"> <ul class="dropdown-menu" role="menu">
{% for help_item in help_items %}
<li><a href="{{ help_item.url }}">{{ help_item.name|safe }}</a></li>
{% endfor %}
</ul> </ul>
</li> </li>

View File

@ -10,11 +10,15 @@
"""A blueprint module implementing the core pgAdmin browser.""" """A blueprint module implementing the core pgAdmin browser."""
MODULE_NAME = 'browser' MODULE_NAME = 'browser'
import config
from flask import Blueprint, current_app, render_template from flask import Blueprint, current_app, render_template
from flaskext.gravatar import Gravatar from flaskext.gravatar import Gravatar
from flask.ext.security import login_required from flask.ext.security import login_required
from flask.ext.login import current_user from flask.ext.login import current_user
from inspect import getmoduleinfo, getmembers
from pgadmin import modules
import config
# Initialise the module # Initialise the module
blueprint = Blueprint(MODULE_NAME, __name__, static_folder='static', static_url_path='', template_folder='templates', url_prefix='/' + MODULE_NAME) blueprint = Blueprint(MODULE_NAME, __name__, static_folder='static', static_url_path='', template_folder='templates', url_prefix='/' + MODULE_NAME)
@ -26,6 +30,7 @@ blueprint = Blueprint(MODULE_NAME, __name__, static_folder='static', static_url
@login_required @login_required
def index(): def index():
"""Render and process the main browser window.""" """Render and process the main browser window."""
# Get the Gravatar
gravatar = Gravatar(current_app, gravatar = Gravatar(current_app,
size=100, size=100,
rating='g', rating='g',
@ -34,4 +39,37 @@ def index():
use_ssl=False, use_ssl=False,
base_url=None) base_url=None)
return render_template(MODULE_NAME + '/index.html', username=current_user.email) # Get the menu items from the module
file_items = [ ]
edit_items = [ ]
tools_items = [ ]
help_items = [ ]
for module in modules:
# Get the edit menu items
if 'browser' in dir(module) and 'get_file_menu_items' in dir(module.browser):
file_items.extend(module.browser.get_file_menu_items())
# Get the edit menu items
if 'browser' in dir(module) and 'get_edit_menu_items' in dir(module.browser):
edit_items.extend(module.browser.get_edit_menu_items())
# Get the tools menu items
if 'browser' in dir(module) and 'get_tools_menu_items' in dir(module.browser):
tools_items.extend(module.browser.get_tools_menu_items())
# Get the help menu items
if 'browser' in dir(module) and 'get_help_menu_items' in dir(module.browser):
help_items.extend(module.browser.get_help_menu_items())
file_items = sorted(file_items, key=lambda k: k['priority'])
edit_items = sorted(edit_items, key=lambda k: k['priority'])
tools_items = sorted(tools_items, key=lambda k: k['priority'])
help_items = sorted(help_items, key=lambda k: k['priority'])
return render_template(MODULE_NAME + '/index.html',
username=current_user.email,
file_items=file_items,
edit_items=edit_items,
tools_items=tools_items,
help_items=help_items)

View File

View File

@ -0,0 +1,16 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2014, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""Browser integration functions for the Test module."""
from flask import url_for
def get_file_menu_items():
"""Return a (set) of dicts of file menu items, with name, priority and URL."""
return [{'name': 'Generated Test HTML', 'priority': 100, 'url': url_for('test.generated')}]

35
web/pgadmin/test/views.py Normal file
View File

@ -0,0 +1,35 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2014, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""A blueprint module providing utility functions for the application."""
MODULE_NAME = 'test'
import config
from flask import Blueprint, render_template
from flask.ext.security import login_required
from time import time, ctime
# Initialise the module
blueprint = Blueprint(MODULE_NAME, __name__, static_folder='static', static_url_path='', template_folder='templates', url_prefix='/' + MODULE_NAME)
##########################################################################
# A test page
##########################################################################
@blueprint.route("/generated")
@login_required
def generated():
"""Generate a simple test page to demonstrate that output can be rendered."""
output = """
Today is <b>%s</b>
<br />
<i>This is Flask-generated HTML.</i>
<br /><br />
<a href="http://www.pgadmin.org/">%s v%s</a>""" % (ctime(time()), config.APP_NAME, config.APP_VERSION)
return output

View File

@ -18,22 +18,6 @@ from time import time, ctime
# Initialise the module # Initialise the module
blueprint = Blueprint(MODULE_NAME, __name__, static_folder='static', static_url_path='', template_folder='templates', url_prefix='/' + MODULE_NAME) blueprint = Blueprint(MODULE_NAME, __name__, static_folder='static', static_url_path='', template_folder='templates', url_prefix='/' + MODULE_NAME)
##########################################################################
# A test page
##########################################################################
@blueprint.route("/test")
@login_required
def test():
"""Generate a simple test page to demonstrate that output can be rendered."""
output = """
Today is <b>%s</b>
<br />
<i>This is Flask-generated HTML.</i>
<br /><br />
<a href="http://www.pgadmin.org/">%s v%s</a>""" % (ctime(time()), config.APP_NAME, config.APP_VERSION)
return output
########################################################################## ##########################################################################
# A special URL used to "ping" the server # A special URL used to "ping" the server
########################################################################## ##########################################################################