From eb6580b43a25bc0f1ade2e2c0c66c34d3cd87f81 Mon Sep 17 00:00:00 2001 From: Ronan Dunklau Date: Mon, 29 Jun 2015 12:28:41 +0530 Subject: [PATCH] Introduced a PgAdmin class inherited from the Flask, which looks for submodules inherited from the PgAdminModule instead of regular Blueprint. This allows us to load the module automatically from the under the pgadmin directory, and will work to extend the pgAdmin extension module. PgAdminModule is inherited from the Blueprint, and bring several methods: - get_own_stylesheets, which returns the stylesheets used by the module (excluding its submodules stylesheets) - get_own_javascripts - menu_items, which returns a dictionray mapping the old hook names (context_items etc) to a list of MenuItem instances For more specialized modules (as for now, any module that should be part of the browser tree construction), one can define an abstract base class defining additional methods. For example, the BrowserPluginModule abstract base class defines the following methods: - jssnippets - csssnipeets - node_type - get_nodes --- web/pgAdmin4.py | 8 +- web/pgadmin/__init__.py | 109 +++++---- web/pgadmin/browser/__init__.py | 159 ++++++++++++- web/pgadmin/browser/hooks.py | 21 -- web/pgadmin/browser/server_groups/__init__.py | 202 ++++++++++++++++- web/pgadmin/browser/server_groups/hooks.py | 78 ------- .../browser/server_groups/servers/__init__.py | 166 +++++++++++++- .../browser/server_groups/servers/hooks.py | 72 ------ .../browser/server_groups/servers/views.py | 121 ---------- .../templates/server_groups/server_groups.js | 2 +- web/pgadmin/browser/server_groups/views.py | 129 ----------- .../browser/templates/browser/css/browser.css | 3 + .../browser/templates/browser/css/node.css | 3 + .../browser/templates/browser/index.html | 2 +- .../browser/templates/browser/js/browser.js | 160 ++++++------- web/pgadmin/browser/views.py | 214 ------------------ web/pgadmin/misc/__init__.py | 25 ++ web/pgadmin/misc/views.py | 27 --- web/pgadmin/redirects/__init__.py | 19 ++ web/pgadmin/redirects/views.py | 29 --- web/pgadmin/settings/__init__.py | 85 ++++++- web/pgadmin/settings/views.py | 83 ------- web/pgadmin/templates/base.html | 16 +- web/pgadmin/utils/__init__.py | 80 +++++++ web/pgadmin/utils/menu.py | 6 + 25 files changed, 876 insertions(+), 943 deletions(-) delete mode 100644 web/pgadmin/browser/hooks.py delete mode 100644 web/pgadmin/browser/server_groups/hooks.py delete mode 100644 web/pgadmin/browser/server_groups/servers/hooks.py delete mode 100644 web/pgadmin/browser/server_groups/servers/views.py delete mode 100644 web/pgadmin/browser/server_groups/views.py create mode 100644 web/pgadmin/browser/templates/browser/css/browser.css create mode 100644 web/pgadmin/browser/templates/browser/css/node.css delete mode 100644 web/pgadmin/browser/views.py delete mode 100644 web/pgadmin/misc/views.py delete mode 100644 web/pgadmin/redirects/views.py delete mode 100644 web/pgadmin/settings/views.py create mode 100644 web/pgadmin/utils/menu.py diff --git a/web/pgAdmin4.py b/web/pgAdmin4.py index 63710852e..9d2cff2dc 100644 --- a/web/pgAdmin4.py +++ b/web/pgAdmin4.py @@ -7,7 +7,7 @@ # ########################################################################## -"""This is the main application entry point for pgAdmin 4. If running on +"""This is the main application entry point for pgAdmin 4. If running on a webserver, this will provide the WSGI interface, otherwise, we're going to start a web server.""" @@ -50,8 +50,11 @@ if not os.path.isfile(config.SQLITE_PATH): # Create the app! app = create_app() +#if config.DEBUG: +# app.debug = True + # Start the web server. The port number should have already been set by the -# runtime if we're running in desktop mode, otherwise we'll just use the +# runtime if we're running in desktop mode, otherwise we'll just use the # Flask default. if 'PGADMIN_PORT' in globals(): app.logger.debug('PGADMIN_PORT set in the runtime environment to %s', PGADMIN_PORT) @@ -64,4 +67,3 @@ try: app.run(port=server_port) except IOError: app.logger.error("Error starting the app server: %s", sys.exc_info()) - diff --git a/web/pgadmin/__init__.py b/web/pgadmin/__init__.py index ee8bc1200..2eb0982f4 100644 --- a/web/pgadmin/__init__.py +++ b/web/pgadmin/__init__.py @@ -10,27 +10,50 @@ """The main pgAdmin module. This handles the application initialisation tasks, such as setup of logging, dynamic loading of modules etc.""" -from flask import Flask, abort, request +from flask import Flask, abort, request, current_app from flask.ext.babel import Babel -from flask.ext.sqlalchemy import SQLAlchemy -from flask.ext.security import Security, SQLAlchemyUserDatastore, login_required +from flask.ext.security import Security, SQLAlchemyUserDatastore from flask_security.utils import login_user from flask_mail import Mail from htmlmin.minify import html_minify from settings.settings_model import db, Role, User - -import inspect, imp, logging, os, sys +from importlib import import_module +from werkzeug.local import LocalProxy +from pgadmin.utils import PgAdminModule +from werkzeug.utils import find_modules +import sys +import logging # Configuration settings import config -# Global module list -modules = [ ] + +class PgAdmin(Flask): + + def find_submodules(self, basemodule): + for module_name in find_modules(basemodule, True): + if module_name in self.config['MODULE_BLACKLIST']: + self.logger.info('Skipping blacklisted module: %s' % + module_name) + continue + self.logger.info('Examining potential module: %s' % module_name) + module = import_module(module_name) + for key, value in module.__dict__.items(): + if isinstance(value, PgAdminModule): + yield value + + +def _find_blueprint(): + if request.blueprint: + return current_app.blueprints[request.blueprint] + +current_blueprint = LocalProxy(_find_blueprint) + def create_app(app_name=config.APP_NAME): """Create the Flask application, startup logging and dynamically load additional modules (blueprints) that are found in this directory.""" - app = Flask(__name__, static_url_path='/static') + app = PgAdmin(__name__, static_url_path='/static') app.config.from_object(config) ########################################################################## @@ -42,7 +65,7 @@ def create_app(app_name=config.APP_NAME): app.logger.setLevel(logging.DEBUG) app.logger.handlers = [] - # We also need to update the handler on the webserver in order to see request. + # We also need to update the handler on the webserver in order to see request. # Setting the level prevents werkzeug from setting up it's own stream handler # thus ensuring all the logging goes through the pgAdmin logger. logger = logging.getLogger('werkzeug') @@ -67,82 +90,48 @@ def create_app(app_name=config.APP_NAME): app.logger.info('Starting %s v%s...', config.APP_NAME, config.APP_VERSION) app.logger.info('################################################################################') app.logger.debug("Python syspath: %s", sys.path) - + ########################################################################## # Setup i18n ########################################################################## - + # Initialise i18n babel = Babel(app) - + app.logger.debug('Available translations: %s' % babel.list_translations()) @babel.localeselector def get_locale(): """Get the best language for the user.""" language = request.accept_languages.best_match(config.LANGUAGES.keys()) - return language + return language ########################################################################## # Setup authentication ########################################################################## - + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + config.SQLITE_PATH.replace('\\', '/') # Only enable password related functionality in server mode. - if config.SERVER_MODE == True: + if config.SERVER_MODE is True: # TODO: Figure out how to disable /logout and /login app.config['SECURITY_RECOVERABLE'] = True app.config['SECURITY_CHANGEABLE'] = True # Create database connection object and mailer db.init_app(app) - mail = Mail(app) + Mail(app) # Setup Flask-Security user_datastore = SQLAlchemyUserDatastore(db, User, Role) - security = Security(app, user_datastore) + Security(app, user_datastore) ########################################################################## # Load plugin modules ########################################################################## - - path = os.path.dirname(os.path.realpath(__file__)) - files = os.listdir(path) - - for f in files: - d = os.path.join(path, f) - if os.path.isdir(d) and os.path.isfile(os.path.join(d, '__init__.py')): - - if f in config.MODULE_BLACKLIST: - app.logger.info('Skipping blacklisted module: %s' % f) - continue - - # Construct the "real" module name - f = 'pgadmin.' + f - - # 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 - # module here. Note that we also try to load the 'hooks' module for - # the browser integration hooks and other similar functions. - app.logger.info('Examining potential module: %s' % d) - module = __import__(f, globals(), locals(), ['hooks', 'views'], -1) - - # 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.register_blueprint(module.views.blueprint) - app.logger.debug(' - root_path: %s' % module.views.blueprint.root_path) - app.logger.debug(' - static_folder: %s' % module.views.blueprint.static_folder) - app.logger.debug(' - template_folder: %s' % module.views.blueprint.template_folder) - - # Register any sub-modules - if 'hooks' in dir(module) and 'register_submodules' in dir(module.hooks): - app.logger.info('Registering sub-modules in %s' % f) - module.hooks.register_submodules(app) + for module in app.find_submodules('pgadmin'): + app.logger.info('Registering blueprint module: %s' % module) + app.register_blueprint(module) ########################################################################## # Handle the desktop login @@ -151,7 +140,7 @@ def create_app(app_name=config.APP_NAME): @app.before_request def before_request(): """Login the default user if running in desktop mode""" - if config.SERVER_MODE == False: + if config.SERVER_MODE is False: user = user_datastore.get_user(config.DESKTOP_USER) # Throw an error if we failed to find the desktop user, to give @@ -165,7 +154,7 @@ def create_app(app_name=config.APP_NAME): ########################################################################## # Minify output - ########################################################################## + ########################################################################## @app.after_request def response_minify(response): """Minify html response to decrease traffic""" @@ -177,10 +166,16 @@ def create_app(app_name=config.APP_NAME): return response + @app.context_processor + def inject_blueprint(): + """Inject a reference to the current blueprint, if any.""" + return { + 'current_blueprint': current_blueprint, + 'menu_items': getattr(current_blueprint, "menu_items", {})} + ########################################################################## # All done! ########################################################################## app.logger.debug('URL map: %s' % app.url_map) return app - diff --git a/web/pgadmin/browser/__init__.py b/web/pgadmin/browser/__init__.py index 5442b7dc2..e340c836d 100644 --- a/web/pgadmin/browser/__init__.py +++ b/web/pgadmin/browser/__init__.py @@ -6,7 +6,160 @@ # This software is released under the PostgreSQL Licence # ########################################################################## +from abc import ABCMeta, abstractmethod, abstractproperty +from pgadmin import current_blueprint +from pgadmin.utils import PgAdminModule +from pgadmin.utils.ajax import make_json_response +from pgadmin.settings import get_setting +from flask import current_app, render_template, url_for, make_response +from flask.ext.security import login_required +from flask.ext.login import current_user +from flaskext.gravatar import Gravatar -# Define the node lists -all_nodes = [ ] -sub_nodes = [ ] \ No newline at end of file +MODULE_NAME = 'browser' + +class BrowserModule(PgAdminModule): + + + + def get_own_stylesheets(self): + stylesheets = [] + # Add browser stylesheets + for (endpoint, filename) in [ + ('static', 'css/codemirror/codemirror.css'), + ('static', 'css/wcDocker/theme.css'), + ('static', 'css/jQuery-contextMenu/jquery.contextMenu.css'), + ('browser.static', 'css/browser.css'), + ('browser.static', 'css/aciTree/css/aciTree.css') + ]: + stylesheets.append(url_for(endpoint, filename=filename)) + stylesheets.append(url_for('browser.browser_css')) + if current_app.debug: + stylesheets.append(url_for('static', filename='css/wcDocker/wcDockerSkeleton.css')) + else: + stylesheets.append(url_for('static', filename='css/wcDocker/wcDockerSkeleton.min.css')) + return stylesheets + + def get_own_javascripts(self): + scripts = [] + for (endpoint, filename) in [ + ('static', 'js/codemirror/codemirror.js'), + ('static', 'js/codemirror/mode/sql.js'), + ('static', 'js/jQuery-contextMenu/jquery.ui.position.js'), + ('static', 'js/jQuery-contextMenu/jquery.contextMenu.js'), + ('browser.static', 'js/aciTree/jquery.aciPlugin.min.js'), + ('browser.static', 'js/aciTree/jquery.aciTree.dom.js'), + ('browser.static', 'js/aciTree/jquery.aciTree.min.js')]: + scripts.append(url_for(endpoint, filename=filename)) + scripts.append(url_for('browser.browser_js')) + if current_app.debug: + scripts.append(url_for( + 'static', + filename='js/wcDocker/wcDocker.js')) + else: + scripts.append(url_for( + 'static', + filename='js/wcDocker/wcDocker.min.js')) + return scripts + + +blueprint = BrowserModule(MODULE_NAME, __name__) + +class BrowserPluginModule(PgAdminModule): + """ + Base class for browser submodules. + """ + + __metaclass__ = ABCMeta + + def __init__(self, import_name, **kwargs): + kwargs.setdefault("url_prefix", self.node_path) + kwargs.setdefault("static_url_path", '') + super(BrowserPluginModule, self).__init__("NODE-%s" % self.node_type, + import_name, + **kwargs) + + + @property + @abstractmethod + def jssnippets(self): + """ + Returns a snippet of javascript to include in the page + """ + # TODO: move those methods to BrowserModule subclass ? + return [] + + @property + def csssnippets(self): + """ + Returns a snippet of css to include in the page + """ + # TODO: move those methods to BrowserModule subclass ? + return render_template("browser/css/node.css", + node_type=self.node_type) + + @abstractmethod + def get_nodes(self): + """ + Each browser module is responsible for fetching + its own tree subnodes. + """ + return [] + + @abstractproperty + def node_type(self): + pass + + @property + def node_path(self): + return '/browser/nodes/' + self.node_type + + +@blueprint.route("/") +@login_required +def index(): + """Render and process the main browser window.""" + # Get the Gravatar + gravatar = Gravatar(current_app, + size=100, + rating='g', + default='retro', + force_default=False, + use_ssl=False, + base_url=None) + return render_template(MODULE_NAME + "/index.html", + username=current_user.email) + +@blueprint.route("/browser.js") +@login_required +def browser_js(): + layout = get_setting('Browser/Layout', default='') + snippets = [] + for submodule in current_blueprint.submodules: + snippets.extend(submodule.jssnippets) + return make_response(render_template( + 'browser/js/browser.js', + layout=layout, + jssnippets=snippets), + 200, {'Content-Type': 'application/x-javascript'}) + +@blueprint.route("/browser.css") +@login_required +def browser_css(): + """Render and return CSS snippets from the nodes and modules.""" + snippets = [] + for submodule in current_blueprint.submodules: + snippets.extend(submodule.csssnippets) + return make_response(render_template('browser/css/browser.css', + snippets=snippets), + 200, {'Content-Type': 'text/css'}) + + +@blueprint.route("/nodes/") +@login_required +def get_nodes(): + """Build a list of treeview nodes from the child nodes.""" + nodes = [] + for submodule in current_blueprint.submodules: + nodes.extend(submodule.get_nodes()) + return make_json_response(data=nodes) diff --git a/web/pgadmin/browser/hooks.py b/web/pgadmin/browser/hooks.py deleted file mode 100644 index c38279566..000000000 --- a/web/pgadmin/browser/hooks.py +++ /dev/null @@ -1,21 +0,0 @@ -########################################################################## -# -# pgAdmin 4 - PostgreSQL Tools -# -# Copyright (C) 2013 - 2015, The pgAdmin Development Team -# This software is released under the PostgreSQL Licence -# -########################################################################## - -"""Browser application hooks""" - -import os, sys -import config - -from pgadmin.browser.utils import register_modules -from pgadmin.browser import all_nodes -from . import sub_nodes - -def register_submodules(app): - """Register any child node blueprints""" - register_modules(app, __file__, all_nodes, sub_nodes, 'pgadmin.browser') diff --git a/web/pgadmin/browser/server_groups/__init__.py b/web/pgadmin/browser/server_groups/__init__.py index 95535f709..bfcc7f333 100644 --- a/web/pgadmin/browser/server_groups/__init__.py +++ b/web/pgadmin/browser/server_groups/__init__.py @@ -6,10 +6,202 @@ # This software is released under the PostgreSQL Licence # ########################################################################## +"""Defines views for management of server groups""" -# Node meta data -NODE_TYPE = 'server-group' -NODE_PATH = '/browser/nodes/' + NODE_TYPE +from abc import ABCMeta, abstractmethod +import traceback +from flask import Blueprint, Response, current_app, request, render_template +from flask.ext.babel import gettext +from flask.ext.security import current_user, login_required +from pgadmin import current_blueprint +from pgadmin.utils.ajax import make_json_response +from pgadmin.browser import BrowserPluginModule +from pgadmin.utils.menu import MenuItem +from pgadmin.settings.settings_model import db, ServerGroup +import config -# Define the child node list -sub_nodes = [ ] \ No newline at end of file + + +class ServerGroupModule(BrowserPluginModule): + + NODE_TYPE = "server-group" + + def get_own_menuitems(self): + return { + 'standard_items': [ + ServerGroupMenuItem(action="drop", priority=10, function="drop_server_group"), + ServerGroupMenuItem(action="rename", priority=10, function="rename_server_group") + ], + 'create_items': [ + ServerGroupMenuItem(name="create_server_group", + label=gettext('Server Group...'), + priority=10, + function="create_server_group", + types=[self.node_type]) + ], + 'context_items': [ + ServerGroupMenuItem(name="delete_server_group", + label=gettext('Delete server group'), + priority=10, + onclick='drop_server_group(item);'), + ServerGroupMenuItem(name="rename_server_group", + label=gettext('Rename server group...'), + priority=10, + onclick='rename_server_group(item);') + ] + } + + + @property + def jssnippets(self): + snippets = [render_template("server_groups/server_groups.js")] + for module in self.submodules: + snippets.extend(module.jssnippets) + return snippets + + def get_nodes(self, **kwargs): + """Return a JSON document listing the server groups for the user""" + groups = ServerGroup.query.filter_by(user_id=current_user.id) + # TODO: Move this JSON generation to a Server method + # this code is duplicated somewhere else + for group in groups: + yield { + "id": "%s/%d" % (self.node_type, group.id), + "label": group.name, + "icon": "icon-%s" % self.node_type, + "inode": True, + "_type": self.node_type + } + + @property + def node_type(self): + return self.NODE_TYPE + + + +class ServerGroupMenuItem(MenuItem): + + def __init__(self, **kwargs): + kwargs.setdefault("type", ServerGroupModule.NODE_TYPE) + super(ServerGroupMenuItem, self).__init__(**kwargs) + + +class ServerGroupPluginModule(BrowserPluginModule): + """ + Base class for server group plugins. + """ + + __metaclass__ = ABCMeta + + + @abstractmethod + def get_nodes(self, servergroup): + pass + + +# Initialise the module +blueprint = ServerGroupModule( __name__, static_url_path='') + +@blueprint.route("/") +@login_required +def get_nodes(server_group): + """Build a list of treeview nodes from the child nodes.""" + nodes = [] + for module in current_blueprint.submodules: + nodes.extend(module.get_nodes(server_group=server_group)) + return make_json_response(data=nodes) + + +@blueprint.route('/add/', methods=['POST']) +@login_required +def add(): + """Add a server group node to the settings database""" + success = 1 + errormsg = '' + data = { } + + if request.form['name'] != '': + servergroup = ServerGroup(user_id=current_user.id, name=request.form['name']) + + try: + db.session.add(servergroup) + db.session.commit() + except Exception as e: + success = 0 + errormsg = e.message + + else: + success = 0 + errormsg = gettext('No server group name was specified') + + if success == 1: + data['id'] = servergroup.id + data['name'] = servergroup.name + + return make_json_response(success=success, + errormsg=errormsg, + info=traceback.format_exc(), + result=request.form, + data=data) + +@blueprint.route('/delete/', methods=['POST']) +@login_required +def delete(): + """Delete a server group node in the settings database""" + success = 1 + errormsg = '' + + if request.form['id'] != '': + # There can be only one record at most + servergroup = ServerGroup.query.filter_by(user_id=current_user.id, id=int(request.form['id'])).first() + + if servergroup is None: + success = 0 + errormsg = gettext('The specified server group could not be found.') + else: + try: + db.session.delete(servergroup) + db.session.commit() + except Exception as e: + success = 0 + errormsg = e.message + + else: + success = 0 + errormsg = gettext('No server group was specified.') + + return make_json_response(success=success, + errormsg=errormsg, + info=traceback.format_exc(), + result=request.form) + +@blueprint.route('/rename/', methods=['POST']) +@login_required +def rename(): + """Rename a server group node in the settings database""" + success = 1 + errormsg = '' + + if request.form['id'] != '': + # There can be only one record at most + servergroup = ServerGroup.query.filter_by(user_id=current_user.id, id=int(request.form['id'])).first() + + if servergroup is None: + success = 0 + errormsg = gettext('The specified server group could not be found.') + else: + try: + servergroup.name = request.form['name'] + db.session.commit() + except Exception as e: + success = 0 + errormsg = e.message + + else: + success = 0 + errormsg = gettext('No server group was specified.') + + return make_json_response(success=success, + errormsg=errormsg, + info=traceback.format_exc(), + result=request.form) diff --git a/web/pgadmin/browser/server_groups/hooks.py b/web/pgadmin/browser/server_groups/hooks.py deleted file mode 100644 index 413e0935e..000000000 --- a/web/pgadmin/browser/server_groups/hooks.py +++ /dev/null @@ -1,78 +0,0 @@ -########################################################################## -# -# pgAdmin 4 - PostgreSQL Tools -# -# Copyright (C) 2013 - 2015, The pgAdmin Development Team -# This software is released under the PostgreSQL Licence -# -########################################################################## - -"""Integration hooks for server groups.""" - -from flask import render_template, url_for -from flask.ext.babel import gettext -from flask.ext.security import current_user - -from pgadmin.browser.utils import register_modules -from pgadmin.settings.settings_model import db, ServerGroup - -from pgadmin.browser import all_nodes -from . import NODE_TYPE, sub_nodes - -def register_submodules(app): - """Register any child node blueprints""" - register_modules(app, __file__, all_nodes, sub_nodes, 'pgadmin.browser.server_groups') - -def get_nodes(): - """Return a JSON document listing the server groups for the user""" - groups = ServerGroup.query.filter_by(user_id=current_user.id) - # TODO: Move this JSON generation to a Server method - # this code is duplicated somewhere else - for group in groups: - yield { - "id": "%s/%d" % (NODE_TYPE, group.id), - "label": group.name, - "icon": "icon-%s" % NODE_TYPE, - "inode": True, - "_type": NODE_TYPE - } - -def get_standard_menu_items(): - """Return a (set) of dicts of standard menu items (create/drop/rename), with - object type, action and the function name (no parens) to call on click.""" - return [ - {'type': 'server-group', 'action': 'drop', 'priority': 10, 'function': 'drop_server_group'}, - {'type': 'server-group', 'action': 'rename', 'priority': 20, 'function': 'rename_server_group'} - ] - - -def get_create_menu_items(): - """Return a (set) of dicts of create menu items, with a Javascript array of - object types on which the option should appear, name, label and the function - name (no parens) to call on click.""" - return [ - {'type': "['server-group']", 'name': 'create_server_group', 'label': gettext('Server Group...'), 'priority': 10, 'function': 'create_server_group'} - ] - - -def get_context_menu_items(): - """Return a (set) of dicts of content menu items with name, node type, label, priority and JS""" - return [ - {'name': 'delete_server_group', 'type': NODE_TYPE, 'label': gettext('Delete server group'), 'priority': 10, 'onclick': 'drop_server_group(item);'}, - {'name': 'rename_server_group', 'type': NODE_TYPE, 'label': gettext('Rename server group...'), 'priority': 20, 'onclick': 'rename_server_group(item);'} - ] - - -def get_script_snippets(): - """Return the script snippets needed to handle treeview node operations.""" - return render_template('server_groups/server_groups.js') - - -def get_css_snippets(): - """Return the CSS needed to display the treeview node image.""" - css = ".icon-server-group {\n" - css += " background: url('%s') 0 0 no-repeat !important;\n" % \ - url_for('NODE-%s.static' % NODE_TYPE, filename='img/server-group.png') - css += "}\n" - - return css diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py index a9e5e3c5b..855bb19b2 100644 --- a/web/pgadmin/browser/server_groups/servers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/__init__.py @@ -6,11 +6,167 @@ # This software is released under the PostgreSQL Licence # ########################################################################## +from flask import render_template, request +from pgadmin.browser.server_groups import ServerGroupPluginModule +from flask.ext.security import login_required, current_user +from pgadmin.settings.settings_model import db, Server +from pgadmin.utils.menu import MenuItem +from pgadmin.utils.ajax import make_json_response +import traceback +from flask.ext.babel import gettext -# Node meta data -NODE_TYPE = 'server' -NODE_PATH = '/browser/nodes/' + NODE_TYPE -# Define the child node list -sub_nodes = [ ] +class ServerModule(ServerGroupPluginModule): + + NODE_TYPE = "server" + + @property + def node_type(self): + return self.NODE_TYPE + + def get_nodes(self, server_group): + """Return a JSON document listing the server groups for the user""" + servers = Server.query.filter_by(user_id=current_user.id, servergroup_id=server_group) + + # TODO: Move this JSON generation to a Server method + for server in servers: + yield { + "id": "%s/%d" % (NODE_TYPE, server.id), + "label": server.name, + "icon": "icon-%s" % NODE_TYPE, + "inode": True, + "_type": NODE_TYPE + } + + def get_own_menuitems(self): + return { + 'standard_items': [ + ServerMenuItem(action="drop", priority=50, function='drop_server'), + ServerMenuItem(action="rename", priority=50, function='rename_server') + ], + 'create_items': [ + ServerMenuItem(types=["server-group", "server"], + name="create_server", + label=gettext('Server...'), + priority=50, + function='create_server') + ], + 'context_items': [ + ServerMenuItem(name='delete_server', + label=gettext('Delete server'), + priority=50, + onclick='drop_server'), + ServerMenuItem(name='rename_server', + label=gettext('Rename server...'), + priority=60, + onclick='rename_server(item);') + ] + } + + + @property + def jssnippets(self): + return [render_template("servers/servers.js")] + + +class ServerMenuItem(MenuItem): + + def __init__(self, **kwargs): + kwargs.setdefault("type", ServerModule.NODE_TYPE) + super(ServerMenuItem, self).__init__(**kwargs) + +blueprint = ServerModule(__name__) + +@blueprint.route('/add/', methods=['POST']) +@login_required +def add(): + """Add a server node to the settings database""" + success = 1 + errormsg = '' + data = {} + + success = False + errormsg = '' + if request.form['name'] != '': + server = Server(user_id=current_user.id, name=request.form['name']) + try: + db.session.add(server) + db.session.commit() + success = True + except Exception as e: + errormsg = e.message + else: + errormsg = gettext('No server name was specified') + + if success: + data['id'] = server.id + data['name'] = server.name + + return make_json_response(success=success, + errormsg=errormsg, + info=traceback.format_exc(), + result=request.form, + data=data) + +@blueprint.route('/delete/', methods=['POST']) +@login_required +def delete(): + """Delete a server node in the settings database""" + success = 1 + errormsg = '' + + if request.form['id'] != '': + # There can be only one record at most + servergroup = Server.query.filter_by(user_id=current_user.id, id=int(request.form['id'])).first() + + if server is None: + success = 0 + errormsg = gettext('The specified server could not be found.') + else: + try: + db.session.delete(server) + db.session.commit() + except Exception as e: + success = 0 + errormsg = e.message + + else: + success = 0 + errormsg = gettext('No server was specified.') + + return make_json_response(success=success, + errormsg=errormsg, + info=traceback.format_exc(), + result=request.form) + +@blueprint.route('/rename/', methods=['POST']) +@login_required +def rename(): + """Rename a server node in the settings database""" + success = 1 + errormsg = '' + + if request.form['id'] != '': + # There can be only one record at most + servergroup = Server.query.filter_by(user_id=current_user.id, id=int(request.form['id'])).first() + + if server is None: + success = 0 + errormsg = gettext('The specified server could not be found.') + else: + try: + server.name = request.form['name'] + db.session.commit() + except Exception as e: + success = 0 + errormsg = e.message + + else: + success = 0 + errormsg = gettext('No server was specified.') + + return make_json_response(success=success, + errormsg=errormsg, + info=traceback.format_exc(), + result=request.form) diff --git a/web/pgadmin/browser/server_groups/servers/hooks.py b/web/pgadmin/browser/server_groups/servers/hooks.py deleted file mode 100644 index 30a7c5f2f..000000000 --- a/web/pgadmin/browser/server_groups/servers/hooks.py +++ /dev/null @@ -1,72 +0,0 @@ -########################################################################## -# -# pgAdmin 4 - PostgreSQL Tools -# -# Copyright (C) 2013 - 2015, The pgAdmin Development Team -# This software is released under the PostgreSQL Licence -# -########################################################################## - -"""Integration hooks for servers.""" - -from flask import render_template, url_for -from flask.ext.babel import gettext -from flask.ext.security import current_user - -from pgadmin.settings.settings_model import db, Server -from . import NODE_TYPE - -def get_nodes(server_group): - """Return a JSON document listing the server groups for the user""" - servers = Server.query.filter_by(user_id=current_user.id, servergroup_id=server_group) - - # TODO: Move this JSON generation to a Server method - for server in servers: - yield { - "id": "%s/%d" % (NODE_TYPE, server.id), - "label": server.name, - "icon": "icon-%s" % NODE_TYPE, - "inode": True, - "_type": NODE_TYPE - } - - -def get_standard_menu_items(): - """Return a (set) of dicts of standard menu items (create/drop/rename), with - object type, action, priority and the function to call on click.""" - return [ - {'type': 'server', 'action': 'drop', 'priority': 50, 'function': 'drop_server'}, - {'type': 'server', 'action': 'rename', 'priority': 60, 'function': 'rename_server'} - ] - - -def get_create_menu_items(): - """Return a (set) of dicts of create menu items, with a Javascript array of - object types on which the option should appear, name, label, priority and - the function name (no parens) to call on click.""" - return [ - {'type': "['server-group', 'server']", 'name': 'create_server', 'label': gettext('Server...'), 'priority': 50, 'function': 'create_server'} - ] - - -def get_context_menu_items(): - """Return a (set) of dicts of content menu items with name, node type, label, priority and JS""" - return [ - {'name': 'delete_server', 'type': NODE_TYPE, 'label': gettext('Delete server'), 'priority': 50, 'onclick': 'drop_server(item);'}, - {'name': 'rename_server', 'type': NODE_TYPE, 'label': gettext('Rename server...'), 'priority': 60, 'onclick': 'rename_server(item);'} - ] - - -def get_script_snippets(): - """Return the script snippets needed to handle treeview node operations.""" - return render_template('servers/servers.js') - - -def get_css_snippets(): - """Return the CSS needed to display the treeview node image.""" - css = ".icon-server {\n" - css += " background: url('%s') 0 0 no-repeat !important;\n" % \ - url_for('NODE-%s.static' % NODE_TYPE, filename='img/server.png') - css += "}\n" - - return css diff --git a/web/pgadmin/browser/server_groups/servers/views.py b/web/pgadmin/browser/server_groups/servers/views.py deleted file mode 100644 index fb38dcd09..000000000 --- a/web/pgadmin/browser/server_groups/servers/views.py +++ /dev/null @@ -1,121 +0,0 @@ -########################################################################## -# -# pgAdmin 4 - PostgreSQL Tools -# -# Copyright (C) 2013 - 2015, The pgAdmin Development Team -# This software is released under the PostgreSQL Licence -# -########################################################################## - -"""Defines views for management of servers""" - -from flask import Blueprint, request -from flask.ext.babel import gettext -from flask.ext.security import current_user, login_required - -from . import NODE_TYPE, NODE_PATH -from pgadmin.utils.ajax import make_json_response -from pgadmin.settings.settings_model import db, Server -import traceback - -# Initialise the module -blueprint = Blueprint("NODE-" + NODE_TYPE, __name__, - static_folder='static', - static_url_path='', - template_folder='templates', - url_prefix=NODE_PATH) - - -@blueprint.route('/add/', methods=['POST']) -@login_required -def add(): - """Add a server node to the settings database""" - success = 1 - errormsg = '' - data = {} - - success = False - errormsg = '' - if request.form['name'] != '': - server = Server(user_id=current_user.id, name=request.form['name']) - try: - db.session.add(server) - db.session.commit() - success = True - except Exception as e: - errormsg = e.message - else: - errormsg = gettext('No server name was specified') - - if success: - data['id'] = server.id - data['name'] = server.name - - return make_json_response(success=success, - errormsg=errormsg, - info=traceback.format_exc(), - result=request.form, - data=data) - -@blueprint.route('/delete/', methods=['POST']) -@login_required -def delete(): - """Delete a server node in the settings database""" - success = 1 - errormsg = '' - - if request.form['id'] != '': - # There can be only one record at most - servergroup = Server.query.filter_by(user_id=current_user.id, id=int(request.form['id'])).first() - - if server is None: - success = 0 - errormsg = gettext('The specified server could not be found.') - else: - try: - db.session.delete(server) - db.session.commit() - except Exception as e: - success = 0 - errormsg = e.message - - else: - success = 0 - errormsg = gettext('No server was specified.') - - return make_json_response(success=success, - errormsg=errormsg, - info=traceback.format_exc(), - result=request.form) - -@blueprint.route('/rename/', methods=['POST']) -@login_required -def rename(): - """Rename a server node in the settings database""" - success = 1 - errormsg = '' - - if request.form['id'] != '': - # There can be only one record at most - servergroup = Server.query.filter_by(user_id=current_user.id, id=int(request.form['id'])).first() - - if server is None: - success = 0 - errormsg = gettext('The specified server could not be found.') - else: - try: - server.name = request.form['name'] - db.session.commit() - except Exception as e: - success = 0 - errormsg = e.message - - else: - success = 0 - errormsg = gettext('No server was specified.') - - return make_json_response(success=success, - errormsg=errormsg, - info=traceback.format_exc(), - result=request.form) - diff --git a/web/pgadmin/browser/server_groups/templates/server_groups/server_groups.js b/web/pgadmin/browser/server_groups/templates/server_groups/server_groups.js index 29b5449ec..f1fa85d8f 100644 --- a/web/pgadmin/browser/server_groups/templates/server_groups/server_groups.js +++ b/web/pgadmin/browser/server_groups/templates/server_groups/server_groups.js @@ -79,4 +79,4 @@ function rename_server_group(item) { }, null ) -} \ No newline at end of file +} diff --git a/web/pgadmin/browser/server_groups/views.py b/web/pgadmin/browser/server_groups/views.py deleted file mode 100644 index 5348819a5..000000000 --- a/web/pgadmin/browser/server_groups/views.py +++ /dev/null @@ -1,129 +0,0 @@ -########################################################################## -# -# pgAdmin 4 - PostgreSQL Tools -# -# Copyright (C) 2013 - 2015, The pgAdmin Development Team -# This software is released under the PostgreSQL Licence -# -########################################################################## - -"""Defines views for management of server groups""" - -import traceback -from flask import Blueprint, Response, current_app, request -from flask.ext.babel import gettext -from flask.ext.security import current_user, login_required - -from . import NODE_TYPE, NODE_PATH, sub_nodes -from pgadmin.utils.ajax import make_json_response -from pgadmin.settings.settings_model import db, ServerGroup -import config - -# Initialise the module -blueprint = Blueprint("NODE-" + NODE_TYPE, __name__, static_folder='static', static_url_path='', template_folder='templates', url_prefix=NODE_PATH) - -@blueprint.route("/") -@login_required -def get_nodes(server_group): - """Build a list of treeview nodes from the child nodes.""" - nodes = [] - for node in sub_nodes: - if hasattr(node, 'hooks') and hasattr(node.hooks, 'get_nodes'): - nodes.extend(node.hooks.get_nodes(server_group)) - return make_json_response(data=nodes) - - -@blueprint.route('/add/', methods=['POST']) -@login_required -def add(): - """Add a server group node to the settings database""" - success = 1 - errormsg = '' - data = { } - - if request.form['name'] != '': - servergroup = ServerGroup(user_id=current_user.id, name=request.form['name']) - - try: - db.session.add(servergroup) - db.session.commit() - except Exception as e: - success = 0 - errormsg = e.message - - else: - success = 0 - errormsg = gettext('No server group name was specified') - - if success == 1: - data['id'] = servergroup.id - data['name'] = servergroup.name - - return make_json_response(success=success, - errormsg=errormsg, - info=traceback.format_exc(), - result=request.form, - data=data) - -@blueprint.route('/delete/', methods=['POST']) -@login_required -def delete(): - """Delete a server group node in the settings database""" - success = 1 - errormsg = '' - - if request.form['id'] != '': - # There can be only one record at most - servergroup = ServerGroup.query.filter_by(user_id=current_user.id, id=int(request.form['id'])).first() - - if servergroup is None: - success = 0 - errormsg = gettext('The specified server group could not be found.') - else: - try: - db.session.delete(servergroup) - db.session.commit() - except Exception as e: - success = 0 - errormsg = e.message - - else: - success = 0 - errormsg = gettext('No server group was specified.') - - return make_json_response(success=success, - errormsg=errormsg, - info=traceback.format_exc(), - result=request.form) - -@blueprint.route('/rename/', methods=['POST']) -@login_required -def rename(): - """Rename a server group node in the settings database""" - success = 1 - errormsg = '' - - if request.form['id'] != '': - # There can be only one record at most - servergroup = ServerGroup.query.filter_by(user_id=current_user.id, id=int(request.form['id'])).first() - - if servergroup is None: - success = 0 - errormsg = gettext('The specified server group could not be found.') - else: - try: - servergroup.name = request.form['name'] - db.session.commit() - except Exception as e: - success = 0 - errormsg = e.message - - else: - success = 0 - errormsg = gettext('No server group was specified.') - - return make_json_response(success=success, - errormsg=errormsg, - info=traceback.format_exc(), - result=request.form) - diff --git a/web/pgadmin/browser/templates/browser/css/browser.css b/web/pgadmin/browser/templates/browser/css/browser.css new file mode 100644 index 000000000..9fb9ed46f --- /dev/null +++ b/web/pgadmin/browser/templates/browser/css/browser.css @@ -0,0 +1,3 @@ +{% for snip in snippets %} +{{ snip }} +{% endfor %} diff --git a/web/pgadmin/browser/templates/browser/css/node.css b/web/pgadmin/browser/templates/browser/css/node.css new file mode 100644 index 000000000..3fea07422 --- /dev/null +++ b/web/pgadmin/browser/templates/browser/css/node.css @@ -0,0 +1,3 @@ +.icon-{{node_type}} { + background: url('{{ url_for('NODE-%s.static' % node_type, filename='img/%s.png' % node_type )}}') 0 0 no-repeat; +} diff --git a/web/pgadmin/browser/templates/browser/index.html b/web/pgadmin/browser/templates/browser/index.html index 481d268f4..a3b63f67a 100644 --- a/web/pgadmin/browser/templates/browser/index.html +++ b/web/pgadmin/browser/templates/browser/index.html @@ -56,7 +56,7 @@
  • {{ management_item.label }}
  • {% endfor %} {% endif %} - + {% if help_items is defined and help_items|count > 0 %}