mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Added support for schema level restriction. Fixes #5583
Allow user to edit the connection properties when the database server is already connected.
This commit is contained in:
parent
4c05287677
commit
c873218c32
@ -97,6 +97,19 @@ Follow these steps to add additional parameter value definitions; to discard a
|
||||
parameter, click the trash icon to the left of the row and confirm deletion in
|
||||
the *Delete Row* popup.
|
||||
|
||||
Click the *Advanced* tab to continue.
|
||||
|
||||
.. image:: images/database_advanced.png
|
||||
:alt: Database dialog advanced tab
|
||||
:align: center
|
||||
|
||||
Use the *Advanced* tab to set advanced parameters for the database.
|
||||
|
||||
* Use *Schema restriction* field to provide a SQL restriction that will be used
|
||||
against the pg_namespace table to limit the schemas that you see.
|
||||
For example, you might enter: *public* so that only *public* are shown in
|
||||
the pgAdmin browser.Separate entries with a comma or tab as you type.
|
||||
|
||||
Click the *SQL* tab to continue.
|
||||
|
||||
Your entries in the *Database* dialog generate a SQL command (see an example
|
||||
|
BIN
docs/en_US/images/database_advanced.png
Normal file
BIN
docs/en_US/images/database_advanced.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 50 KiB |
@ -9,6 +9,7 @@ This release contains a number of bug fixes and new features since the release o
|
||||
New features
|
||||
************
|
||||
|
||||
| `Issue #5583 <https://redmine.postgresql.org/issues/5583>`_ - Added support for schema level restriction.
|
||||
|
||||
Housekeeping
|
||||
************
|
||||
|
32
web/migrations/versions/84700139beb0_.py
Normal file
32
web/migrations/versions/84700139beb0_.py
Normal file
@ -0,0 +1,32 @@
|
||||
|
||||
"""empty message
|
||||
|
||||
Revision ID: 84700139beb0
|
||||
Revises: d39482714a2e
|
||||
Create Date: 2020-06-24 15:53:56.489518
|
||||
|
||||
"""
|
||||
from pgadmin.model import db
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '84700139beb0'
|
||||
down_revision = 'd39482714a2e'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
db.engine.execute("""
|
||||
CREATE TABLE "database" (
|
||||
"id" INTEGER NOT NULL,
|
||||
"schema_res" TEXT,
|
||||
"server" INTEGER NOT NULL,
|
||||
PRIMARY KEY("id","server"),
|
||||
FOREIGN KEY("server") REFERENCES "server"("id")
|
||||
);
|
||||
""")
|
||||
|
||||
|
||||
def downgrade():
|
||||
pass
|
@ -536,7 +536,7 @@ class ServerNode(PGChildNodeView):
|
||||
|
||||
if connected:
|
||||
for arg in (
|
||||
'host', 'hostaddr', 'port', 'db', 'username', 'sslmode',
|
||||
'hostaddr', 'db', 'sslmode',
|
||||
'role', 'service'
|
||||
):
|
||||
if arg in data:
|
||||
@ -1016,6 +1016,7 @@ class ServerNode(PGChildNodeView):
|
||||
|
||||
# Connect the Server
|
||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||
manager.update(server)
|
||||
conn = manager.connection()
|
||||
|
||||
# Get enc key
|
||||
|
@ -32,7 +32,7 @@ from pgadmin.utils.driver import get_driver
|
||||
from pgadmin.tools.sqleditor.utils.query_history import QueryHistory
|
||||
|
||||
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
|
||||
from pgadmin.model import Server
|
||||
from pgadmin.model import db, Server, Database
|
||||
|
||||
|
||||
class DatabaseModule(CollectionNodeModule):
|
||||
@ -423,6 +423,11 @@ class DatabaseView(PGChildNodeView):
|
||||
)
|
||||
|
||||
status, res1 = self.conn.execute_dict(SQL)
|
||||
database = Database.query.filter_by(id=did, server=sid).first()
|
||||
|
||||
if database:
|
||||
result['schema_res'] = database.schema_res.split(
|
||||
',') if database.schema_res else []
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res1)
|
||||
@ -611,6 +616,11 @@ class DatabaseView(PGChildNodeView):
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
response = res['rows'][0]
|
||||
# Add database entry into database table with schema_restrictions.
|
||||
database = Database(id=response['did'], server=sid,
|
||||
schema_res=','.join(data['schema_res']))
|
||||
db.session.add(database)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify(
|
||||
node=self.blueprint.generate_browser_node(
|
||||
@ -627,53 +637,27 @@ class DatabaseView(PGChildNodeView):
|
||||
)
|
||||
)
|
||||
|
||||
@check_precondition(action='update')
|
||||
def update(self, gid, sid, did):
|
||||
"""Update the database."""
|
||||
@staticmethod
|
||||
def _update_db_schema_res(data, did, sid):
|
||||
database = Database.query.filter_by(id=did, server=sid).first()
|
||||
if 'schema_res' in data:
|
||||
if database:
|
||||
data['schema_res'] = ','.join(data['schema_res'])
|
||||
setattr(database, 'schema_res', data['schema_res'])
|
||||
else:
|
||||
database_obj = Database(id=did, server=sid,
|
||||
schema_res=','.join(
|
||||
data['schema_res']))
|
||||
db.session.add(database_obj)
|
||||
|
||||
data = request.form if request.form else json.loads(
|
||||
request.data, encoding='utf-8'
|
||||
)
|
||||
|
||||
# Generic connection for offline updates
|
||||
conn = self.manager.connection(conn_id='db_offline_update')
|
||||
status, errmsg = conn.connect()
|
||||
if not status:
|
||||
current_app.logger.error(
|
||||
"Could not create database connection for offline updates\n"
|
||||
"Err: {0}".format(errmsg)
|
||||
)
|
||||
return internal_server_error(errmsg)
|
||||
|
||||
if did is not None:
|
||||
# Fetch the name of database for comparison
|
||||
status, rset = self.conn.execute_dict(
|
||||
render_template(
|
||||
"/".join([self.template_path, 'nodes.sql']),
|
||||
did=did, conn=self.conn, last_system_oid=0
|
||||
)
|
||||
)
|
||||
if not status:
|
||||
return internal_server_error(errormsg=rset)
|
||||
|
||||
if len(rset['rows']) == 0:
|
||||
return gone(
|
||||
_('Could not find the database on the server.')
|
||||
)
|
||||
|
||||
data['old_name'] = (rset['rows'][0])['name']
|
||||
if 'name' not in data:
|
||||
data['name'] = data['old_name']
|
||||
|
||||
# Release any existing connection from connection manager
|
||||
# to perform offline operation
|
||||
self.manager.release(did=did)
|
||||
def _check_rename_db_or_change_table_space(self, data, conn, all_ids):
|
||||
|
||||
for action in ["rename_database", "tablespace"]:
|
||||
SQL = self.get_offline_sql(gid, sid, data, did, action)
|
||||
SQL = SQL.strip('\n').strip(' ')
|
||||
if SQL and SQL != "":
|
||||
status, msg = conn.execute_scalar(SQL)
|
||||
sql = self.get_offline_sql(all_ids['gid'], all_ids['sid'], data,
|
||||
all_ids['did'], action)
|
||||
sql = sql.strip('\n').strip(' ')
|
||||
if sql and sql != "":
|
||||
status, msg = conn.execute_scalar(sql)
|
||||
if not status:
|
||||
# In case of error from server while rename it,
|
||||
# reconnect to the database with old name again.
|
||||
@ -684,13 +668,38 @@ class DatabaseView(PGChildNodeView):
|
||||
if not status:
|
||||
current_app.logger.error(
|
||||
'Could not reconnected to database(#{0}).\n'
|
||||
'Error: {1}'.format(did, errmsg)
|
||||
'Error: {1}'.format(all_ids['did'], errmsg)
|
||||
)
|
||||
return internal_server_error(errormsg=msg)
|
||||
return True, msg
|
||||
|
||||
QueryHistory.update_history_dbname(
|
||||
current_user.id, sid, data['old_name'], data['name'])
|
||||
# Make connection for database again
|
||||
current_user.id, all_ids['sid'], data['old_name'],
|
||||
data['name'])
|
||||
return False, ''
|
||||
|
||||
def _fetch_db_details(self, data, did):
|
||||
if did is not None:
|
||||
# Fetch the name of database for comparison
|
||||
status, rset = self.conn.execute_dict(
|
||||
render_template(
|
||||
"/".join([self.template_path, 'nodes.sql']),
|
||||
did=did, conn=self.conn, last_system_oid=0
|
||||
)
|
||||
)
|
||||
if not status:
|
||||
return True, rset
|
||||
|
||||
if len(rset['rows']) == 0:
|
||||
return gone(
|
||||
_('Could not find the database on the server.')
|
||||
)
|
||||
|
||||
data['old_name'] = (rset['rows'][0])['name']
|
||||
if 'name' not in data:
|
||||
data['name'] = data['old_name']
|
||||
return False, ''
|
||||
|
||||
def _reconnect_connect_db(self, data, did):
|
||||
if self._db['datallowconn']:
|
||||
self.conn = self.manager.connection(
|
||||
database=data['name'], auto_reconnect=True
|
||||
@ -702,12 +711,70 @@ class DatabaseView(PGChildNodeView):
|
||||
'Could not connected to database(#{0}).\n'
|
||||
'Error: {1}'.format(did, errmsg)
|
||||
)
|
||||
return internal_server_error(errmsg)
|
||||
return True, errmsg
|
||||
return False, ''
|
||||
|
||||
SQL = self.get_online_sql(gid, sid, data, did)
|
||||
SQL = SQL.strip('\n').strip(' ')
|
||||
if SQL and SQL != "":
|
||||
status, msg = self.conn.execute_scalar(SQL)
|
||||
def _commit_db_changes(self, res, can_drop):
|
||||
if self.manager.db == res['name']:
|
||||
can_drop = False
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
return True, e.message, False
|
||||
return False, '', can_drop
|
||||
|
||||
def _get_data_from_request(self):
|
||||
return request.form if request.form else json.loads(
|
||||
request.data, encoding='utf-8'
|
||||
)
|
||||
|
||||
@check_precondition(action='update')
|
||||
def update(self, gid, sid, did):
|
||||
"""Update the database."""
|
||||
|
||||
data = self._get_data_from_request()
|
||||
# Update schema restriction in db object.
|
||||
DatabaseView._update_db_schema_res(data, did, sid)
|
||||
|
||||
# Generic connection for offline updates
|
||||
conn = self.manager.connection(conn_id='db_offline_update')
|
||||
status, errmsg = conn.connect()
|
||||
if not status:
|
||||
current_app.logger.error(
|
||||
"Could not create database connection for offline updates\n"
|
||||
"Err: {0}".format(errmsg)
|
||||
)
|
||||
return internal_server_error(errmsg)
|
||||
|
||||
fetching_error, err_msg = self._fetch_db_details(data, did)
|
||||
if fetching_error:
|
||||
return internal_server_error(errormsg=err_msg)
|
||||
|
||||
# Release any existing connection from connection manager
|
||||
# to perform offline operation
|
||||
self.manager.release(did=did)
|
||||
all_ids = {
|
||||
'gid': gid,
|
||||
'sid': sid,
|
||||
'did': did
|
||||
}
|
||||
is_error, errmsg = self._check_rename_db_or_change_table_space(data,
|
||||
conn,
|
||||
all_ids)
|
||||
if is_error:
|
||||
return internal_server_error(errmsg)
|
||||
|
||||
# Make connection for database again
|
||||
connection_error, errmsg = self._reconnect_connect_db(data, did)
|
||||
if connection_error:
|
||||
return internal_server_error(errmsg)
|
||||
|
||||
sql = self.get_online_sql(gid, sid, data, did)
|
||||
sql = sql.strip('\n').strip(' ')
|
||||
if sql and sql != "":
|
||||
status, msg = self.conn.execute_scalar(sql)
|
||||
if not status:
|
||||
return internal_server_error(errormsg=msg)
|
||||
|
||||
@ -733,9 +800,15 @@ class DatabaseView(PGChildNodeView):
|
||||
|
||||
res = rset['rows'][0]
|
||||
|
||||
can_drop = can_dis_conn = True
|
||||
if self.manager.db == res['name']:
|
||||
can_drop = can_dis_conn = False
|
||||
can_drop = True
|
||||
error, errmsg, is_can_drop = self._commit_db_changes(res, can_drop)
|
||||
if error:
|
||||
return make_json_response(
|
||||
success=0,
|
||||
errormsg=errmsg
|
||||
)
|
||||
|
||||
can_drop = can_dis_conn = is_can_drop
|
||||
|
||||
return jsonify(
|
||||
node=self.blueprint.generate_browser_node(
|
||||
|
@ -24,6 +24,7 @@ from pgadmin.utils.ajax import make_json_response, internal_server_error, \
|
||||
make_response as ajax_response, gone, bad_request
|
||||
from pgadmin.utils.driver import get_driver
|
||||
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
|
||||
from pgadmin.model import Database
|
||||
|
||||
"""
|
||||
This module is responsible for generating two nodes
|
||||
@ -384,10 +385,21 @@ class SchemaView(PGChildNodeView):
|
||||
Returns:
|
||||
JSON of available schema nodes
|
||||
"""
|
||||
database = Database.query.filter_by(id=did, server=sid).first()
|
||||
param = None
|
||||
if database:
|
||||
schema_restrictions = database.schema_res
|
||||
|
||||
if schema_restrictions:
|
||||
schema_res = ",".join(
|
||||
["'%s'"] * len(schema_restrictions.split(',')))
|
||||
param = schema_res % (tuple(schema_restrictions.split(',')))
|
||||
|
||||
SQL = render_template(
|
||||
"/".join([self.template_path, 'sql/properties.sql']),
|
||||
_=gettext,
|
||||
show_sysobj=self.blueprint.show_system_objects
|
||||
show_sysobj=self.blueprint.show_system_objects,
|
||||
schema_restrictions=param
|
||||
)
|
||||
status, res = self.conn.execute_dict(SQL)
|
||||
|
||||
@ -413,11 +425,22 @@ class SchemaView(PGChildNodeView):
|
||||
JSON of available schema child nodes
|
||||
"""
|
||||
res = []
|
||||
database = Database.query.filter_by(id=did, server=sid).first()
|
||||
param = None
|
||||
if database:
|
||||
schema_restrictions = database.schema_res
|
||||
|
||||
if schema_restrictions:
|
||||
schema_res = ",".join(
|
||||
["'%s'"] * len(schema_restrictions.split(',')))
|
||||
param = schema_res % (tuple(schema_restrictions.split(',')))
|
||||
|
||||
SQL = render_template(
|
||||
"/".join([self.template_path, 'sql/nodes.sql']),
|
||||
show_sysobj=self.blueprint.show_system_objects,
|
||||
_=gettext,
|
||||
scid=scid
|
||||
scid=scid,
|
||||
schema_restrictions=param
|
||||
)
|
||||
|
||||
status, rset = self.conn.execute_2darray(SQL)
|
||||
@ -428,10 +451,9 @@ class SchemaView(PGChildNodeView):
|
||||
|
||||
if scid is not None:
|
||||
if len(rset['rows']) == 0:
|
||||
return gone(gettext("""
|
||||
Could not find the schema in the database.
|
||||
It may have been removed by another user.
|
||||
"""))
|
||||
return gone(gettext(
|
||||
"""Could not find the schema in the database.
|
||||
It may have been removed by another user."""))
|
||||
row = rset['rows'][0]
|
||||
return make_json_response(
|
||||
data=self.blueprint.generate_browser_node(
|
||||
@ -896,10 +918,9 @@ It may have been removed by another user.
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
if len(res['rows']) == 0:
|
||||
return gone(gettext("""
|
||||
Could not find the schema in the database.
|
||||
It may have been removed by another user.
|
||||
"""))
|
||||
return gone(gettext(
|
||||
"""Could not find the schema in the database.
|
||||
It may have been removed by another user."""))
|
||||
|
||||
data = res['rows'][0]
|
||||
backend_support_keywords = kwargs.copy()
|
||||
|
@ -17,4 +17,10 @@ WHERE
|
||||
NOT (
|
||||
{{ CATALOGS.LIST('nsp') }}
|
||||
)
|
||||
|
||||
{% if schema_restrictions %}
|
||||
AND
|
||||
nsp.nspname in ({{schema_restrictions}})
|
||||
{% endif %}
|
||||
|
||||
ORDER BY nspname;
|
||||
|
@ -50,4 +50,8 @@ WHERE
|
||||
NOT (
|
||||
{{ CATALOGS.LIST('nsp') }}
|
||||
)
|
||||
{% if schema_restrictions %}
|
||||
AND
|
||||
nsp.nspname in ({{schema_restrictions}})
|
||||
{% endif %}
|
||||
ORDER BY 1, nspname;
|
||||
|
@ -46,6 +46,7 @@ define('pgadmin.node.database', [
|
||||
node_image: function() {
|
||||
return 'pg-icon-database';
|
||||
},
|
||||
width: '700px',
|
||||
Init: function() {
|
||||
/* Avoid mulitple registration of menus */
|
||||
if (this.initialized)
|
||||
@ -297,6 +298,7 @@ define('pgadmin.node.database', [
|
||||
defseqacl: [],
|
||||
is_template: false,
|
||||
deftypeacl: [],
|
||||
schema_res:'',
|
||||
},
|
||||
|
||||
// Default values!
|
||||
@ -310,150 +312,189 @@ define('pgadmin.node.database', [
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Database'), cell: 'string',
|
||||
editable: false, type: 'text',
|
||||
},{
|
||||
id: 'did', label: gettext('OID'), cell: 'string', mode: ['properties'],
|
||||
editable: false, type: 'text',
|
||||
},{
|
||||
id: 'datowner', label: gettext('Owner'),
|
||||
editable: false, type: 'text', node: 'role',
|
||||
control: Backform.NodeListByNameControl, select2: { allowClear: false },
|
||||
},{
|
||||
id: 'acl', label: gettext('Privileges'), type: 'text',
|
||||
group: gettext('Security'), mode: ['properties'],
|
||||
},{
|
||||
id: 'tblacl', label: gettext('Default TABLE privileges'), type: 'text',
|
||||
group: gettext('Security'), mode: ['properties'],
|
||||
},{
|
||||
id: 'seqacl', label: gettext('Default SEQUENCE privileges'), type: 'text',
|
||||
group: gettext('Security'), mode: ['properties'],
|
||||
},{
|
||||
id: 'funcacl', label: gettext('Default FUNCTION privileges'), type: 'text',
|
||||
group: gettext('Security'), mode: ['properties'],
|
||||
},{
|
||||
id: 'typeacl', label: gettext('Default TYPE privileges'), type: 'text',
|
||||
group: gettext('Security'), mode: ['properties'], min_version: 90200,
|
||||
},{
|
||||
id: 'is_sys_obj', label: gettext('System database?'),
|
||||
cell:'boolean', type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'comments', label: gettext('Comment'),
|
||||
editable: false, type: 'multiline',
|
||||
},{
|
||||
id: 'encoding', label: gettext('Encoding'),
|
||||
editable: false, type: 'text', group: gettext('Definition'),
|
||||
readonly: function(m) { return !m.isNew(); }, url: 'get_encodings',
|
||||
control: 'node-ajax-options', cache_level: 'server',
|
||||
},{
|
||||
id: 'template', label: gettext('Template'),
|
||||
editable: false, type: 'text', group: gettext('Definition'),
|
||||
readonly: function(m) { return !m.isNew(); },
|
||||
control: 'node-list-by-name', url: 'get_databases', cache_level: 'server',
|
||||
select2: { allowClear: false }, mode: ['create'],
|
||||
transform: function(data, cell) {
|
||||
var res = [],
|
||||
control = cell || this,
|
||||
label = control.model.get('name');
|
||||
schema: [
|
||||
{
|
||||
id: 'name', label: gettext('Database'), cell: 'string',
|
||||
editable: false, type: 'text',
|
||||
},{
|
||||
id: 'did', label: gettext('OID'), cell: 'string', mode: ['properties'],
|
||||
editable: false, type: 'text',
|
||||
},{
|
||||
id: 'datowner', label: gettext('Owner'),
|
||||
editable: false, type: 'text', node: 'role',
|
||||
control: Backform.NodeListByNameControl, select2: { allowClear: false },
|
||||
},{
|
||||
id: 'acl', label: gettext('Privileges'), type: 'text',
|
||||
group: gettext('Security'), mode: ['properties'],
|
||||
},{
|
||||
id: 'tblacl', label: gettext('Default TABLE privileges'), type: 'text',
|
||||
group: gettext('Security'), mode: ['properties'],
|
||||
},{
|
||||
id: 'seqacl', label: gettext('Default SEQUENCE privileges'), type: 'text',
|
||||
group: gettext('Security'), mode: ['properties'],
|
||||
},{
|
||||
id: 'funcacl', label: gettext('Default FUNCTION privileges'), type: 'text',
|
||||
group: gettext('Security'), mode: ['properties'],
|
||||
},{
|
||||
id: 'typeacl', label: gettext('Default TYPE privileges'), type: 'text',
|
||||
group: gettext('Security'), mode: ['properties'], min_version: 90200,
|
||||
},{
|
||||
id: 'is_sys_obj', label: gettext('System database?'),
|
||||
cell:'boolean', type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'comments', label: gettext('Comment'),
|
||||
editable: false, type: 'multiline',
|
||||
},{
|
||||
id: 'encoding', label: gettext('Encoding'),
|
||||
editable: false, type: 'text', group: gettext('Definition'),
|
||||
readonly: function(m) { return !m.isNew(); }, url: 'get_encodings',
|
||||
control: 'node-ajax-options', cache_level: 'server',
|
||||
},{
|
||||
id: 'template', label: gettext('Template'),
|
||||
editable: false, type: 'text', group: gettext('Definition'),
|
||||
readonly: function(m) { return !m.isNew(); },
|
||||
control: 'node-list-by-name', url: 'get_databases', cache_level: 'server',
|
||||
select2: { allowClear: false }, mode: ['create'],
|
||||
transform: function(data, cell) {
|
||||
var res = [],
|
||||
control = cell || this,
|
||||
label = control.model.get('name');
|
||||
|
||||
if (!control.model.isNew()) {
|
||||
res.push({label: label, value: label});
|
||||
}
|
||||
else {
|
||||
if (data && _.isArray(data)) {
|
||||
_.each(data, function(d) {
|
||||
res.push({label: d, value: d,
|
||||
image: 'pg-icon-database'});
|
||||
});
|
||||
if (!control.model.isNew()) {
|
||||
res.push({label: label, value: label});
|
||||
}
|
||||
}
|
||||
return res;
|
||||
},
|
||||
},{
|
||||
id: 'spcname', label: gettext('Tablespace'),
|
||||
editable: false, type: 'text', group: gettext('Definition'),
|
||||
control: 'node-list-by-name', node: 'tablespace',
|
||||
select2: { allowClear: false },
|
||||
filter: function(m) {
|
||||
return (m.label != 'pg_global');
|
||||
},
|
||||
},{
|
||||
id: 'datcollate', label: gettext('Collation'),
|
||||
editable: false, type: 'text', group: gettext('Definition'),
|
||||
readonly: function(m) { return !m.isNew(); }, url: 'get_ctypes',
|
||||
control: 'node-ajax-options', cache_level: 'server',
|
||||
},{
|
||||
id: 'datctype', label: gettext('Character type'),
|
||||
editable: false, type: 'text', group: gettext('Definition'),
|
||||
readonly: function(m) { return !m.isNew(); }, url: 'get_ctypes',
|
||||
control: 'node-ajax-options', cache_level: 'server',
|
||||
},{
|
||||
id: 'datconnlimit', label: gettext('Connection limit'),
|
||||
editable: false, type: 'int', group: gettext('Definition'), min: -1,
|
||||
},{
|
||||
id: 'is_template', label: gettext('Template?'),
|
||||
editable: false, type: 'switch', group: gettext('Definition'),
|
||||
readonly: true, mode: ['properties', 'edit'],
|
||||
},{
|
||||
id: 'datallowconn', label: gettext('Allow connections?'),
|
||||
editable: false, type: 'switch', group: gettext('Definition'),
|
||||
mode: ['properties'],
|
||||
},{
|
||||
id: 'datacl', label: gettext('Privileges'), type: 'collection',
|
||||
model: pgBrowser.Node.PrivilegeRoleModel.extend({
|
||||
privileges: ['C', 'T', 'c'],
|
||||
}), uniqueCol : ['grantee', 'grantor'], editable: false,
|
||||
group: gettext('Security'), mode: ['edit', 'create'],
|
||||
canAdd: true, canDelete: true, control: 'unique-col-collection',
|
||||
},{
|
||||
id: 'variables', label: '', type: 'collection',
|
||||
model: pgBrowser.Node.VariableModel.extend({keys:['name', 'role']}), editable: false,
|
||||
group: gettext('Parameters'), mode: ['edit', 'create'],
|
||||
canAdd: true, canEdit: false, canDelete: true, hasRole: true,
|
||||
control: Backform.VariableCollectionControl, node: 'role',
|
||||
},{
|
||||
id: 'seclabels', label: gettext('Security labels'),
|
||||
model: pgBrowser.SecLabelModel,
|
||||
editable: false, type: 'collection', canEdit: false,
|
||||
group: gettext('Security'), canDelete: true,
|
||||
mode: ['edit', 'create'], canAdd: true,
|
||||
control: 'unique-col-collection', uniqueCol : ['provider'],
|
||||
min_version: 90200,
|
||||
},{
|
||||
type: 'nested', control: 'tab', group: gettext('Default Privileges'),
|
||||
mode: ['edit'],
|
||||
schema:[{
|
||||
id: 'deftblacl', model: pgBrowser.Node.PrivilegeRoleModel.extend(
|
||||
{privileges: ['a', 'r', 'w', 'd', 'D', 'x', 't']}), label: '',
|
||||
editable: false, type: 'collection', group: gettext('Tables'),
|
||||
mode: ['edit', 'create'], control: 'unique-col-collection',
|
||||
canAdd: true, canDelete: true, uniqueCol : ['grantee', 'grantor'],
|
||||
else {
|
||||
if (data && _.isArray(data)) {
|
||||
_.each(data, function(d) {
|
||||
res.push({label: d, value: d,
|
||||
image: 'pg-icon-database'});
|
||||
});
|
||||
}
|
||||
}
|
||||
return res;
|
||||
},
|
||||
},{
|
||||
id: 'defseqacl', model: pgBrowser.Node.PrivilegeRoleModel.extend(
|
||||
{privileges: ['r', 'w', 'U']}), label: '',
|
||||
editable: false, type: 'collection', group: gettext('Sequences'),
|
||||
mode: ['edit', 'create'], control: 'unique-col-collection',
|
||||
canAdd: true, canDelete: true, uniqueCol : ['grantee', 'grantor'],
|
||||
id: 'spcname', label: gettext('Tablespace'),
|
||||
editable: false, type: 'text', group: gettext('Definition'),
|
||||
control: 'node-list-by-name', node: 'tablespace',
|
||||
select2: { allowClear: false },
|
||||
filter: function(m) {
|
||||
return (m.label != 'pg_global');
|
||||
},
|
||||
},{
|
||||
id: 'deffuncacl', model: pgBrowser.Node.PrivilegeRoleModel.extend(
|
||||
{privileges: ['X']}), label: '',
|
||||
editable: false, type: 'collection', group: gettext('Functions'),
|
||||
mode: ['edit', 'create'], control: 'unique-col-collection',
|
||||
canAdd: true, canDelete: true, uniqueCol : ['grantee', 'grantor'],
|
||||
id: 'datcollate', label: gettext('Collation'),
|
||||
editable: false, type: 'text', group: gettext('Definition'),
|
||||
readonly: function(m) { return !m.isNew(); }, url: 'get_ctypes',
|
||||
control: 'node-ajax-options', cache_level: 'server',
|
||||
},{
|
||||
id: 'deftypeacl', model: pgBrowser.Node.PrivilegeRoleModel.extend(
|
||||
{privileges: ['U']}), label: '',
|
||||
editable: false, type: 'collection', group: 'deftypesacl_group',
|
||||
mode: ['edit', 'create'], control: 'unique-col-collection',
|
||||
canAdd: true, canDelete: true, uniqueCol : ['grantee', 'grantor'],
|
||||
id: 'datctype', label: gettext('Character type'),
|
||||
editable: false, type: 'text', group: gettext('Definition'),
|
||||
readonly: function(m) { return !m.isNew(); }, url: 'get_ctypes',
|
||||
control: 'node-ajax-options', cache_level: 'server',
|
||||
},{
|
||||
id: 'datconnlimit', label: gettext('Connection limit'),
|
||||
editable: false, type: 'int', group: gettext('Definition'), min: -1,
|
||||
},{
|
||||
id: 'is_template', label: gettext('Template?'),
|
||||
editable: false, type: 'switch', group: gettext('Definition'),
|
||||
readonly: true, mode: ['properties', 'edit'],
|
||||
},{
|
||||
id: 'datallowconn', label: gettext('Allow connections?'),
|
||||
editable: false, type: 'switch', group: gettext('Definition'),
|
||||
mode: ['properties'],
|
||||
},{
|
||||
id: 'datacl', label: gettext('Privileges'), type: 'collection',
|
||||
model: pgBrowser.Node.PrivilegeRoleModel.extend({
|
||||
privileges: ['C', 'T', 'c'],
|
||||
}), uniqueCol : ['grantee', 'grantor'], editable: false,
|
||||
group: gettext('Security'), mode: ['edit', 'create'],
|
||||
canAdd: true, canDelete: true, control: 'unique-col-collection',
|
||||
},{
|
||||
id: 'variables', label: '', type: 'collection',
|
||||
model: pgBrowser.Node.VariableModel.extend({keys:['name', 'role']}), editable: false,
|
||||
group: gettext('Parameters'), mode: ['edit', 'create'],
|
||||
canAdd: true, canEdit: false, canDelete: true, hasRole: true,
|
||||
control: Backform.VariableCollectionControl, node: 'role',
|
||||
},{
|
||||
id: 'seclabels', label: gettext('Security labels'),
|
||||
model: pgBrowser.SecLabelModel,
|
||||
editable: false, type: 'collection', canEdit: false,
|
||||
group: gettext('Security'), canDelete: true,
|
||||
mode: ['edit', 'create'], canAdd: true,
|
||||
control: 'unique-col-collection', uniqueCol : ['provider'],
|
||||
min_version: 90200,
|
||||
},{
|
||||
id: 'deftypesacl_group', type: 'group', label: gettext('Types'),
|
||||
mode: ['edit', 'create'], min_version: 90200,
|
||||
type: 'nested', control: 'tab', group: gettext('Default Privileges'),
|
||||
mode: ['edit'],
|
||||
schema:[{
|
||||
id: 'deftblacl', model: pgBrowser.Node.PrivilegeRoleModel.extend(
|
||||
{privileges: ['a', 'r', 'w', 'd', 'D', 'x', 't']}), label: '',
|
||||
editable: false, type: 'collection', group: gettext('Tables'),
|
||||
mode: ['edit', 'create'], control: 'unique-col-collection',
|
||||
canAdd: true, canDelete: true, uniqueCol : ['grantee', 'grantor'],
|
||||
},{
|
||||
id: 'defseqacl', model: pgBrowser.Node.PrivilegeRoleModel.extend(
|
||||
{privileges: ['r', 'w', 'U']}), label: '',
|
||||
editable: false, type: 'collection', group: gettext('Sequences'),
|
||||
mode: ['edit', 'create'], control: 'unique-col-collection',
|
||||
canAdd: true, canDelete: true, uniqueCol : ['grantee', 'grantor'],
|
||||
},{
|
||||
id: 'deffuncacl', model: pgBrowser.Node.PrivilegeRoleModel.extend(
|
||||
{privileges: ['X']}), label: '',
|
||||
editable: false, type: 'collection', group: gettext('Functions'),
|
||||
mode: ['edit', 'create'], control: 'unique-col-collection',
|
||||
canAdd: true, canDelete: true, uniqueCol : ['grantee', 'grantor'],
|
||||
},{
|
||||
id: 'deftypeacl', model: pgBrowser.Node.PrivilegeRoleModel.extend(
|
||||
{privileges: ['U']}), label: '',
|
||||
editable: false, type: 'collection', group: 'deftypesacl_group',
|
||||
mode: ['edit', 'create'], control: 'unique-col-collection',
|
||||
canAdd: true, canDelete: true, uniqueCol : ['grantee', 'grantor'],
|
||||
min_version: 90200,
|
||||
},{
|
||||
id: 'deftypesacl_group', type: 'group', label: gettext('Types'),
|
||||
mode: ['edit', 'create'], min_version: 90200,
|
||||
},
|
||||
],
|
||||
},{
|
||||
type: 'collection', group: gettext('Advanced'),
|
||||
},
|
||||
{
|
||||
id: 'schema_res', label: gettext('Schema restriction'),
|
||||
type: 'select2', group: gettext('Advanced'),
|
||||
mode: ['properties', 'edit', 'create'],
|
||||
select2: {
|
||||
multiple: true, allowClear: false, tags: true,
|
||||
tokenSeparators: [','], first_empty: false,
|
||||
selectOnClose: true, emptyOptions: true,
|
||||
},
|
||||
control: Backform.Select2Control.extend({
|
||||
onChange: function() {
|
||||
Backform.Select2Control.prototype.onChange.apply(this, arguments);
|
||||
if (!this.model || !(
|
||||
this.model.changed &&
|
||||
this.model.get('oid') !== undefined
|
||||
)) {
|
||||
this.model.inform_text = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.model.origSessAttrs.schema_res != this.model.changed.schema_res)
|
||||
{
|
||||
this.model.inform_text = gettext(
|
||||
'Please refresh the Schemas node to make changes to the schema restriction take effect.'
|
||||
);
|
||||
} else {
|
||||
this.model.inform_text = undefined;
|
||||
}
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'note', label: gettext('Note: Changes to the schema restriction will require the Schemas node in the browser to be refreshed before they will be shown.'),
|
||||
group: gettext('Advanced'), type: 'help',
|
||||
mode: ['edit', 'create'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
validate: function() {
|
||||
var name = this.get('name');
|
||||
|
@ -41,7 +41,8 @@ class DatabasesUpdateTestCase(BaseTestGenerator):
|
||||
try:
|
||||
data = {
|
||||
"comments": "This is db update comment",
|
||||
"id": self.db_id
|
||||
"id": self.db_id,
|
||||
"schema_res": ["public"]
|
||||
}
|
||||
response = self.tester.put(
|
||||
self.url + str(utils.SERVER_GROUP) + '/' + str(
|
||||
|
@ -73,7 +73,8 @@ def get_db_data(db_owner):
|
||||
"name": "db_add_%s" % str(uuid.uuid4())[1: 8],
|
||||
"privileges": [],
|
||||
"securities": [],
|
||||
"variables": []
|
||||
"variables": [],
|
||||
"schema_res": ["public", "sample"]
|
||||
}
|
||||
return data
|
||||
|
||||
|
@ -39,7 +39,6 @@ define('pgadmin.node.server', [
|
||||
}],
|
||||
validate: function() {
|
||||
this.errorModel.clear();
|
||||
|
||||
if (_.isUndefined(this.get('label')) ||
|
||||
_.isNull(this.get('label')) ||
|
||||
String(this.get('label')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
@ -66,7 +65,6 @@ define('pgadmin.node.server', [
|
||||
return d && d.connected;
|
||||
},
|
||||
Init: function() {
|
||||
|
||||
/* Avoid multiple registration of same menus */
|
||||
if (this.initialized)
|
||||
return;
|
||||
@ -785,16 +783,70 @@ define('pgadmin.node.server', [
|
||||
mode: ['properties', 'edit', 'create'],
|
||||
},{
|
||||
id: 'host', label: gettext('Host name/address'), type: 'text', group: gettext('Connection'),
|
||||
mode: ['properties', 'edit', 'create'], readonly: 'isConnected',
|
||||
mode: ['properties', 'edit', 'create'],
|
||||
control: Backform.InputControl.extend({
|
||||
onChange: function() {
|
||||
Backform.InputControl.prototype.onChange.apply(this, arguments);
|
||||
if (!this.model || !this.model.changed) {
|
||||
this.model.inform_text = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.model.origSessAttrs.host != this.model.changed.host && !this.model.isNew() && this.model.get('connected'))
|
||||
{
|
||||
this.model.inform_text = gettext(
|
||||
'To apply changes to the connection configuration, please disconnect from the server and then reconnect.'
|
||||
);
|
||||
} else {
|
||||
this.model.inform_text = undefined;
|
||||
}
|
||||
},
|
||||
}),
|
||||
},{
|
||||
id: 'port', label: gettext('Port'), type: 'int', group: gettext('Connection'),
|
||||
mode: ['properties', 'edit', 'create'], readonly: 'isConnected', min: 1, max: 65535,
|
||||
mode: ['properties', 'edit', 'create'], min: 1, max: 65535,
|
||||
control: Backform.InputControl.extend({
|
||||
onChange: function() {
|
||||
Backform.InputControl.prototype.onChange.apply(this, arguments);
|
||||
if (!this.model || !this.model.changed) {
|
||||
this.model.inform_text = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.model.origSessAttrs.port != this.model.changed.port && !this.model.isNew() && this.model.get('connected'))
|
||||
{
|
||||
this.model.inform_text = gettext(
|
||||
'To apply changes to the connection configuration, please disconnect from the server and then reconnect.'
|
||||
);
|
||||
} else {
|
||||
this.model.inform_text = undefined;
|
||||
}
|
||||
},
|
||||
}),
|
||||
},{
|
||||
id: 'db', label: gettext('Maintenance database'), type: 'text', group: gettext('Connection'),
|
||||
mode: ['properties', 'edit', 'create'], readonly: 'isConnected',
|
||||
},{
|
||||
id: 'username', label: gettext('Username'), type: 'text', group: gettext('Connection'),
|
||||
mode: ['properties', 'edit', 'create'], readonly: 'isConnected',
|
||||
mode: ['properties', 'edit', 'create'],
|
||||
control: Backform.InputControl.extend({
|
||||
onChange: function() {
|
||||
Backform.InputControl.prototype.onChange.apply(this, arguments);
|
||||
if (!this.model || !this.model.changed) {
|
||||
this.model.inform_text = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.model.origSessAttrs.username != this.model.changed.username && !this.model.isNew() && this.model.get('connected'))
|
||||
{
|
||||
this.model.inform_text = gettext(
|
||||
'To apply changes to the connection configuration, please disconnect from the server and then reconnect.'
|
||||
);
|
||||
} else {
|
||||
this.model.inform_text = undefined;
|
||||
}
|
||||
},
|
||||
}),
|
||||
},{
|
||||
id: 'password', label: gettext('Password'), type: 'password', maxlength: '2000',
|
||||
group: gettext('Connection'), control: 'input', mode: ['create'], deps: ['connect_now'],
|
||||
|
@ -1339,6 +1339,21 @@ define('pgadmin.browser.node', [
|
||||
}
|
||||
}.bind(panel),
|
||||
|
||||
informBeforeAttributeChange = function(ok_callback) {
|
||||
var j = this.$container.find('.obj_properties').first();
|
||||
view = j && j.data('obj-view');
|
||||
|
||||
if (view && view.model && !_.isUndefined(view.model.inform_text) && !_.isNull(view.model.inform_text)) {
|
||||
Alertify.alert(
|
||||
gettext('Warning'),
|
||||
gettext(view.model.inform_text)
|
||||
);
|
||||
|
||||
}
|
||||
ok_callback();
|
||||
return true;
|
||||
}.bind(panel),
|
||||
|
||||
onSave = function(view, saveBtn) {
|
||||
var m = view.model,
|
||||
d = m.toJSON(true),
|
||||
@ -1535,9 +1550,11 @@ define('pgadmin.browser.node', [
|
||||
warnBeforeAttributeChange.call(
|
||||
panel,
|
||||
function() {
|
||||
setTimeout(function() {
|
||||
onSave.call(this, view, btn);
|
||||
}, 0);
|
||||
informBeforeAttributeChange.call(panel, function(){
|
||||
setTimeout(function() {
|
||||
onSave.call(this, view, btn);
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -290,3 +290,18 @@ class QueryHistoryModel(db.Model):
|
||||
dbname = db.Column(db.String(), nullable=False, primary_key=True)
|
||||
query_info = db.Column(db.String(), nullable=False)
|
||||
last_updated_flag = db.Column(db.String(), nullable=False)
|
||||
|
||||
|
||||
class Database(db.Model):
|
||||
"""
|
||||
Define a Database.
|
||||
"""
|
||||
__tablename__ = 'database'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
schema_res = db.Column(db.String(256), nullable=True)
|
||||
server = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('server.id'),
|
||||
nullable=False,
|
||||
primary_key=True
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user