From c873218c3279a12d97852f06d3d64fd33f6346d8 Mon Sep 17 00:00:00 2001 From: Nikhil Mohite Date: Tue, 30 Jun 2020 19:15:23 +0530 Subject: [PATCH] Added support for schema level restriction. Fixes #5583 Allow user to edit the connection properties when the database server is already connected. --- docs/en_US/database_dialog.rst | 13 + docs/en_US/images/database_advanced.png | Bin 0 -> 51645 bytes docs/en_US/release_notes_4_24.rst | 1 + web/migrations/versions/84700139beb0_.py | 32 ++ .../browser/server_groups/servers/__init__.py | 3 +- .../servers/databases/__init__.py | 187 +++++++---- .../servers/databases/schemas/__init__.py | 41 ++- .../schemas/pg/9.2_plus/sql/nodes.sql | 6 + .../schemas/pg/9.2_plus/sql/properties.sql | 4 + .../servers/databases/static/js/database.js | 313 ++++++++++-------- .../servers/databases/tests/test_db_put.py | 3 +- .../servers/databases/tests/utils.py | 3 +- .../server_groups/servers/static/js/server.js | 62 +++- web/pgadmin/browser/static/js/node.js | 23 +- web/pgadmin/model/__init__.py | 15 + 15 files changed, 492 insertions(+), 214 deletions(-) create mode 100644 docs/en_US/images/database_advanced.png create mode 100644 web/migrations/versions/84700139beb0_.py diff --git a/docs/en_US/database_dialog.rst b/docs/en_US/database_dialog.rst index 36939a154..51237e174 100644 --- a/docs/en_US/database_dialog.rst +++ b/docs/en_US/database_dialog.rst @@ -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 diff --git a/docs/en_US/images/database_advanced.png b/docs/en_US/images/database_advanced.png new file mode 100644 index 0000000000000000000000000000000000000000..cf90f3cc7bb430a4fb17f9219535b11e9ef19525 GIT binary patch literal 51645 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 Im)0NuKTW|K6951J literal 0 HcmV?d00001 diff --git a/docs/en_US/release_notes_4_24.rst b/docs/en_US/release_notes_4_24.rst index a277124f5..b3f0da759 100644 --- a/docs/en_US/release_notes_4_24.rst +++ b/docs/en_US/release_notes_4_24.rst @@ -9,6 +9,7 @@ This release contains a number of bug fixes and new features since the release o New features ************ +| `Issue #5583 `_ - Added support for schema level restriction. Housekeeping ************ diff --git a/web/migrations/versions/84700139beb0_.py b/web/migrations/versions/84700139beb0_.py new file mode 100644 index 000000000..455fd3d9b --- /dev/null +++ b/web/migrations/versions/84700139beb0_.py @@ -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 diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py index 2745e5c65..ca6cb8bb6 100644 --- a/web/pgadmin/browser/server_groups/servers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/__init__.py @@ -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 diff --git a/web/pgadmin/browser/server_groups/servers/databases/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/__init__.py index df0e668a7..42394c5e1 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/__init__.py @@ -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( diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/__init__.py index e3c32e3f6..943e9df7e 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/__init__.py @@ -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() diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/schemas/pg/9.2_plus/sql/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/schemas/pg/9.2_plus/sql/nodes.sql index d1a19d1a3..3cf66474a 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/schemas/pg/9.2_plus/sql/nodes.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/schemas/pg/9.2_plus/sql/nodes.sql @@ -17,4 +17,10 @@ WHERE NOT ( {{ CATALOGS.LIST('nsp') }} ) + + {% if schema_restrictions %} + AND + nsp.nspname in ({{schema_restrictions}}) + {% endif %} + ORDER BY nspname; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/schemas/pg/9.2_plus/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/schemas/pg/9.2_plus/sql/properties.sql index e07626d80..f340623fb 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/schemas/pg/9.2_plus/sql/properties.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/schemas/pg/9.2_plus/sql/properties.sql @@ -50,4 +50,8 @@ WHERE NOT ( {{ CATALOGS.LIST('nsp') }} ) + {% if schema_restrictions %} + AND + nsp.nspname in ({{schema_restrictions}}) + {% endif %} ORDER BY 1, nspname; diff --git a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js index affa368ed..779f3e085 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js +++ b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js @@ -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'); diff --git a/web/pgadmin/browser/server_groups/servers/databases/tests/test_db_put.py b/web/pgadmin/browser/server_groups/servers/databases/tests/test_db_put.py index ae861b165..0a40d79fb 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/tests/test_db_put.py +++ b/web/pgadmin/browser/server_groups/servers/databases/tests/test_db_put.py @@ -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( diff --git a/web/pgadmin/browser/server_groups/servers/databases/tests/utils.py b/web/pgadmin/browser/server_groups/servers/databases/tests/utils.py index 8eeedf779..f44a81de5 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/tests/utils.py +++ b/web/pgadmin/browser/server_groups/servers/databases/tests/utils.py @@ -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 diff --git a/web/pgadmin/browser/server_groups/servers/static/js/server.js b/web/pgadmin/browser/server_groups/servers/static/js/server.js index 240154c4c..37d3f1693 100644 --- a/web/pgadmin/browser/server_groups/servers/static/js/server.js +++ b/web/pgadmin/browser/server_groups/servers/static/js/server.js @@ -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'], diff --git a/web/pgadmin/browser/static/js/node.js b/web/pgadmin/browser/static/js/node.js index 7828b30c0..c42d24bce 100644 --- a/web/pgadmin/browser/static/js/node.js +++ b/web/pgadmin/browser/static/js/node.js @@ -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); + }); } ); }); diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py index ab9858326..03681e120 100644 --- a/web/pgadmin/model/__init__.py +++ b/web/pgadmin/model/__init__.py @@ -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 + )