2014-12-18 11:49:09 -06:00
|
|
|
##########################################################################
|
|
|
|
#
|
|
|
|
# pgAdmin 4 - PostgreSQL Tools
|
|
|
|
#
|
2016-01-18 08:48:14 -06:00
|
|
|
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
|
2014-12-18 11:49:09 -06:00
|
|
|
# This software is released under the PostgreSQL Licence
|
|
|
|
#
|
|
|
|
##########################################################################
|
|
|
|
|
2015-01-21 06:00:13 -06:00
|
|
|
"""The main pgAdmin module. This handles the application initialisation tasks,
|
|
|
|
such as setup of logging, dynamic loading of modules etc."""
|
2015-06-29 02:54:05 -05:00
|
|
|
from collections import defaultdict
|
2015-06-29 01:58:41 -05:00
|
|
|
from flask import Flask, abort, request, current_app
|
2015-02-25 11:06:00 -06:00
|
|
|
from flask.ext.babel import Babel
|
2015-06-29 01:58:41 -05:00
|
|
|
from flask.ext.security import Security, SQLAlchemyUserDatastore
|
2015-01-26 09:20:28 -06:00
|
|
|
from flask_security.utils import login_user
|
2015-01-22 09:56:23 -06:00
|
|
|
from flask_mail import Mail
|
2015-02-12 04:28:15 -06:00
|
|
|
from htmlmin.minify import html_minify
|
2016-03-07 05:48:24 -06:00
|
|
|
from pgadmin.model import db, Role, User, Version
|
2015-06-29 01:58:41 -05:00
|
|
|
from importlib import import_module
|
|
|
|
from werkzeug.local import LocalProxy
|
2015-10-20 02:03:18 -05:00
|
|
|
from pgadmin.utils import PgAdminModule, driver
|
2015-06-29 01:58:41 -05:00
|
|
|
from werkzeug.utils import find_modules
|
|
|
|
import sys
|
|
|
|
import logging
|
2014-12-18 11:49:09 -06:00
|
|
|
|
2016-03-22 10:05:43 -05:00
|
|
|
from pgadmin.utils.session import ServerSideSessionInterface
|
|
|
|
|
2016-02-29 11:38:05 -06:00
|
|
|
|
2014-12-18 11:49:09 -06:00
|
|
|
# Configuration settings
|
|
|
|
import config
|
|
|
|
|
2015-06-29 01:58:41 -05:00
|
|
|
|
|
|
|
class PgAdmin(Flask):
|
|
|
|
|
|
|
|
def find_submodules(self, basemodule):
|
|
|
|
for module_name in find_modules(basemodule, True):
|
|
|
|
if module_name in self.config['MODULE_BLACKLIST']:
|
2015-10-20 02:03:18 -05:00
|
|
|
self.logger.info(
|
|
|
|
'Skipping blacklisted module: %s' % module_name
|
|
|
|
)
|
2015-06-29 01:58:41 -05:00
|
|
|
continue
|
|
|
|
self.logger.info('Examining potential module: %s' % module_name)
|
|
|
|
module = import_module(module_name)
|
2015-11-06 04:23:19 -06:00
|
|
|
for key in list(module.__dict__.keys()):
|
|
|
|
if isinstance(module.__dict__[key], PgAdminModule):
|
|
|
|
yield module.__dict__[key]
|
2015-06-29 01:58:41 -05:00
|
|
|
|
2015-06-29 02:54:05 -05:00
|
|
|
@property
|
|
|
|
def submodules(self):
|
|
|
|
for blueprint in self.blueprints.values():
|
|
|
|
if isinstance(blueprint, PgAdminModule):
|
|
|
|
yield blueprint
|
|
|
|
|
|
|
|
@property
|
|
|
|
def stylesheets(self):
|
|
|
|
stylesheets = []
|
|
|
|
for module in self.submodules:
|
|
|
|
stylesheets.extend(getattr(module, "stylesheets", []))
|
2016-05-11 02:16:10 -05:00
|
|
|
return set(stylesheets)
|
2015-06-29 02:54:05 -05:00
|
|
|
|
2016-05-10 05:37:45 -05:00
|
|
|
@property
|
|
|
|
def messages(self):
|
|
|
|
messages = dict()
|
|
|
|
for module in self.submodules:
|
|
|
|
messages.update(getattr(module, "messages", dict()))
|
|
|
|
return messages
|
|
|
|
|
2015-06-29 02:54:05 -05:00
|
|
|
@property
|
|
|
|
def javascripts(self):
|
2016-04-14 02:04:14 -05:00
|
|
|
scripts = []
|
|
|
|
scripts_names = []
|
|
|
|
|
|
|
|
# Remove duplicate javascripts from the list
|
2015-06-29 02:54:05 -05:00
|
|
|
for module in self.submodules:
|
2016-04-14 02:04:14 -05:00
|
|
|
module_scripts = getattr(module, "javascripts", [])
|
|
|
|
for s in module_scripts:
|
|
|
|
if s['name'] not in scripts_names:
|
|
|
|
scripts.append(s)
|
|
|
|
scripts_names.append(s['name'])
|
|
|
|
|
|
|
|
return scripts
|
2015-06-29 02:54:05 -05:00
|
|
|
|
|
|
|
@property
|
|
|
|
def panels(self):
|
|
|
|
panels = []
|
|
|
|
for module in self.submodules:
|
|
|
|
panels.extend(module.get_panels())
|
|
|
|
return panels
|
2015-06-29 01:58:41 -05:00
|
|
|
|
2015-06-30 00:51:55 -05:00
|
|
|
@property
|
|
|
|
def menu_items(self):
|
|
|
|
from operator import attrgetter
|
|
|
|
|
|
|
|
menu_items = defaultdict(list)
|
|
|
|
for module in self.submodules:
|
|
|
|
for key, value in module.menu_items.items():
|
|
|
|
menu_items[key].extend(value)
|
2016-01-27 08:59:54 -06:00
|
|
|
menu_items = dict((key, sorted(value, key=attrgetter('priority')))
|
|
|
|
for key, value in menu_items.items())
|
2015-06-30 00:51:55 -05:00
|
|
|
return menu_items
|
|
|
|
|
2015-10-20 02:03:18 -05:00
|
|
|
|
2015-06-29 01:58:41 -05:00
|
|
|
def _find_blueprint():
|
|
|
|
if request.blueprint:
|
|
|
|
return current_app.blueprints[request.blueprint]
|
|
|
|
|
|
|
|
current_blueprint = LocalProxy(_find_blueprint)
|
|
|
|
|
2015-01-27 08:18:27 -06:00
|
|
|
|
2014-12-18 11:49:09 -06:00
|
|
|
def create_app(app_name=config.APP_NAME):
|
2015-01-21 06:00:13 -06:00
|
|
|
"""Create the Flask application, startup logging and dynamically load
|
|
|
|
additional modules (blueprints) that are found in this directory."""
|
2015-06-29 01:58:41 -05:00
|
|
|
app = PgAdmin(__name__, static_url_path='/static')
|
2015-12-21 23:13:24 -06:00
|
|
|
# Removes unwanted whitespace from render_template function
|
|
|
|
app.jinja_env.trim_blocks = True
|
2014-12-18 11:49:09 -06:00
|
|
|
app.config.from_object(config)
|
|
|
|
|
2016-03-22 10:05:43 -05:00
|
|
|
##########################################################################
|
|
|
|
# Setup session management
|
|
|
|
##########################################################################
|
|
|
|
app.session_interface = ServerSideSessionInterface(config.SESSION_DB_PATH)
|
|
|
|
|
2014-12-18 11:49:09 -06:00
|
|
|
##########################################################################
|
|
|
|
# Setup logging and log the application startup
|
|
|
|
##########################################################################
|
|
|
|
|
|
|
|
# Add SQL level logging, and set the base logging level
|
|
|
|
logging.addLevelName(25, 'SQL')
|
|
|
|
app.logger.setLevel(logging.DEBUG)
|
2014-12-18 11:56:17 -06:00
|
|
|
app.logger.handlers = []
|
2014-12-18 11:49:09 -06:00
|
|
|
|
2015-10-20 02:03:18 -05:00
|
|
|
# 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.
|
2014-12-18 11:49:09 -06:00
|
|
|
logger = logging.getLogger('werkzeug')
|
|
|
|
logger.setLevel(logging.INFO)
|
|
|
|
|
|
|
|
# File logging
|
|
|
|
fh = logging.FileHandler(config.LOG_FILE)
|
|
|
|
fh.setLevel(config.FILE_LOG_LEVEL)
|
|
|
|
fh.setFormatter(logging.Formatter(config.FILE_LOG_FORMAT))
|
|
|
|
app.logger.addHandler(fh)
|
|
|
|
logger.addHandler(fh)
|
|
|
|
|
|
|
|
# Console logging
|
|
|
|
ch = logging.StreamHandler()
|
|
|
|
ch.setLevel(config.CONSOLE_LOG_LEVEL)
|
|
|
|
ch.setFormatter(logging.Formatter(config.CONSOLE_LOG_FORMAT))
|
|
|
|
app.logger.addHandler(ch)
|
|
|
|
logger.addHandler(ch)
|
|
|
|
|
|
|
|
# Log the startup
|
2015-10-20 02:03:18 -05:00
|
|
|
app.logger.info('########################################################')
|
2014-12-18 11:49:09 -06:00
|
|
|
app.logger.info('Starting %s v%s...', config.APP_NAME, config.APP_VERSION)
|
2015-10-20 02:03:18 -05:00
|
|
|
app.logger.info('########################################################')
|
2015-03-10 08:09:11 -05:00
|
|
|
app.logger.debug("Python syspath: %s", sys.path)
|
2015-06-29 01:58:41 -05:00
|
|
|
|
2015-02-25 11:06:00 -06:00
|
|
|
##########################################################################
|
|
|
|
# Setup i18n
|
|
|
|
##########################################################################
|
2015-06-29 01:58:41 -05:00
|
|
|
|
2015-02-25 11:06:00 -06:00
|
|
|
# Initialise i18n
|
|
|
|
babel = Babel(app)
|
2015-06-29 01:58:41 -05:00
|
|
|
|
2015-02-25 11:06:00 -06:00
|
|
|
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())
|
2015-06-29 01:58:41 -05:00
|
|
|
return language
|
2015-02-25 11:06:00 -06:00
|
|
|
|
2015-01-22 09:56:23 -06:00
|
|
|
##########################################################################
|
|
|
|
# Setup authentication
|
|
|
|
##########################################################################
|
2015-06-29 01:58:41 -05:00
|
|
|
|
2016-05-10 05:28:59 -05:00
|
|
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///{0}?timeout={1}'.format(
|
|
|
|
config.SQLITE_PATH.replace('\\', '/'),
|
|
|
|
getattr(config, 'SQLITE_TIMEOUT', 500)
|
|
|
|
)
|
2015-01-26 09:20:28 -06:00
|
|
|
|
|
|
|
# Only enable password related functionality in server mode.
|
2015-06-29 01:58:41 -05:00
|
|
|
if config.SERVER_MODE is True:
|
2015-01-26 09:20:28 -06:00
|
|
|
# TODO: Figure out how to disable /logout and /login
|
|
|
|
app.config['SECURITY_RECOVERABLE'] = True
|
|
|
|
app.config['SECURITY_CHANGEABLE'] = True
|
2015-01-22 09:56:23 -06:00
|
|
|
|
|
|
|
# Create database connection object and mailer
|
|
|
|
db.init_app(app)
|
2015-06-29 01:58:41 -05:00
|
|
|
Mail(app)
|
2015-01-22 09:56:23 -06:00
|
|
|
|
2016-05-12 13:34:28 -05:00
|
|
|
import pgadmin.utils.storage as storage
|
|
|
|
storage.init_app(app)
|
|
|
|
|
2015-01-22 09:56:23 -06:00
|
|
|
# Setup Flask-Security
|
|
|
|
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
|
2015-07-22 11:42:39 -05:00
|
|
|
security = Security(app, user_datastore)
|
|
|
|
|
|
|
|
# Upgrade the schema (if required)
|
|
|
|
with app.app_context():
|
|
|
|
version = Version.query.filter_by(name='ConfigDB').first()
|
|
|
|
|
|
|
|
# Pre-flight checks
|
|
|
|
if int(version.value) < int(config.SETTINGS_SCHEMA_VERSION):
|
|
|
|
app.logger.info(
|
|
|
|
"""Upgrading the database schema from version {0} to {1}.""".format(
|
|
|
|
version.value, config.SETTINGS_SCHEMA_VERSION
|
|
|
|
)
|
|
|
|
)
|
|
|
|
from setup import do_upgrade
|
|
|
|
do_upgrade(app, user_datastore, security, version)
|
2015-01-22 09:56:23 -06:00
|
|
|
|
2016-04-25 05:03:48 -05:00
|
|
|
# Load all available serve drivers
|
|
|
|
driver.init_app(app)
|
|
|
|
|
2015-01-22 09:56:23 -06:00
|
|
|
##########################################################################
|
|
|
|
# Load plugin modules
|
|
|
|
##########################################################################
|
2015-06-29 01:58:41 -05:00
|
|
|
for module in app.find_submodules('pgadmin'):
|
|
|
|
app.logger.info('Registering blueprint module: %s' % module)
|
|
|
|
app.register_blueprint(module)
|
2015-01-19 10:38:47 -06:00
|
|
|
|
2015-01-26 09:20:28 -06:00
|
|
|
##########################################################################
|
|
|
|
# Handle the desktop login
|
|
|
|
##########################################################################
|
|
|
|
|
|
|
|
@app.before_request
|
|
|
|
def before_request():
|
|
|
|
"""Login the default user if running in desktop mode"""
|
2015-06-29 01:58:41 -05:00
|
|
|
if config.SERVER_MODE is False:
|
2015-01-26 09:20:28 -06:00
|
|
|
user = user_datastore.get_user(config.DESKTOP_USER)
|
|
|
|
|
|
|
|
# Throw an error if we failed to find the desktop user, to give
|
|
|
|
# the sysadmin a hint. We'll continue to try to login anyway as
|
|
|
|
# that'll through a nice 500 error for us.
|
|
|
|
if user is None:
|
2015-10-20 02:03:18 -05:00
|
|
|
app.logger.error(
|
|
|
|
'The desktop user %s was not found in the configuration database.'
|
|
|
|
% config.DESKTOP_USER
|
|
|
|
)
|
2015-01-26 09:20:28 -06:00
|
|
|
abort(401)
|
|
|
|
|
|
|
|
login_user(user)
|
2015-02-12 04:28:15 -06:00
|
|
|
|
|
|
|
##########################################################################
|
|
|
|
# Minify output
|
2015-06-29 01:58:41 -05:00
|
|
|
##########################################################################
|
2015-02-12 04:28:15 -06:00
|
|
|
@app.after_request
|
|
|
|
def response_minify(response):
|
|
|
|
"""Minify html response to decrease traffic"""
|
|
|
|
if config.MINIFY_HTML and not config.DEBUG:
|
|
|
|
if response.content_type == u'text/html; charset=utf-8':
|
|
|
|
response.set_data(
|
|
|
|
html_minify(response.get_data(as_text=True))
|
|
|
|
)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
2015-06-29 01:58:41 -05:00
|
|
|
@app.context_processor
|
|
|
|
def inject_blueprint():
|
|
|
|
"""Inject a reference to the current blueprint, if any."""
|
|
|
|
return {
|
2015-06-29 02:54:05 -05:00
|
|
|
'current_app': current_app,
|
2015-10-20 02:03:18 -05:00
|
|
|
'current_blueprint': current_blueprint
|
|
|
|
}
|
|
|
|
|
2015-01-26 09:20:28 -06:00
|
|
|
##########################################################################
|
2015-01-22 09:56:23 -06:00
|
|
|
# All done!
|
2015-01-26 09:20:28 -06:00
|
|
|
##########################################################################
|
2015-01-19 10:38:47 -06:00
|
|
|
app.logger.debug('URL map: %s' % app.url_map)
|
2015-10-20 02:03:18 -05:00
|
|
|
|
2014-12-18 11:49:09 -06:00
|
|
|
return app
|