mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Preferences dialogue. Patch by Ashesh and Khushboo Vashi.
This commit is contained in:
parent
43116750b4
commit
5ea822f33e
@ -138,7 +138,7 @@ MAX_SESSION_IDLE_TIME = 60
|
||||
|
||||
# The schema version number for the configuration database
|
||||
# DO NOT CHANGE UNLESS YOU ARE A PGADMIN DEVELOPER!!
|
||||
SETTINGS_SCHEMA_VERSION = 7
|
||||
SETTINGS_SCHEMA_VERSION = 8
|
||||
|
||||
# The default path to the SQLite database used to store user accounts and
|
||||
# settings. This default places the file in the same directory as this
|
||||
|
@ -16,7 +16,7 @@ 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 pgadmin.settings.settings_model import db, Role, User, Version
|
||||
from pgadmin.model import db, Role, User, Version
|
||||
from importlib import import_module
|
||||
from werkzeug.local import LocalProxy
|
||||
from pgadmin.utils import PgAdminModule, driver
|
||||
|
@ -29,8 +29,11 @@ except:
|
||||
|
||||
MODULE_NAME = 'browser'
|
||||
|
||||
|
||||
class BrowserModule(PgAdminModule):
|
||||
|
||||
LABEL = gettext('Browser')
|
||||
|
||||
def get_own_stylesheets(self):
|
||||
stylesheets = []
|
||||
# Add browser stylesheets
|
||||
@ -168,20 +171,56 @@ class BrowserModule(PgAdminModule):
|
||||
scripts.extend(module.get_own_javascripts())
|
||||
return scripts
|
||||
|
||||
def register_preferences(self):
|
||||
self.show_system_objects = self.preference.register(
|
||||
'display', 'show_system_objects',
|
||||
gettext("Show system objects"), 'boolean', False,
|
||||
category_label=gettext('Display')
|
||||
)
|
||||
|
||||
blueprint = BrowserModule(MODULE_NAME, __name__)
|
||||
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
class BrowserPluginModule(PgAdminModule):
|
||||
"""
|
||||
Base class for browser submodules.
|
||||
Abstract base class for browser submodules.
|
||||
|
||||
It helps to define the node for each and every node comes under the browser
|
||||
tree. It makes sure every module comes under browser will have prefix
|
||||
'/browser', and sets the 'url_prefix', 'static_url_path', etc.
|
||||
|
||||
Also, creates some of the preferences to be used by the node.
|
||||
"""
|
||||
|
||||
browser_url_prefix = blueprint.url_prefix + '/'
|
||||
SHOW_ON_BROWSER = True
|
||||
|
||||
def __init__(self, import_name, **kwargs):
|
||||
"""
|
||||
Construct a new 'BrowserPluginModule' object.
|
||||
|
||||
:param import_name: Name of the module
|
||||
:param **kwargs: Extra parameters passed to the base class
|
||||
pgAdminModule.
|
||||
|
||||
:return: returns nothing
|
||||
|
||||
It sets the url_prefix to based on the 'node_path'. And,
|
||||
static_url_path to relative path to '/static'.
|
||||
|
||||
Every module extended from this will be identified as 'NODE-<type>'.
|
||||
|
||||
Also, create a preference 'show_node_<type>' to fetch whether it
|
||||
can be shown in the browser or not. Also, refer to the browser-preference.
|
||||
"""
|
||||
kwargs.setdefault("url_prefix", self.node_path)
|
||||
kwargs.setdefault("static_url_path", '/static')
|
||||
|
||||
self.browser_preference = None
|
||||
self.pref_show_system_objects = None
|
||||
self.pref_show_node = None
|
||||
|
||||
super(BrowserPluginModule, self).__init__(
|
||||
"NODE-%s" % self.node_type,
|
||||
import_name,
|
||||
@ -196,6 +235,24 @@ class BrowserPluginModule(PgAdminModule):
|
||||
return []
|
||||
|
||||
def get_own_javascripts(self):
|
||||
"""
|
||||
Returns the list of javascripts information used by the module.
|
||||
|
||||
Each javascripts information must contain name, path of the script.
|
||||
|
||||
The name must be unique for each module, hence - in order to refer them
|
||||
properly, we do use 'pgadmin.node.<type>' as norm.
|
||||
|
||||
That can also refer to when to load the script.
|
||||
|
||||
i.e.
|
||||
We may not need to load the javascript of table node, when we're
|
||||
not yet connected to a server, and no database is loaded. Hence - it
|
||||
make sense to load them when a database is loaded.
|
||||
|
||||
We may also add 'deps', which also refers to the list of javascripts,
|
||||
it may depends on.
|
||||
"""
|
||||
scripts = []
|
||||
|
||||
scripts.extend([{
|
||||
@ -211,6 +268,27 @@ class BrowserPluginModule(PgAdminModule):
|
||||
def generate_browser_node(
|
||||
self, node_id, parent_id, label, icon, inode, node_type, **kwargs
|
||||
):
|
||||
"""
|
||||
Helper function to create a browser node for this particular subnode.
|
||||
|
||||
:param node_id: Unique Id for each node
|
||||
:param parent_id: Id of the parent.
|
||||
:param label: Label for the node
|
||||
:param icon: Icon for displaying along with this node on browser
|
||||
tree. Icon refers to a class name, it refers to.
|
||||
:param inode: True/False.
|
||||
Used by the browser tree node to check, if the
|
||||
current node will have children or not.
|
||||
:param node_type: String to refer to the node type.
|
||||
:param **kwargs: A node can have extra information other than this
|
||||
data, which can be passed as key-value pair as
|
||||
argument here.
|
||||
i.e. A database, server node can have extra
|
||||
information like connected, or not.
|
||||
|
||||
Returns a dictionary object representing this node object for the
|
||||
browser tree.
|
||||
"""
|
||||
obj = {
|
||||
"id": "%s/%s" % (node_type, node_id),
|
||||
"label": label,
|
||||
@ -269,12 +347,66 @@ class BrowserPluginModule(PgAdminModule):
|
||||
|
||||
@property
|
||||
def node_path(self):
|
||||
"""
|
||||
Defines the url path prefix for this submodule.
|
||||
"""
|
||||
return self.browser_url_prefix + self.node_type
|
||||
|
||||
@property
|
||||
def javascripts(self):
|
||||
"""
|
||||
Override the javascript of PgAdminModule, so that - we don't return
|
||||
javascripts from the get_own_javascripts itself.
|
||||
"""
|
||||
return []
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
"""
|
||||
Module label.
|
||||
"""
|
||||
return self.LABEL
|
||||
|
||||
@property
|
||||
def show_node(self):
|
||||
"""
|
||||
A proper to check to show node for this module on the browser tree or not.
|
||||
|
||||
Relies on show_node preference object, otherwise on the SHOW_ON_BROWSER
|
||||
default value.
|
||||
"""
|
||||
if self.pref_show_node:
|
||||
return self.pref_show_node.get()
|
||||
else:
|
||||
return self.SHOW_ON_BROWSER
|
||||
|
||||
@property
|
||||
def show_system_objects(self):
|
||||
"""
|
||||
Show/Hide the system objects in the database server.
|
||||
"""
|
||||
if self.pref_show_system_objects:
|
||||
return self.pref_show_system_objects.get()
|
||||
else:
|
||||
return False
|
||||
|
||||
def register_preferences(self):
|
||||
"""
|
||||
Registers the preferences object for this module.
|
||||
|
||||
Sets the browser_preference, show_system_objects, show_node preference
|
||||
objects for this submodule.
|
||||
"""
|
||||
# Add the node informaton for browser, not in respective node preferences
|
||||
self.browser_preference = blueprint.preference
|
||||
self.pref_show_system_objects = blueprint.preference.preference(
|
||||
'display', 'show_system_objects'
|
||||
)
|
||||
self.pref_show_node = self.browser_preference.preference(
|
||||
'node', 'show_node_' + self.node_type,
|
||||
self.label, 'boolean', self.SHOW_ON_BROWSER, category_label=gettext('Nodes')
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/")
|
||||
@login_required
|
||||
|
@ -14,6 +14,8 @@ from flask.ext.babel import gettext
|
||||
from pgadmin.utils import PgAdminModule
|
||||
from pgadmin.browser.utils import PGChildModule
|
||||
from pgadmin.browser import BrowserPluginModule
|
||||
from pgadmin.utils.preferences import Preferences
|
||||
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
class CollectionNodeModule(PgAdminModule, PGChildModule):
|
||||
@ -21,6 +23,7 @@ class CollectionNodeModule(PgAdminModule, PGChildModule):
|
||||
Base class for collection node submodules.
|
||||
"""
|
||||
browser_url_prefix = BrowserPluginModule.browser_url_prefix
|
||||
SHOW_ON_BROWSER = True
|
||||
|
||||
def __init__(self, import_name, **kwargs):
|
||||
kwargs.setdefault("url_prefix", self.node_path)
|
||||
@ -123,6 +126,10 @@ class CollectionNodeModule(PgAdminModule, PGChildModule):
|
||||
"""
|
||||
return self.COLLECTION_LABEL
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
return self.COLLECTION_LABEL
|
||||
|
||||
@property
|
||||
def collection_icon(self):
|
||||
"""
|
||||
@ -173,3 +180,47 @@ class CollectionNodeModule(PgAdminModule, PGChildModule):
|
||||
@property
|
||||
def javascripts(self):
|
||||
return []
|
||||
|
||||
@property
|
||||
def show_node(self):
|
||||
"""
|
||||
Property to check whether to show the node for this module on the
|
||||
browser tree or not.
|
||||
|
||||
Relies on show_node preference object, otherwise on the SHOW_ON_BROWSER
|
||||
default value.
|
||||
"""
|
||||
if self.pref_show_node:
|
||||
return self.pref_show_node.get()
|
||||
else:
|
||||
return self.SHOW_ON_BROWSER
|
||||
|
||||
@property
|
||||
def show_system_objects(self):
|
||||
"""
|
||||
Show/Hide the system objects in the database server.
|
||||
"""
|
||||
if self.pref_show_system_objects:
|
||||
return self.pref_show_system_objects.get()
|
||||
else:
|
||||
return False
|
||||
|
||||
def register_preferences(self):
|
||||
"""
|
||||
register_preferences
|
||||
Register preferences for this module.
|
||||
|
||||
Keep the browser preference object to be used by overriden submodule,
|
||||
along with that get two browser level preferences show_system_objects,
|
||||
and show_node will be registered to used by the submodules.
|
||||
"""
|
||||
# Add the node informaton for browser, not in respective node preferences
|
||||
self.browser_preference = Preferences.module('browser')
|
||||
self.pref_show_system_objects = self.browser_preference.preference(
|
||||
'show_system_objects'
|
||||
)
|
||||
self.pref_show_node = self.browser_preference.register(
|
||||
'node', 'show_node_' + self.node_type,
|
||||
self.collection_label, 'node', self.SHOW_ON_BROWSER,
|
||||
category_label=gettext('Nodes')
|
||||
)
|
||||
|
@ -9,17 +9,15 @@
|
||||
"""Defines views for management of server groups"""
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
import traceback
|
||||
import json
|
||||
from flask import request, render_template, make_response, jsonify
|
||||
from flask.ext.babel import gettext
|
||||
from flask.ext.security import current_user, login_required
|
||||
from pgadmin import current_blueprint
|
||||
from flask.ext.security import current_user
|
||||
from pgadmin.utils.ajax import make_json_response, \
|
||||
make_response as ajax_response
|
||||
from pgadmin.browser import BrowserPluginModule
|
||||
from pgadmin.utils.menu import MenuItem
|
||||
from pgadmin.settings.settings_model import db, ServerGroup
|
||||
from pgadmin.model import db, ServerGroup
|
||||
from pgadmin.browser.utils import NodeView
|
||||
import six
|
||||
|
||||
@ -41,12 +39,30 @@ class ServerGroupModule(BrowserPluginModule):
|
||||
|
||||
@property
|
||||
def node_type(self):
|
||||
"""
|
||||
node_type
|
||||
Node type for Server Group is server-group. It is defined by NODE_TYPE
|
||||
static attribute of the class.
|
||||
"""
|
||||
return self.NODE_TYPE
|
||||
|
||||
@property
|
||||
def script_load(self):
|
||||
"""
|
||||
script_load
|
||||
Load the server-group javascript module on loading of browser module.
|
||||
"""
|
||||
return None
|
||||
|
||||
def register_preferences(self):
|
||||
"""
|
||||
register_preferences
|
||||
Overrides the register_preferences PgAdminModule, because - we will not
|
||||
register any preference for server-group (specially the show_node
|
||||
preference.)
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ServerGroupMenuItem(MenuItem):
|
||||
|
||||
@ -153,7 +169,6 @@ class ServerGroupView(NodeView):
|
||||
sg = ServerGroup.query.filter_by(
|
||||
user_id=current_user.id,
|
||||
id=gid).first()
|
||||
data = {}
|
||||
|
||||
if sg is None:
|
||||
return make_json_response(
|
||||
@ -181,7 +196,7 @@ class ServerGroupView(NodeView):
|
||||
|
||||
data[u'id'] = sg.id
|
||||
data[u'name'] = sg.name
|
||||
|
||||
|
||||
return jsonify(
|
||||
node=self.blueprint.generate_browser_node(
|
||||
"%d" % (sg.id), None,
|
||||
|
@ -8,25 +8,22 @@
|
||||
##########################################################################
|
||||
|
||||
import json
|
||||
from abc import ABCMeta, abstractproperty
|
||||
from flask import render_template, request, make_response, jsonify, \
|
||||
current_app, url_for
|
||||
from flask.ext.security import login_required, current_user
|
||||
from pgadmin.settings.settings_model import db, Server, ServerGroup, User
|
||||
from flask.ext.security import current_user
|
||||
from pgadmin.model import db, Server, ServerGroup, User
|
||||
from pgadmin.utils.menu import MenuItem
|
||||
from pgadmin.utils.ajax import make_json_response, \
|
||||
make_response as ajax_response, internal_server_error, success_return, \
|
||||
unauthorized, bad_request, precondition_required, forbidden
|
||||
from pgadmin.utils.ajax import make_json_response, bad_request, forbidden, \
|
||||
make_response as ajax_response, internal_server_error, unauthorized
|
||||
from pgadmin.browser.utils import PGChildNodeView
|
||||
import traceback
|
||||
from flask.ext.babel import gettext
|
||||
import pgadmin.browser.server_groups as sg
|
||||
from pgadmin.utils.crypto import encrypt
|
||||
from pgadmin.browser import BrowserPluginModule
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
import six
|
||||
from pgadmin.browser.server_groups.servers.types import ServerType
|
||||
|
||||
|
||||
def has_any(data, keys):
|
||||
"""
|
||||
Checks any one of the keys present in the data given
|
||||
@ -46,6 +43,7 @@ def has_any(data, keys):
|
||||
|
||||
class ServerModule(sg.ServerGroupPluginModule):
|
||||
NODE_TYPE = "server"
|
||||
LABEL = gettext("Servers")
|
||||
|
||||
@property
|
||||
def node_type(self):
|
||||
@ -146,6 +144,15 @@ class ServerModule(sg.ServerGroupPluginModule):
|
||||
|
||||
super(ServerModule, self).register(app, options, first_registration)
|
||||
|
||||
# We do not have any preferences for server node.
|
||||
def register_preferences(self):
|
||||
"""
|
||||
register_preferences
|
||||
Override it so that - it does not register the show_node preference for
|
||||
server type.
|
||||
"""
|
||||
pass
|
||||
|
||||
class ServerMenuItem(MenuItem):
|
||||
def __init__(self, **kwargs):
|
||||
kwargs.setdefault("type", ServerModule.NODE_TYPE)
|
||||
|
@ -41,7 +41,8 @@ class DatabaseModule(CollectionNodeModule):
|
||||
"""
|
||||
Generate the collection node
|
||||
"""
|
||||
yield self.generate_browser_collection_node(sid)
|
||||
if self.show_node:
|
||||
yield self.generate_browser_collection_node(sid)
|
||||
|
||||
@property
|
||||
def script_load(self):
|
||||
@ -634,7 +635,7 @@ class DatabaseView(PGChildNodeView):
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
return make_json_response(
|
||||
data="-- modified SQL",
|
||||
data=_("-- modified SQL"),
|
||||
status=200
|
||||
)
|
||||
|
||||
|
@ -10,8 +10,7 @@ from flask import render_template, request, jsonify, current_app
|
||||
from flask.ext.babel import gettext as _
|
||||
from pgadmin.utils.ajax import make_json_response, \
|
||||
make_response as ajax_response, precondition_required, \
|
||||
internal_server_error, forbidden, \
|
||||
not_implemented, success_return, gone
|
||||
internal_server_error, forbidden, success_return, gone
|
||||
from pgadmin.browser.utils import PGChildNodeView
|
||||
from pgadmin.browser.collection import CollectionNodeModule
|
||||
import pgadmin.browser.server_groups as sg
|
||||
@ -37,8 +36,9 @@ class RoleModule(CollectionNodeModule):
|
||||
"""
|
||||
Generate the collection node
|
||||
"""
|
||||
|
||||
yield self.generate_browser_collection_node(sid)
|
||||
if self.show_node:
|
||||
yield self.generate_browser_collection_node(sid)
|
||||
pass
|
||||
|
||||
@property
|
||||
def node_inode(self):
|
||||
@ -480,7 +480,8 @@ rolmembership:{
|
||||
if check_permission:
|
||||
user = self.manager.user_info
|
||||
|
||||
if not user['is_superuser'] and not user['can_create_role']:
|
||||
if not user['is_superuser'] and \
|
||||
not user['can_create_role']:
|
||||
if (action != 'update' or
|
||||
'rid' in kwargs and kwargs['rid'] != -1 and
|
||||
user['id'] != kwargs['rid']):
|
||||
|
@ -26,12 +26,6 @@ class TablespaceModule(CollectionNodeModule):
|
||||
NODE_TYPE = 'tablespace'
|
||||
COLLECTION_LABEL = gettext("Tablespaces")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.min_ver = None
|
||||
self.max_ver = None
|
||||
|
||||
super(TablespaceModule, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_nodes(self, gid, sid):
|
||||
"""
|
||||
Generate the collection node
|
||||
|
@ -9,29 +9,14 @@
|
||||
|
||||
"""Browser helper utilities"""
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from abc import abstractmethod
|
||||
import flask
|
||||
from flask.views import View, MethodViewType, with_metaclass
|
||||
from flask.ext.babel import gettext
|
||||
from flask import render_template, current_app
|
||||
import six
|
||||
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
from pgadmin.utils.ajax import make_json_response, precondition_required
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
class NodeAttr(object):
|
||||
"""
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def validate(self, mode, value):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def schema(self):
|
||||
pass
|
||||
|
||||
|
||||
class PGChildModule(object):
|
||||
"""
|
||||
@ -47,10 +32,6 @@ class PGChildModule(object):
|
||||
- Return True when it supports certain version.
|
||||
Uses the psycopg2 server connection manager as input for checking the
|
||||
compatibility of the current module.
|
||||
|
||||
* AddAttr(attr)
|
||||
- This adds the attribute supported for specific version only. It takes
|
||||
NodeAttr as input, and update max_ver, min_ver variables for this module.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -58,9 +39,12 @@ class PGChildModule(object):
|
||||
self.max_ver = 1000000000
|
||||
self.server_type = None
|
||||
|
||||
super(PGChildModule, self).__init__(*args, **kwargs)
|
||||
super(PGChildModule, self).__init__()
|
||||
|
||||
def BackendSupported(self, manager, **kwargs):
|
||||
if hasattr(self, 'show_node'):
|
||||
if not self.show_node:
|
||||
return False
|
||||
sversion = getattr(manager, 'sversion', None)
|
||||
if (sversion is None or not isinstance(sversion, int)):
|
||||
return False
|
||||
@ -83,6 +67,7 @@ class PGChildModule(object):
|
||||
def get_nodes(self, sid=None, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class NodeView(with_metaclass(MethodViewType, View)):
|
||||
"""
|
||||
A PostgreSQL Object has so many operaions/functions apart from CRUD
|
||||
|
@ -109,3 +109,42 @@ class Server(db.Model):
|
||||
comment = db.Column(
|
||||
db.String(1024),
|
||||
nullable=True)
|
||||
|
||||
class ModulePreference(db.Model):
|
||||
"""Define a preferences table for any modules."""
|
||||
__tablename__ = 'module_preference'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(256), nullable=False)
|
||||
|
||||
class PreferenceCategory(db.Model):
|
||||
"""Define a preferences category for each modules."""
|
||||
__tablename__ = 'preference_category'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
mid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('module_preference.id'),
|
||||
nullable=False
|
||||
)
|
||||
name = db.Column(db.String(256), nullable=False)
|
||||
|
||||
class Preferences(db.Model):
|
||||
"""Define a particular preference."""
|
||||
__tablename__ = 'preferences'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
cid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('preference_category.id'),
|
||||
nullable=False
|
||||
)
|
||||
name = db.Column(db.String(1024), nullable=False)
|
||||
|
||||
class UserPreference(db.Model):
|
||||
"""Define the preference for a particular user."""
|
||||
__tablename__ = 'user_preferences'
|
||||
pid = db.Column(
|
||||
db.Integer, db.ForeignKey('preferences.id'), primary_key=True
|
||||
)
|
||||
uid = db.Column(
|
||||
db.Integer, db.ForeignKey('user.id'), primary_key=True
|
||||
)
|
||||
value = db.Column(db.String(1024), nullable=False)
|
137
web/pgadmin/preferences/__init__.py
Normal file
137
web/pgadmin/preferences/__init__.py
Normal file
@ -0,0 +1,137 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
|
||||
#
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
"""
|
||||
Implements the routes for creating Preferences/Options Dialog on the client
|
||||
side and for getting/setting preferences.
|
||||
"""
|
||||
|
||||
from pgadmin.utils import PgAdminModule
|
||||
from pgadmin.utils.ajax import success_return, \
|
||||
make_response as ajax_response, internal_server_error
|
||||
|
||||
from flask import render_template, url_for, Response, request
|
||||
from flask.ext.security import login_required
|
||||
from flask.ext.login import current_user
|
||||
from flask.ext.babel import gettext
|
||||
|
||||
from pgadmin.utils.menu import MenuItem
|
||||
from pgadmin.utils.preferences import Preferences
|
||||
import simplejson as json
|
||||
|
||||
|
||||
MODULE_NAME = 'preferences'
|
||||
|
||||
class PreferencesModule(PgAdminModule):
|
||||
"""
|
||||
PreferenceModule represets the preferences of different modules to the
|
||||
user in UI.
|
||||
|
||||
And, allows the user to modify (not add/remove) as per their requirement.
|
||||
"""
|
||||
|
||||
def get_own_javascripts(self):
|
||||
return [{
|
||||
'name': 'pgadmin.preferences',
|
||||
'path': url_for('preferences.index') + 'preferences',
|
||||
'when': None
|
||||
}]
|
||||
|
||||
def get_own_stylesheets(self):
|
||||
return [url_for('preferences.static', filename='css/preferences.css')]
|
||||
|
||||
def get_own_menuitems(self):
|
||||
return {
|
||||
'file_items': [
|
||||
MenuItem(name='mnu_preferences',
|
||||
priority=999,
|
||||
module="pgAdmin.Preferences",
|
||||
callback='show',
|
||||
icon='fa fa-cog',
|
||||
label=gettext('Preferences'))
|
||||
]
|
||||
}
|
||||
|
||||
blueprint = PreferencesModule(MODULE_NAME, __name__)
|
||||
|
||||
|
||||
@blueprint.route("/")
|
||||
@login_required
|
||||
def index():
|
||||
"""Render the preferences dialog."""
|
||||
return render_template(
|
||||
MODULE_NAME + "/index.html",
|
||||
username=current_user.email,
|
||||
_=gettext
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/preferences.js")
|
||||
@login_required
|
||||
def script():
|
||||
"""render the required javascript"""
|
||||
return Response(response=render_template("preferences/preferences.js", _=gettext),
|
||||
status=200,
|
||||
mimetype="application/javascript")
|
||||
|
||||
|
||||
@blueprint.route("/preferences", methods=["GET"])
|
||||
@login_required
|
||||
def preferences():
|
||||
"""Fetch all the preferences of pgAdmin IV."""
|
||||
|
||||
# Load Preferences
|
||||
preferences = Preferences.preferences()
|
||||
res = []
|
||||
|
||||
for m in preferences:
|
||||
if len(m['categories']):
|
||||
om = {
|
||||
"id": m['id'],
|
||||
"label": m['label'],
|
||||
"inode": True,
|
||||
"open": True,
|
||||
"branch": []
|
||||
}
|
||||
|
||||
for c in m['categories']:
|
||||
oc = {
|
||||
"id": c['id'],
|
||||
"mid": m['id'],
|
||||
"label": c['label'],
|
||||
"inode": False,
|
||||
"open": False,
|
||||
"preferences": c['preferences']
|
||||
}
|
||||
|
||||
(om['branch']).append(oc)
|
||||
|
||||
res.append(om)
|
||||
|
||||
return ajax_response(
|
||||
response=res,
|
||||
status=200
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/preferences/<int:pid>", methods=["PUT"])
|
||||
@login_required
|
||||
def save(pid):
|
||||
"""
|
||||
Save a specific preference.
|
||||
"""
|
||||
data = request.form if request.form else json.loads(request.data.decode())
|
||||
|
||||
res, msg = Preferences.save(data['mid'], data['cid'], data['id'], data['value'])
|
||||
|
||||
if not res:
|
||||
return internal_server_error(errormsg=msg)
|
||||
|
||||
return success_return()
|
40
web/pgadmin/preferences/static/css/preferences.css
Normal file
40
web/pgadmin/preferences/static/css/preferences.css
Normal file
@ -0,0 +1,40 @@
|
||||
.preferences_dialog {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.preferences_tree{
|
||||
padding: 0px;
|
||||
padding-top: 2px;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
border-right: 2px solid #999999;
|
||||
background-image: #FAFAFA;
|
||||
}
|
||||
|
||||
.preferences_content {
|
||||
padding-top: 10px;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.preferences_content .control-label, .preferences_content .pgadmin-controls {
|
||||
min-width: 100px !important;
|
||||
}
|
||||
|
||||
.pgadmin-preference-body {
|
||||
min-width: 300px !important;
|
||||
min-height: 400px !important;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.pgadmin-preference-body {
|
||||
min-width: 600px !important;
|
||||
min-height: 480px !important;
|
||||
}
|
||||
}
|
8
web/pgadmin/preferences/templates/preferences/index.html
Normal file
8
web/pgadmin/preferences/templates/preferences/index.html
Normal file
@ -0,0 +1,8 @@
|
||||
<div id="options_dialog">
|
||||
<div id="options_tree" class="">
|
||||
ACI TREE
|
||||
</div>
|
||||
<div id='options_content'>
|
||||
Right hand side content
|
||||
</div>
|
||||
</div>
|
374
web/pgadmin/preferences/templates/preferences/preferences.js
Normal file
374
web/pgadmin/preferences/templates/preferences/preferences.js
Normal file
@ -0,0 +1,374 @@
|
||||
define(
|
||||
['jquery', 'alertify', 'pgadmin', 'underscore', 'backform', 'pgadmin.backform'],
|
||||
|
||||
// This defines the Preference/Options Dialog for pgAdmin IV.
|
||||
function($, alertify, pgAdmin, _, Backform) {
|
||||
pgAdmin = pgAdmin || window.pgAdmin || {};
|
||||
|
||||
/*
|
||||
* Hmm... this module is already been initialized, we can refer to the old
|
||||
* object from here.
|
||||
*/
|
||||
if (pgAdmin.Preferences)
|
||||
return pgAdmin.Preferences;
|
||||
|
||||
pgAdmin.Preferences = {
|
||||
init: function() {
|
||||
if (this.initialized)
|
||||
return;
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
// Declare the Preferences dialog
|
||||
alertify.dialog('preferencesDlg', function() {
|
||||
|
||||
var jTree, // Variable to create the aci-tree
|
||||
controls = [], // Keep tracking of all the backform controls
|
||||
// created by the dialog.
|
||||
// Dialog containter
|
||||
$container = $("<div class='preferences_dialog'></div>");
|
||||
|
||||
|
||||
/*
|
||||
* Preference Model
|
||||
*
|
||||
* This model will be used to keep tracking of the changes done for
|
||||
* an individual option.
|
||||
*/
|
||||
var PreferenceModel = Backbone.Model.extend({
|
||||
idAttribute: 'id',
|
||||
defaults: {
|
||||
id: undefined,
|
||||
value: undefined
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Preferences Collection object.
|
||||
*
|
||||
* We will use only one collection object to keep track of all the
|
||||
* preferences.
|
||||
*/
|
||||
var preferences = this.preferences = new (Backbone.Collection.extend({
|
||||
model: PreferenceModel,
|
||||
url: "{{ url_for('preferences.preferences') }}",
|
||||
updateAll: function() {
|
||||
// We will send only the modified data to the server.
|
||||
this.each(function(m) {
|
||||
if (m.hasChanged()) {
|
||||
m.save({
|
||||
fail: function() {
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}))(null);
|
||||
|
||||
/*
|
||||
* Function: renderPreferencePanel
|
||||
*
|
||||
* Renders the preference panel in the content div based on the given
|
||||
* preferences.
|
||||
*/
|
||||
var renderPreferencePanel = function(prefs) {
|
||||
/*
|
||||
* Clear the existing html in the preferences content
|
||||
*/
|
||||
var content = $container.find('.preferences_content');
|
||||
content.empty();
|
||||
|
||||
/*
|
||||
* We should clean up the existing controls.
|
||||
*/
|
||||
if (controls) {
|
||||
_.each(controls, function(c) {
|
||||
c.remove();
|
||||
});
|
||||
}
|
||||
controls = [];
|
||||
|
||||
/*
|
||||
* We will create new set of controls and render it based on the
|
||||
* list of preferences using the Backform Field, Control.
|
||||
*/
|
||||
_.each(prefs, function(p) {
|
||||
|
||||
var m = preferences.get(p.id),
|
||||
f = new Backform.Field(_.extend({}, p, {id: 'value', name: 'value'})),
|
||||
cntr = new (f.get("control")) ({
|
||||
field: f,
|
||||
model: m
|
||||
});
|
||||
content.append(cntr.render().$el);
|
||||
|
||||
// We will keep track of all the controls rendered at the
|
||||
// moment.
|
||||
controls.push(cntr);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
* Function: dialogContentCleanup
|
||||
*
|
||||
* Do the dialog container cleanup on openning.
|
||||
*/
|
||||
|
||||
var dialogContentCleanup = function() {
|
||||
// Remove the existing preferences
|
||||
if (!jTree)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Remove the aci-tree (mainly to remove the jquery object of
|
||||
* aciTree from the system for this container).
|
||||
*/
|
||||
try {
|
||||
jTreeApi = jTree.aciTree('destroy');
|
||||
} catch(ex) {
|
||||
// Sometimes - it fails to destroy the tree properly and throws
|
||||
// exception.
|
||||
}
|
||||
jTree.off('acitree', treeEventHandler);
|
||||
|
||||
// We need to reset the data from the preferences too
|
||||
preferences.reset();
|
||||
|
||||
/*
|
||||
* Clean up the existing controls.
|
||||
*/
|
||||
if (controls) {
|
||||
_.each(controls, function(c) {
|
||||
c.remove();
|
||||
});
|
||||
}
|
||||
controls = [];
|
||||
|
||||
// Remove all the objects now.
|
||||
$container.empty();
|
||||
},
|
||||
/*
|
||||
* Function: selectFirstCategory
|
||||
*
|
||||
* Whenever a user select a module instead of a category, we should
|
||||
* select the first categroy of it.
|
||||
*/
|
||||
selectFirstCategory = function(api, item) {
|
||||
var data = item ? api.itemData(item) : null;
|
||||
|
||||
if (data && data.preferences) {
|
||||
api.select(item);
|
||||
return;
|
||||
}
|
||||
item = api.first(item);
|
||||
selectFirstCategory(api, item);
|
||||
},
|
||||
/*
|
||||
* A map on how to create controls for each datatype in preferences
|
||||
* dialog.
|
||||
*/
|
||||
getControlMappedForType = function(p) {
|
||||
switch(p.type) {
|
||||
case 'boolean':
|
||||
p.options = {
|
||||
onText: '{{ _('True') }}',
|
||||
offText: '{{ _('False') }}',
|
||||
onColor: 'success',
|
||||
offColor: 'default',
|
||||
size: 'mini'
|
||||
};
|
||||
return 'switch';
|
||||
case 'node':
|
||||
p.options = {
|
||||
onText: '{{ _('Show') }}',
|
||||
offText: '{{ _('Hide') }}',
|
||||
onColor: 'success',
|
||||
offColor: 'default',
|
||||
size: 'mini'
|
||||
};
|
||||
return 'switch';
|
||||
case 'integer':
|
||||
return 'integer';
|
||||
case 'numeric':
|
||||
return 'numeric';
|
||||
case 'date':
|
||||
// TODO::
|
||||
// Datetime picker Control is missing at the moment, replace
|
||||
// once it has been implemented.
|
||||
return 'datepicker';
|
||||
case 'datetime':
|
||||
return 'datepicker';
|
||||
case 'options':
|
||||
var opts = [];
|
||||
// Convert the array to SelectControl understandable options.
|
||||
_.each(p.options, function(o) {
|
||||
opts.push({'label': o, 'value': o});
|
||||
});
|
||||
p.options = opts;
|
||||
return 'select2';
|
||||
case 'multiline':
|
||||
return 'textarea';
|
||||
case 'switch':
|
||||
return 'switch';
|
||||
default:
|
||||
if (console && console.log) {
|
||||
// Warning for developer only.
|
||||
console.log(
|
||||
"Hmm.. We don't know how to render this type - ''" + type + "' of control."
|
||||
);
|
||||
}
|
||||
return 'input';
|
||||
}
|
||||
},
|
||||
/*
|
||||
* function: treeEventHandler
|
||||
*
|
||||
* It is basically a callback, which listens to aci-tree events,
|
||||
* and act accordingly.
|
||||
*
|
||||
* + Selection of the node will existance of the preferences for
|
||||
* the selected tree-node, if not pass on to select the first
|
||||
* category under a module, else pass on to the render function.
|
||||
*
|
||||
* + When a new node is added in the tree, it will add the relavent
|
||||
* preferences in the preferences model collection, which will be
|
||||
* called during initialization itself.
|
||||
*
|
||||
*
|
||||
*/
|
||||
treeEventHandler = function(event, api, item, eventName) {
|
||||
// Look for selected item (if none supplied)!
|
||||
item = item || api.selected();
|
||||
|
||||
// Event tree item has itemData
|
||||
var d = item ? api.itemData(item) : null;
|
||||
|
||||
/*
|
||||
* boolean (switch/checkbox), string, enum (combobox - enumvals),
|
||||
* integer (min-max), font, color
|
||||
*/
|
||||
switch (eventName) {
|
||||
case "selected":
|
||||
if (!d)
|
||||
return true;
|
||||
|
||||
if (d.preferences) {
|
||||
/*
|
||||
* Clear the existing html in the preferences content
|
||||
*/
|
||||
renderPreferencePanel(d.preferences);
|
||||
|
||||
return true;
|
||||
} else{
|
||||
selectFirstCategory(api, item);
|
||||
}
|
||||
break;
|
||||
case 'added':
|
||||
if (!d)
|
||||
return true;
|
||||
|
||||
// We will add the preferences in to the preferences data
|
||||
// collection.
|
||||
if (d.preferences && _.isArray(d.preferences)) {
|
||||
_.each(d.preferences, function(p) {
|
||||
preferences.add({
|
||||
'id': p.id, 'value': p.value, 'cid': d.id, 'mid': d.mid
|
||||
});
|
||||
/*
|
||||
* We don't know until now, how to render the control for
|
||||
* this preference.
|
||||
*/
|
||||
if (!p.control) {
|
||||
p.control = getControlMappedForType(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
d.sortable = false;
|
||||
break;
|
||||
case 'loaded':
|
||||
// Let's select the first category from the prefrences.
|
||||
// We need to wait for sometime before all item gets loaded
|
||||
// properly.
|
||||
setTimeout(
|
||||
function() {
|
||||
selectFirstCategory(api, null);
|
||||
}, 300);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// Dialog property
|
||||
return {
|
||||
main: function() {
|
||||
|
||||
// Remove the existing content first.
|
||||
dialogContentCleanup();
|
||||
|
||||
$container.append(
|
||||
"<div class='col-xs-3 preferences_tree aciTree'></div>"
|
||||
).append(
|
||||
"<div class='col-xs-9 preferences_content'>" +
|
||||
" {{ _('Category is not selected.')|safe }}" +
|
||||
"</div>"
|
||||
);
|
||||
|
||||
// Create the aci-tree for listing the modules and categories of
|
||||
// it.
|
||||
jTree = $container.find('.preferences_tree');
|
||||
jTree.on('acitree', treeEventHandler);
|
||||
|
||||
jTree.aciTree({
|
||||
selectable: true,
|
||||
expand: true,
|
||||
ajax: {
|
||||
url: "{{ url_for('preferences.preferences') }}"
|
||||
}
|
||||
});
|
||||
|
||||
this.show();
|
||||
},
|
||||
setup:function(){
|
||||
return {
|
||||
buttons:[
|
||||
{
|
||||
text: "{{ _('OK') }}", key: 13, className: "btn btn-primary"
|
||||
},
|
||||
{
|
||||
text: "{{ _('Cancel') }}", className: "btn btn-danger"
|
||||
}
|
||||
],
|
||||
focus: { element: 0 },
|
||||
options: {
|
||||
padding: !1,
|
||||
overflow: !1,
|
||||
title: '{{ _('Preferences')|safe }}'
|
||||
}
|
||||
};
|
||||
},
|
||||
callback: function(closeEvent){
|
||||
if (closeEvent.button.text == "{{ _('OK') }}"){
|
||||
preferences.updateAll();
|
||||
}
|
||||
},
|
||||
build: function() {
|
||||
this.elements.content.appendChild($container.get(0));
|
||||
},
|
||||
hooks: {
|
||||
onshow: function() {
|
||||
$(this.elements.body).addClass('pgadmin-preference-body');
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
},
|
||||
show: function() {
|
||||
alertify.preferencesDlg(true).resizeTo('60%', '60%');
|
||||
}
|
||||
};
|
||||
|
||||
return pgAdmin.Preferences;
|
||||
});
|
@ -9,16 +9,13 @@
|
||||
|
||||
"""Utility functions for storing and retrieving user configuration settings."""
|
||||
|
||||
from flask import current_app
|
||||
from flask.ext.login import current_user
|
||||
from flask.ext.sqlalchemy import SQLAlchemy
|
||||
|
||||
from .settings_model import db, Setting
|
||||
from pgadmin.model import db, Setting
|
||||
import traceback
|
||||
from flask import Blueprint, Response, abort, request, render_template
|
||||
from flask import Response, request, render_template
|
||||
from flask.ext.security import login_required
|
||||
|
||||
import config
|
||||
from pgadmin.utils.ajax import make_json_response
|
||||
from pgadmin.utils import PgAdminModule
|
||||
|
||||
@ -59,7 +56,6 @@ def store(setting=None, value=None):
|
||||
"""Store a configuration setting, or if this is a POST request and a
|
||||
count value is present, store multiple settings at once."""
|
||||
success = 1
|
||||
errorcode = 0
|
||||
errormsg = ''
|
||||
|
||||
try:
|
||||
@ -97,7 +93,6 @@ def get(setting=None, default=None):
|
||||
default = request.form['default']
|
||||
|
||||
success = 1
|
||||
errorcode = 0
|
||||
errormsg = ''
|
||||
|
||||
try:
|
||||
|
@ -425,6 +425,7 @@
|
||||
/* Array of objects having attributes [label, fields] */
|
||||
schema: undefined,
|
||||
tagName: "form",
|
||||
legend: true,
|
||||
className: function() {
|
||||
return 'col-sm-12 col-md-12 col-lg-12 col-xs-12';
|
||||
},
|
||||
@ -441,6 +442,7 @@
|
||||
o.cId = o.cId || _.uniqueId('pgC_');
|
||||
o.hId = o.hId || _.uniqueId('pgH_');
|
||||
o.disabled = o.disabled || false;
|
||||
o.legend = opts.legend;
|
||||
});
|
||||
if (opts.tabPanelClassName && _.isFunction(opts.tabPanelClassName)) {
|
||||
this.tabPanelClassName = opts.tabPanelClassName;
|
||||
@ -547,8 +549,9 @@
|
||||
template: {
|
||||
'header': _.template([
|
||||
'<fieldset class="<%=fieldsetClass%>" <%=disabled ? "disabled" : ""%>>',
|
||||
' <% if (legend != false) { %>',
|
||||
' <legend class="<%=legendClass%>" <%=collapse ? "data-toggle=\'collapse\'" : ""%> data-target="#<%=cId%>"><%=collapse ? "<span class=\'caret\'></span>" : "" %><%=label%></legend>',
|
||||
' ',
|
||||
' <% } %>',
|
||||
'</fieldset>'
|
||||
].join("\n")),
|
||||
'content': _.template(
|
||||
|
@ -10,7 +10,7 @@
|
||||
from flask import Blueprint
|
||||
from collections import defaultdict
|
||||
from operator import attrgetter
|
||||
import sys
|
||||
from .preferences import Preferences
|
||||
|
||||
|
||||
class PgAdminModule(Blueprint):
|
||||
@ -26,8 +26,25 @@ class PgAdminModule(Blueprint):
|
||||
kwargs.setdefault('template_folder', 'templates')
|
||||
kwargs.setdefault('static_folder', 'static')
|
||||
self.submodules = []
|
||||
|
||||
super(PgAdminModule, self).__init__(name, import_name, **kwargs)
|
||||
|
||||
def create_module_preference():
|
||||
# Create preference for each module by default
|
||||
if hasattr(self, 'LABEL'):
|
||||
self.preference = Preferences(self.name, self.LABEL)
|
||||
else:
|
||||
self.preference = Preferences(self.name, None)
|
||||
|
||||
self.register_preferences()
|
||||
|
||||
# Create and register the module preference object and preferences for
|
||||
# it just before the first request
|
||||
self.before_app_first_request(create_module_preference)
|
||||
|
||||
def register_preferences(self):
|
||||
pass
|
||||
|
||||
def register(self, app, options, first_registration=False):
|
||||
"""
|
||||
Override the default register function to automagically register
|
||||
@ -35,7 +52,9 @@ class PgAdminModule(Blueprint):
|
||||
"""
|
||||
if first_registration:
|
||||
self.submodules = list(app.find_submodules(self.import_name))
|
||||
|
||||
super(PgAdminModule, self).register(app, options, first_registration)
|
||||
|
||||
for module in self.submodules:
|
||||
app.register_blueprint(module)
|
||||
|
||||
|
@ -24,7 +24,7 @@ from flask.ext.babel import gettext
|
||||
from flask.ext.security import current_user
|
||||
|
||||
from ..abstract import BaseDriver, BaseConnection
|
||||
from pgadmin.settings.settings_model import Server, User
|
||||
from pgadmin.model import Server, User
|
||||
from pgadmin.utils.crypto import decrypt
|
||||
import random
|
||||
import select
|
||||
@ -1003,7 +1003,7 @@ class Driver(BaseDriver):
|
||||
|
||||
managers['pinged'] = datetime.datetime.now()
|
||||
if str(sid) not in managers:
|
||||
from pgadmin.settings.settings_model import Server
|
||||
from pgadmin.model import Server
|
||||
s = Server.query.filter_by(id=sid).first()
|
||||
|
||||
managers[str(sid)] = ServerManager(s)
|
||||
|
528
web/pgadmin/utils/preferences.py
Normal file
528
web/pgadmin/utils/preferences.py
Normal file
@ -0,0 +1,528 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
"""
|
||||
Utility classes to register, getter, setter functions for the preferences of a
|
||||
module within the system.
|
||||
"""
|
||||
|
||||
from flask import current_app
|
||||
from flask.ext.security import current_user
|
||||
from pgadmin.model import db, Preferences as PrefTable, \
|
||||
ModulePreference as ModulePrefTable, UserPreference as UserPrefTable, \
|
||||
PreferenceCategory as PrefCategoryTbl
|
||||
from flask.ext.babel import gettext
|
||||
import dateutil.parser as dateutil_parser
|
||||
import decimal
|
||||
|
||||
|
||||
class _Preference(object):
|
||||
"""
|
||||
Internal class representing module, and categoy bound preference.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, cid, name, label, _type, default, help_str=None, min_val=None,
|
||||
max_val=None, options=None
|
||||
):
|
||||
"""
|
||||
__init__
|
||||
Constructor/Initializer for the internal _Preference object.
|
||||
|
||||
It creates a new entry for this preference in configuration table based
|
||||
on the name (if not exists), and keep the id of it for on demand value
|
||||
fetching from the configuration table in later stage. Also, keeps track
|
||||
of type of the preference/option, and other supporting parameters like
|
||||
min, max, options, etc.
|
||||
|
||||
:param cid: configuration id
|
||||
:param name: Name of the preference (must be unique for each
|
||||
configuration)
|
||||
:param label: Display name of the options/preference
|
||||
:param _type: Type for proper validation on value
|
||||
:param default: Default value
|
||||
:param help_str: Help string to be shown in preferences dialog.
|
||||
:param min_val: minimum value
|
||||
:param max_val: maximum value
|
||||
:param options: options (Array of list objects)
|
||||
|
||||
:returns: nothing
|
||||
"""
|
||||
self.cid = cid
|
||||
self.name = name
|
||||
self.default = default
|
||||
self.label = label
|
||||
self._type = _type
|
||||
self.help_str = help_str
|
||||
self.min_val = min_val
|
||||
self.max_val = max_val
|
||||
self.options = options
|
||||
|
||||
# Look into the configuration table to find out the id of the specific
|
||||
# preference.
|
||||
res = PrefTable.query.filter_by(
|
||||
name=name
|
||||
).first()
|
||||
|
||||
if res is None:
|
||||
# Couldn't find in the configuration table, we will create new
|
||||
# entry for it.
|
||||
res = PrefTable(name=self.name, cid=cid)
|
||||
db.session.add(res)
|
||||
db.session.commit()
|
||||
res = PrefTable.query.filter_by(
|
||||
name=name
|
||||
).first()
|
||||
|
||||
# Save this id for letter use.
|
||||
self.pid = res.id
|
||||
|
||||
def get(self):
|
||||
"""
|
||||
get
|
||||
Fetch the value from the server for the current user from the
|
||||
configuration table (if available), otherwise returns the default value
|
||||
for it.
|
||||
|
||||
:returns: value for this preference.
|
||||
"""
|
||||
res = UserPrefTable.query.filter_by(
|
||||
pid=self.pid
|
||||
).filter_by(uid=current_user.id).first()
|
||||
|
||||
# Couldn't find any preference for this user, return default value.
|
||||
if res is None:
|
||||
return self.default
|
||||
|
||||
# The data stored in the configuration will be in string format, we
|
||||
# need to convert them in proper format.
|
||||
if self._type == 'boolean' or self._type == 'switch' or \
|
||||
self._type == 'node':
|
||||
return res.value == 'True'
|
||||
if self._type == 'integer':
|
||||
try:
|
||||
return int(res.value)
|
||||
except Exception as e:
|
||||
current_app.logger.exeception(e)
|
||||
return self.default
|
||||
if self._type == 'numeric':
|
||||
try:
|
||||
return decimal.Decimal(res.value)
|
||||
except Exception as e:
|
||||
current_app.logger.exeception(e)
|
||||
return self.default
|
||||
if self._type == 'date' or self._type == 'datetime':
|
||||
try:
|
||||
return dateutil_parser.parse(res.value)
|
||||
except Exception as e:
|
||||
current_app.logger.exeception(e)
|
||||
return self.default
|
||||
if self._type == 'options':
|
||||
if res.value in self.options:
|
||||
return res.value
|
||||
return self.default
|
||||
|
||||
return res.value
|
||||
|
||||
def set(self, value):
|
||||
"""
|
||||
set
|
||||
Set the value into the configuration table for this current user.
|
||||
|
||||
:param value: Value to be set
|
||||
|
||||
:returns: nothing.
|
||||
"""
|
||||
# We can't store the values in the given format, we need to convert
|
||||
# them in string first. We also need to validate the value type.
|
||||
if self._type == 'boolean' or self._type == 'switch' or \
|
||||
self._type == 'node':
|
||||
if type(value) != bool:
|
||||
return False, gettext("Invalid value for boolean type!")
|
||||
elif self._type == 'integer':
|
||||
if type(value) != int:
|
||||
return False, gettext("Invalid value for integer type!")
|
||||
elif self._type == 'numeric':
|
||||
t = type(value)
|
||||
if t != float and t != int and t != decimal.Decimal:
|
||||
return False, gettext("Invalid value for numeric type!")
|
||||
elif self._type == 'date':
|
||||
try:
|
||||
value = dateutil_parser.parse(value).date()
|
||||
except Exception as e:
|
||||
current_app.logger.exeception(e)
|
||||
return False, gettext("Invalid value for date type!")
|
||||
elif self._type == 'datetime':
|
||||
try:
|
||||
value = dateutil_parser.parse(value)
|
||||
except Exception as e:
|
||||
current_app.logger.exeception(e)
|
||||
return False, gettext("Invalid value for datetime type!")
|
||||
elif self._type == 'options':
|
||||
if value not in self.options:
|
||||
return False, gettext("Invalid value for options type!")
|
||||
|
||||
pref = UserPrefTable.query.filter_by(
|
||||
pid=self.pid
|
||||
).filter_by(uid=current_user.id).first()
|
||||
|
||||
if pref is None:
|
||||
pref = UserPrefTable(
|
||||
uid=current_user.id, pid=self.pid, value=str(value)
|
||||
)
|
||||
db.session.add(pref)
|
||||
else:
|
||||
pref.value = str(value)
|
||||
db.session.commit()
|
||||
|
||||
return True, None
|
||||
|
||||
def to_json(self):
|
||||
"""
|
||||
to_json
|
||||
Returns the JSON object representing this preferences.
|
||||
|
||||
:returns: the JSON representation for this preferences
|
||||
"""
|
||||
res = {
|
||||
'id': self.pid,
|
||||
'cid': self.cid,
|
||||
'name': self.name,
|
||||
'label': self.label or self.name,
|
||||
'type': self._type,
|
||||
'help_str': self.help_str,
|
||||
'min_val': self.min_val,
|
||||
'max_val': self.max_val,
|
||||
'options': self.options,
|
||||
'value': self.get()
|
||||
}
|
||||
return res
|
||||
|
||||
|
||||
class Preferences(object):
|
||||
"""
|
||||
class Preferences
|
||||
|
||||
It helps to manage all the preferences/options related to a specific
|
||||
module.
|
||||
|
||||
It keeps track of all the preferences registered with it using this class
|
||||
in the group of categories.
|
||||
|
||||
Also, create the required entries for each module, and categories in the
|
||||
preferences tables (if required). If it is already present, it will refer
|
||||
to the existing data from those tables.
|
||||
|
||||
class variables:
|
||||
---------------
|
||||
modules:
|
||||
Dictionary of all the modules, can be refered by its name.
|
||||
Keeps track of all the modules in it, so that - only one object per module
|
||||
gets created. If the same module refered by different object, the
|
||||
categories dictionary within it will be shared between them to keep the
|
||||
consistent data among all the object.
|
||||
|
||||
Instance Definitions:
|
||||
-------- -----------
|
||||
"""
|
||||
modules = dict()
|
||||
|
||||
def __init__(self, name, label=None):
|
||||
"""
|
||||
__init__
|
||||
Constructor/Initializer for the Preferences class.
|
||||
|
||||
:param name: Name of the module
|
||||
:param label: Display name of the module, it will be displayed in the
|
||||
preferences dialog.
|
||||
|
||||
:returns nothing
|
||||
"""
|
||||
self.name = name
|
||||
self.label = label
|
||||
self.categories = dict()
|
||||
|
||||
# Find the entry for this module in the configuration database.
|
||||
module = ModulePrefTable.query.filter_by(name=name).first()
|
||||
|
||||
# Can't find the reference for it in the configuration database,
|
||||
# create on for it.
|
||||
if module is None:
|
||||
module = ModulePrefTable(name=name)
|
||||
db.session.add(module)
|
||||
db.session.commit()
|
||||
module = ModulePrefTable.query.filter_by(name=name).first()
|
||||
|
||||
self.mid = module.id
|
||||
|
||||
if name in Preferences.modules:
|
||||
m = Preferences.modules
|
||||
self.categories = m.categories
|
||||
else:
|
||||
Preferences.modules[name] = self
|
||||
|
||||
def to_json(self):
|
||||
"""
|
||||
to_json
|
||||
Converts the preference object to the JSON Format.
|
||||
|
||||
:returns: a JSON object contains information.
|
||||
"""
|
||||
res = {
|
||||
'id': self.mid,
|
||||
'label': self.label or self.name,
|
||||
'categories': []
|
||||
}
|
||||
for c in self.categories:
|
||||
cat = self.categories[c]
|
||||
interm = {
|
||||
'id': cat['id'],
|
||||
'label': cat['label'] or cat['name'],
|
||||
'preferences': []
|
||||
}
|
||||
|
||||
res['categories'].append(interm)
|
||||
|
||||
for p in cat['preferences']:
|
||||
pref = (cat['preferences'][p]).to_json().copy()
|
||||
pref.update({'mid': self.mid, 'cid': cat['id']})
|
||||
interm['preferences'].append(pref)
|
||||
|
||||
return res
|
||||
|
||||
def __category(self, name, label):
|
||||
"""
|
||||
__category
|
||||
|
||||
A private method to create/refer category for/of this module.
|
||||
|
||||
:param name: Name of the category
|
||||
:param label: Display name of the category, it will be send to
|
||||
client/front end to list down in the preferences/options
|
||||
dialog.
|
||||
:returns: A dictionary object reprenting this category.
|
||||
"""
|
||||
if name in self.categories:
|
||||
res = self.categories[name]
|
||||
# Update the category label (if not yet defined)
|
||||
res['label'] = res['label'] or label
|
||||
|
||||
return res
|
||||
|
||||
cat = PrefCategoryTbl.query.filter_by(
|
||||
mid=self.mid
|
||||
).filter_by(name=name).first()
|
||||
|
||||
if cat is None:
|
||||
cat = PrefCategoryTbl(name=name, mid=self.mid)
|
||||
db.session.add(cat)
|
||||
db.session.commit()
|
||||
cat = PrefCategoryTbl.query.filter_by(
|
||||
mid=self.mid
|
||||
).filter_by(name=name).first()
|
||||
|
||||
self.categories[name] = res = {
|
||||
'id': cat.id,
|
||||
'name': name,
|
||||
'label': label,
|
||||
'preferences': dict()
|
||||
}
|
||||
|
||||
return res
|
||||
|
||||
def register(
|
||||
self, category, name, label, _type, default, min_val=None,
|
||||
max_val=None, options=None, help_str=None, category_label=None
|
||||
):
|
||||
"""
|
||||
register
|
||||
Register/Refer the particular preference in this module.
|
||||
|
||||
:param category: name of the category, in which this preference/option
|
||||
will be displayed.
|
||||
:param name: name of the preference/option
|
||||
:param label: Display name of the preference
|
||||
:param _type: [optional] Type of the options.
|
||||
It is an optional argument, only if this
|
||||
option/preference is registered earlier.
|
||||
:param default: [optional] Default value of the options
|
||||
It is an optional argument, only if this
|
||||
option/preference is registered earlier.
|
||||
:param min_val:
|
||||
:param max_val:
|
||||
:param options:
|
||||
:param help_str:
|
||||
:param category_label:
|
||||
"""
|
||||
cat = self.__category(category, category_label)
|
||||
if name in cat['preferences']:
|
||||
return (cat['preferences'])[name]
|
||||
|
||||
assert label is not None, "Label for a preference can not be none!"
|
||||
assert _type is not None, "Type for a preference can not be none!"
|
||||
assert _type in (
|
||||
'boolean', 'integer', 'numeric', 'date', 'datetime',
|
||||
'options', 'multiline', 'switch', 'node'
|
||||
), "Type can not be found in the defined list!"
|
||||
|
||||
(cat['preferences'])[name] = res = _Preference(
|
||||
cat['id'], name, label, _type, default, help_str, min_val,
|
||||
max_val, options
|
||||
)
|
||||
|
||||
return res
|
||||
|
||||
def preference(self, name):
|
||||
"""
|
||||
preference
|
||||
Refer the particular preference in this module.
|
||||
|
||||
:param name: name of the preference/option
|
||||
"""
|
||||
for key in self.categories:
|
||||
cat = self.categories[key]
|
||||
if name in cat['preferences']:
|
||||
return (cat['preferences'])[name]
|
||||
|
||||
assert False, """Couldn't find the preference in this preference!
|
||||
Did you forget to register it?"""
|
||||
|
||||
@classmethod
|
||||
def preferences(cls):
|
||||
"""
|
||||
preferences
|
||||
Convert all the module preferences in the JSON format.
|
||||
|
||||
:returns: a list of the preferences for each of the modules.
|
||||
"""
|
||||
res = []
|
||||
|
||||
for m in Preferences.modules:
|
||||
res.append(Preferences.modules[m].to_json())
|
||||
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def register_preference(
|
||||
cls, module, category, name, label, _type, default, min_val=None,
|
||||
max_val=None, options=None, help_str=None, module_label=None,
|
||||
category_label=None
|
||||
):
|
||||
"""
|
||||
register
|
||||
Register/Refer a preference in the system for any module.
|
||||
|
||||
:param module: Name of the module
|
||||
:param category: Name of category
|
||||
:param name: Name of the option
|
||||
:param label: Label of the option, shown in the preferences dialog.
|
||||
:param _type: Type of the option.
|
||||
Allowed type of options are as below:
|
||||
boolean, integer, numeric, date, datetime,
|
||||
options, multiline, switch, node
|
||||
:param default: Default value for the preference/option
|
||||
:param min_val: Minimum value for integer, and numeric type
|
||||
:param max_val: Maximum value for integer, and numeric type
|
||||
:param options: Allowed list of options for 'option' type
|
||||
:param help_str: Help string show for that preference/option.
|
||||
:param module_label: Label for the module
|
||||
:param category_label: Label for the category
|
||||
"""
|
||||
m = None
|
||||
if module in Preferences.modules:
|
||||
m = Preferences.modules[module]
|
||||
# Update the label (if not defined yet)
|
||||
m.label = m.label or module_label
|
||||
else:
|
||||
m = Preferences(module, module_label)
|
||||
|
||||
return m.register(
|
||||
category, name, label, _type, default, min_val, max_val,
|
||||
options, help_str, category_label
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def module(cls, name):
|
||||
"""
|
||||
module (classmethod)
|
||||
Get the module preferences object
|
||||
|
||||
:param name: Name of the module
|
||||
:returns: a Preferences object representing for the module.
|
||||
"""
|
||||
if name in Preferences.modules:
|
||||
m = Preferences.modules[name]
|
||||
# Update the label (if not defined yet)
|
||||
if m.label is None:
|
||||
m.label = name
|
||||
return m
|
||||
else:
|
||||
m = Preferences(name, None)
|
||||
|
||||
return m
|
||||
|
||||
@classmethod
|
||||
def save(cls, mid, cid, pid, value):
|
||||
"""
|
||||
save
|
||||
Update the value for the preference in the configuration database.
|
||||
|
||||
:param mid: Module ID
|
||||
:param cid: Category ID
|
||||
:param pid: Preference ID
|
||||
:param value: Value for the options
|
||||
"""
|
||||
# Find the entry for this module in the configuration database.
|
||||
module = ModulePrefTable.query.filter_by(id=mid).first()
|
||||
|
||||
# Can't find the reference for it in the configuration database,
|
||||
# create on for it.
|
||||
if module is None:
|
||||
return False, gettext("Couldn't find the specified module.")
|
||||
|
||||
m = cls.modules[module.name]
|
||||
|
||||
if m is None:
|
||||
return False, gettext(
|
||||
"Module '{0}' is no longer in use!"
|
||||
).format(module.name)
|
||||
|
||||
category = None
|
||||
|
||||
for c in m.categories:
|
||||
cat = m.categories[c]
|
||||
if cid == cat['id']:
|
||||
category = cat
|
||||
break
|
||||
|
||||
if category is None:
|
||||
return False, gettext(
|
||||
"Module '{0}' does not have category with id '{1}'"
|
||||
).format(module.name, cid)
|
||||
|
||||
preference = None
|
||||
|
||||
for p in category['preferences']:
|
||||
pref = (category['preferences'])[p]
|
||||
|
||||
if pref.pid == pid:
|
||||
preference = pref
|
||||
break
|
||||
|
||||
if preference is None:
|
||||
return False, gettext(
|
||||
"Couldn't find the given preference!"
|
||||
)
|
||||
|
||||
try:
|
||||
pref.set(value)
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
return True, None
|
60
web/setup.py
60
web/setup.py
@ -17,10 +17,9 @@ import random
|
||||
import string
|
||||
|
||||
from flask import Flask
|
||||
from flask.ext.sqlalchemy import SQLAlchemy
|
||||
from flask.ext.security import Security, SQLAlchemyUserDatastore
|
||||
from flask.ext.security.utils import encrypt_password
|
||||
from pgadmin.settings.settings_model import db, Role, User, Server, \
|
||||
from pgadmin.model import db, Role, User, Server, \
|
||||
ServerGroup, Version
|
||||
|
||||
# Configuration settings
|
||||
@ -146,7 +145,7 @@ Exiting...""".format(version.value))
|
||||
)
|
||||
if int(version.value) < 5:
|
||||
db.engine.execute('ALTER TABLE server ADD COLUMN role text(64)')
|
||||
if int(version.value) == 6:
|
||||
if int(version.value) < 6:
|
||||
db.engine.execute("ALTER TABLE server RENAME TO server_old")
|
||||
db.engine.execute("""
|
||||
CREATE TABLE server (
|
||||
@ -177,9 +176,46 @@ INSERT INTO server (
|
||||
FROM server_old""")
|
||||
db.engine.execute("DROP TABLE server_old")
|
||||
|
||||
# Finally, update the schema version
|
||||
version.value = config.SETTINGS_SCHEMA_VERSION
|
||||
db.session.merge(version)
|
||||
if int(version.value) < 8:
|
||||
app.logger.info(
|
||||
"Creating the preferences tables..."
|
||||
)
|
||||
db.engine.execute("""
|
||||
CREATE TABLE module_preference(
|
||||
id INTEGER PRIMARY KEY,
|
||||
name VARCHAR(256) NOT NULL
|
||||
)""")
|
||||
|
||||
db.engine.execute("""
|
||||
CREATE TABLE preference_category(
|
||||
id INTEGER PRIMARY KEY,
|
||||
mid INTEGER,
|
||||
name VARCHAR(256) NOT NULL,
|
||||
|
||||
FOREIGN KEY(mid) REFERENCES module_preference(id)
|
||||
)""")
|
||||
|
||||
db.engine.execute("""
|
||||
CREATE TABLE preferences (
|
||||
|
||||
id INTEGER PRIMARY KEY,
|
||||
cid INTEGER NOT NULL,
|
||||
name VARCHAR(256) NOT NULL,
|
||||
|
||||
FOREIGN KEY(cid) REFERENCES preference_category (id)
|
||||
)""")
|
||||
|
||||
db.engine.execute("""
|
||||
CREATE TABLE user_preferences (
|
||||
|
||||
pid INTEGER,
|
||||
uid INTEGER,
|
||||
value VARCHAR(1024) NOT NULL,
|
||||
|
||||
PRIMARY KEY (pid, uid),
|
||||
FOREIGN KEY(pid) REFERENCES preferences (pid),
|
||||
FOREIGN KEY(uid) REFERENCES user (id)
|
||||
)""")
|
||||
|
||||
# Finally, update the schema version
|
||||
version.value = config.SETTINGS_SCHEMA_VERSION
|
||||
@ -190,7 +226,7 @@ FROM server_old""")
|
||||
# Done!
|
||||
app.logger.info(
|
||||
"The configuration database %s has been upgraded to version %d" %
|
||||
(config.SQLITE_PATH, config.SETTINGS_SCHEMA_VERSION)
|
||||
(config.SQLITE_PATH, config.SETTINGS_SCHEMA_VERSION)
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
@ -222,8 +258,8 @@ if __name__ == '__main__':
|
||||
# Check if the database exists. If it does, tell the user and exit.
|
||||
if os.path.isfile(config.SQLITE_PATH):
|
||||
print("""
|
||||
The configuration database %s already exists.
|
||||
Entering upgrade mode...""".format(config.SQLITE_PATH))
|
||||
The configuration database '%s' already exists.
|
||||
Entering upgrade mode...""" % config.SQLITE_PATH)
|
||||
|
||||
# Setup Flask-Security
|
||||
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
|
||||
@ -238,12 +274,12 @@ Entering upgrade mode...""".format(config.SQLITE_PATH))
|
||||
print("""
|
||||
The database schema version is %d, whilst the version required by the \
|
||||
software is %d.
|
||||
Exiting...""".format(version.value, config.SETTINGS_SCHEMA_VERSION))
|
||||
Exiting...""" % (version.value, config.SETTINGS_SCHEMA_VERSION))
|
||||
sys.exit(1)
|
||||
elif int(version.value) == int(config.SETTINGS_SCHEMA_VERSION):
|
||||
print("""
|
||||
The database schema version is %d as required.
|
||||
Exiting...""".format(version.value))
|
||||
Exiting...""" % (version.value))
|
||||
sys.exit(1)
|
||||
|
||||
print("NOTE: Upgrading database schema from version %d to %d." % (
|
||||
@ -252,6 +288,6 @@ Exiting...""".format(version.value))
|
||||
do_upgrade(app, user_datastore, security, version)
|
||||
else:
|
||||
print("""
|
||||
The configuration database - {0} does not exist.
|
||||
The configuration database - '{0}' does not exist.
|
||||
Entering initial setup mode...""".format(config.SQLITE_PATH))
|
||||
do_setup(app)
|
||||
|
Loading…
Reference in New Issue
Block a user