public inbox for [email protected]  
help / color / mirror / Atom feed
[pgAdmin4] [Patch]: Grant Wizard
24+ messages / 3 participants
[nested] [flat]

* [pgAdmin4] [Patch]: Grant Wizard
@ 2016-03-03 12:49  Surinder Kumar <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Surinder Kumar @ 2016-03-03 12:49 UTC (permalink / raw)
  To: pgadmin-hackers

Hi,

PFA patch for Grant Wizard.

Before applying grant wizard patch:

        1. Apply patch for "wizard JS file" which Khushboo had shared with
Ashesh personally.
            I am using that patch with few changes in that. Ashesh will
review
            and commit that patch.

        2. Apply patches of "Sequence" and "Functions" macros.


Please review the patch and Let me know for any comments.


Thanks
Surinder Kumar


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [application/octet-stream] grant_wizard.patch (56.5K, 3-grant_wizard.patch)
  download | inline diff:
diff --git a/web/pgadmin/browser/__init__.py b/web/pgadmin/browser/__init__.py
index 38693d4..e00b6a7 100644
--- a/web/pgadmin/browser/__init__.py
+++ b/web/pgadmin/browser/__init__.py
@@ -153,7 +153,8 @@ class BrowserModule(PgAdminModule):
         for name, end in [
                 ['pgadmin.browser.menu', 'js/menu'],
                 ['pgadmin.browser.panel', 'js/panel'],
-                ['pgadmin.browser.frame', 'js/frame']]:
+                ['pgadmin.browser.frame', 'js/frame'],
+                ['pgadmin.browser.wizard', 'js/wizard']]:
             scripts.append({
                 'name': name, 'path': url_for('browser.static', filename=end),
                 'preloaded': True})
diff --git a/web/pgadmin/browser/static/js/node.ui.js b/web/pgadmin/browser/static/js/node.ui.js
index b84b6ee..47f1266 100644
--- a/web/pgadmin/browser/static/js/node.ui.js
+++ b/web/pgadmin/browser/static/js/node.ui.js
@@ -279,7 +279,7 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
         return false;
       };
 
-      while(p) {
+      while(p && p.length > 0) {
         top = p.get(0).offsetTop + p.height();
         p = p.parent();
         if (hasScrollbar(p)) {
diff --git a/web/pgadmin/static/js/backform.pgadmin.js b/web/pgadmin/static/js/backform.pgadmin.js
index 4346156..8f913d2 100644
--- a/web/pgadmin/static/js/backform.pgadmin.js
+++ b/web/pgadmin/static/js/backform.pgadmin.js
@@ -108,7 +108,7 @@
       return m[idx > len ? 0 : idx];
     }
     return type;
-  }
+  };
 
 
   var BackformControlInit = Backform.Control.prototype.initialize,
@@ -745,6 +745,13 @@
         self.stopListening(self.collection, "change", self.collectionChanged);
       }
 
+      // Remove grid
+      if (this.grid) {
+        this.grid.remove();
+        delete this.grid;
+        this.grid = null;
+      }
+
       Backform.Control.prototype.remove.apply(this, arguments);
     },
     collectionChanged: function(newModel, coll, op) {
@@ -1146,8 +1153,12 @@
       /*
        * We will listen to the tab change event to check, if the SQL tab has
        * been clicked or, not.
+       *
+       * Also, listen to the wizard next/prev page changed
        */
       this.model.on('pg-property-tab-changed', this.onTabChange, this);
+      this.model.on('pgadmin-wizard:nextpage:sql', this.onWizardNextPageChange, this);
+      this.model.on('pgadmin-wizard:prevpage:sql', this.onWizardPrevPageChange, this);
     },
     getValueFromDOM: function() {
         return this.formatter.toRaw(this.$el.find("textarea").val(), this.model);
@@ -1205,8 +1216,47 @@
         }
       }
     },
+
+    // This method fetches the modified SQL for the wizard
+    onWizardNextPageChange: function(obj){
+
+      var self = this,
+        m = self.model,
+        gid = m.node_info['server-group']._id,
+        sid = m.node_info.server._id,
+        did = m.node_info.database._id,
+        nspname = m.node_info.schema.label;
+
+        // generate encoded url based on wizard type
+        msql_url = "/wizard/"+obj.wizard_type+"/msql/"+
+          S('%s/%s/%s/%s/%s/').sprintf(
+              encodeURI(gid), encodeURI(sid), encodeURI(did),
+              encodeURI(obj.node_type), encodeURI(nspname)).value();
+
+      // Fetches modified SQL
+      $.ajax({
+        url: msql_url,
+        type: 'GET',
+        cache: false,
+        data: self.model.toJSON(true, 'GET'),
+        dataType: "json",
+        contentType: "application/json"
+      }).done(function(res) {
+        self.sqlTab.clearHistory();
+        self.sqlTab.setValue(res.data);
+      }).fail(function() {
+        self.model.trigger('pgadmin-view:msql:error');
+      }).always(function() {
+        self.model.trigger('pgadmin-view:msql:fetched');
+      });
+
+    },
     remove: function() {
       this.model.off('pg-property-tab-changed', this.onTabChange, this);
+
+      // Stop listen to next/prev page events
+      this.model.off('pgadmin-wizard:nextpage:sql', this.onWizardNextPageChange, this);
+      this.model.off('pgadmin-wizard:prevpage:sql', this.onWizardPrevPageChange, this);
       Backform.Control.__super__.remove.apply(this, arguments);
     }
 });
diff --git a/web/pgadmin/templates/base.html b/web/pgadmin/templates/base.html
index 6ea5d69..d91203a 100755
--- a/web/pgadmin/templates/base.html
+++ b/web/pgadmin/templates/base.html
@@ -28,6 +28,8 @@
         <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/backgrid/backgrid-paginator.css' if config.DEBUG else 'css/backgrid/backgrid-paginator.min.css')}}"/>
         <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/backgrid/backgrid-filter.css' if config.DEBUG else 'css/backgrid/backgrid-filter.min.css')}}"/>
         <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/select2/select2.css' if config.DEBUG else 'css/select2/select2.min.css')}}"/>
+        <link type="text/css" rel="stylesheet" href="{{ url_for('grantwizard.static', filename='css/wizard.css') }}"/>
+        <link type="text/css" rel="stylesheet" href="{{ url_for('grantwizard.static', filename='css/grantwizard.css') }}"/>
 
         <!-- View specified stylesheets -->
         {% for stylesheet in current_app.stylesheets %}
diff --git a/web/pgadmin/tools/grantwizard/__init__.py b/web/pgadmin/tools/grantwizard/__init__.py
new file mode 100644
index 0000000..8b2e82b
--- /dev/null
+++ b/web/pgadmin/tools/grantwizard/__init__.py
@@ -0,0 +1,286 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+""" Implements Grant Wizard"""
+
+import json
+from flask import render_template, request, current_app
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_response as ajax_response, \
+    make_json_response, internal_server_error
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from pgadmin.browser.server_groups.servers.utils import parse_priv_to_db
+from pgadmin.utils import PgAdminModule
+from flask import Response, url_for
+from flask.ext.security import login_required
+
+# As unicode type is not available in python3
+# If we check a variable is "isinstance(variable, str)
+# it breaks in python 3 as variable type is not string its unicode.
+# We assign basestring as str type if it is python3, unicode
+# if it is python2.
+
+try:
+    unicode = unicode
+except NameError:
+    # 'unicode' is undefined, must be Python 3
+    str = str
+    unicode = str
+    bytes = bytes
+    basestring = (str, bytes)
+else:
+    # 'unicode' exists, must be Python 2
+    str = str
+    unicode = unicode
+    bytes = str
+    basestring = basestring
+
+MODULE_NAME = 'grantwizard'
+
+
+class GrantWizardModule(PgAdminModule):
+    """
+    class GrantWizardModule(Object):
+
+        It is a wizard which inherits PgAdminModule
+        class and define methods to load its own
+        javascript file.
+    """
+    def get_own_javascripts(self):
+        """Add grantwizard.js file to load it on window load"""
+        return [{
+            'name': 'pgadmin.grantwizard',
+            'path': url_for('grantwizard.index') + 'grantwizard',
+            'when': None
+        }
+        ]
+
+# Create blueprint for GrantWizardModule class
+blueprint = GrantWizardModule(
+    MODULE_NAME, __name__, static_url_path='',
+    url_prefix='/wizard')
+
+
[email protected]("/")
+@login_required
+def index():
+    pass
+
+
[email protected]("/grantwizard.js")
+@login_required
+def script():
+    """render own javascript"""
+    return Response(response=render_template(
+                "grantwizard/js/grantwizard.js", _=gettext),
+                status=200,
+                mimetype="application/javascript")
+
+
[email protected](
+    '/grantwizard/properties/<int:gid>/<int:sid>/<int:did>'
+    '/<int:node_id>/<node_name>/<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+@login_required
+def properties(gid, sid, did, node_id, node_name, node_type, nspname):
+    """It fetches the properties of object types
+       and render into selection page of wizard
+    """
+    res_data = []
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    # we will set template path for sql scripts
+    template_path = 'grantwizard/sql'
+
+    # Fetch functions against schema
+    if node_type in ['schema']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type=node_type, nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch trigger functions
+    if node_type in ['schema', 'trigger_function']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='trigger_function', nspname=nspname)
+        status, res = conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch Sequences against schema
+    if node_type in ['schema', 'sequence']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='sequence', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+        res_data.extend(res['rows'])
+
+    # Fetch Tables against schema
+    if node_type in ['schema', 'table']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='table', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch Views against schema
+    if node_type in ['schema', 'coll-view']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='coll-view', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    return ajax_response(
+            response=res_data,
+            status=200
+            )
+
+
[email protected](
+    '/grantwizard/msql/<int:gid>/<int:sid>/<int:did>/<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+def msql(gid, sid, did, node_type, nspname):
+    """
+    This function will return modified SQL
+    """
+    data = {}
+    for k, v in request.args.items():
+        try:
+            data[k] = json.loads(v)
+        except ValueError:
+            data[k] = v
+
+    # Form db connection
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    # we will set template path for sql scripts
+    template_path = 'grantwizard/sql'
+
+    acls = []
+    try:
+        acls = render_template(
+            "/".join([template_path, 'allowed_acl.json'])
+            )
+        acls = json.loads(acls)
+    except Exception as e:
+        current_app.logger.exception(e)
+
+    try:
+        # Parse Privileges
+        if 'acl' in data:
+            data['acl'] = parse_priv_to_db(data['acl'], acls[node_type]['acl'])
+
+        # Pass Database Objects and get SQL for privileges
+        SQL = render_template("/".join(
+            [template_path, 'grant.sql']),
+            data=data, nspname=nspname, conn=conn)
+
+        res = {'data': SQL}
+
+        return ajax_response(
+            response=res,
+            status=200
+            )
+
+    except Exception as e:
+        return make_json_response(
+            status=410,
+            success=0,
+            errormsg=e.message
+        )
+
+
[email protected](
+    '/grantwizard/save/<int:gid>/<int:sid>/<int:did>/'
+    '<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+def save(gid, sid, did, node_type, nspname):
+    """
+    This function will apply the privileges to the selected
+    Database Objects
+    """
+    data = request.form if request.form else json.loads(request.data.decode())
+
+    # Form db connection and we use conn to execute sql
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    # we will set template path for sql scripts
+    template_path = 'grantwizard/sql'
+
+    acls = []
+    try:
+        acls = render_template(
+            "/".join([template_path, 'allowed_acl.json'])
+            )
+        acls = json.loads(acls)
+    except Exception as e:
+        current_app.logger.exception(e)
+
+    try:
+
+        # Parse privileges
+        if 'acl' in data:
+            data['acl'] = parse_priv_to_db(
+                data['acl'],
+                acls[node_type]['acl']
+                )
+
+        # Pass Database Objects and get SQL for privileges
+        SQL = render_template("/".join(
+            [template_path, 'grant.sql']),
+            data=data, nspname=nspname, conn=conn)
+
+        if SQL and isinstance(SQL, basestring) and \
+                SQL.strip('\n') and SQL.strip(' '):
+                status, res = conn.execute_dict(SQL)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                return make_json_response(
+                    success=1,
+                    info="Privileges Applied"
+                )
+
+    except Exception as e:
+        return make_json_response(
+            status=410,
+            success=0,
+            errormsg=e.message
+        )
diff --git a/web/pgadmin/tools/grantwizard/static/css/grantwizard.css b/web/pgadmin/tools/grantwizard/static/css/grantwizard.css
new file mode 100644
index 0000000..953e788
--- /dev/null
+++ b/web/pgadmin/tools/grantwizard/static/css/grantwizard.css
@@ -0,0 +1,48 @@
+/* Grant Wizard CSS */
+
+/* CSS to make Db object type table
+ * fixed so that tbody content may
+ * scroll
+ */
+.object_type_table thead tr {
+  position: relative;
+  display: block;
+}
+
+.object_type_table tbody {
+  display: block;
+  overflow-y: scroll;
+  height: 160px;
+}
+
+.object_type_table tbody tr td:nth-child(1),
+.object_type_table thead tr th:nth-child(1) {
+  width: 28px;
+  min-width: 28px;
+}
+
+.object_type_table tbody tr td:nth-child(2),
+.object_type_table thead tr th:nth-child(2) {
+  width: 185px;
+  min-width: 185px;
+}
+
+.object_type_table tbody tr td:nth-child(3),
+.object_type_table thead tr th:nth-child(3) {
+  width: 110px;
+  min-width: 110px;
+}
+
+.object_type_table tbody tr td:nth-child(4),
+.object_type_table thead tr th:nth-child(4) {
+  width: 244px;
+}
+
+.error_msg_div {
+	display:block;
+}
+
+/** Override Backgrid filter CSS **/
+.backgrid-filter.form-search {
+  margin: 0px 5px 5px 0;
+}
diff --git a/web/pgadmin/tools/grantwizard/static/css/wizard.css b/web/pgadmin/tools/grantwizard/static/css/wizard.css
new file mode 100644
index 0000000..058afa7
--- /dev/null
+++ b/web/pgadmin/tools/grantwizard/static/css/wizard.css
@@ -0,0 +1,86 @@
+/** CSS for Wizard **/
+.wizard_dlg {
+	height: 300px;
+	padding: 0 10px;
+}
+
+.ajs-content {
+	padding-top: 0px !important;
+}
+
+/* Wizard Header CSS */
+.wizard-header {
+  background: #428bca;
+	padding-left: 15px;
+	padding-bottom: 7px;
+	color: #fff;
+	font-size: 18px;
+	margin-bottom: 15px;
+	height: 76px;
+	line-height: 76px;
+}
+
+.wizard-header h3 {
+  font-size: 18px;
+  display: inline-block;
+}
+
+.wizard-content {
+	overflow-y: auto;
+	float: left;
+	padding: 0;
+	min-height: 280px;
+	max-height: 280px;
+}
+
+/* Wizard Footer CSS */
+.footer {
+  position: absolute;
+  border-top: 1px solid #ccc;
+  bottom: 0px;
+  height: 71px;
+  right: 0px;
+  padding-top: 22px;
+}
+
+/* Wizard Button CSS */
+.wizard-buttons {
+  float: right;
+}
+
+.wizard-buttons button {
+  float: left;
+  padding: 7px 15.2px;
+  font-size: 14px;
+  margin-right: 5px;
+}
+
+.wizard-finish {
+  margin-right: 0;
+}
+
+/* Wizard Status bar CSS */
+.wizard-status-bar {
+  height: 50px;
+  padding-bottom: 10px;
+  margin-bottom: 10px;
+  padding-left: 15px;
+}
+
+/* Error message css */
+.error_msg_div,
+.error_msg_div .pg-prop-status-bar {
+  background: #fff;
+  display: none;
+}
+
+/* In wizard select2 dropdown doesn't
+ * popup because z-index of alertify
+ * wizard is greater than the z-index
+ * of select2 dropdown. To make select2
+ * visible, set z-index of select2
+ * higher value than wizard's
+ */
+.select2-container--open {
+  z-index: 10000;
+}
diff --git a/web/pgadmin/tools/grantwizard/static/img/coll-extension.png b/web/pgadmin/tools/grantwizard/static/img/coll-extension.png
new file mode 100644
index 0000000000000000000000000000000000000000..eed7ca97a33ef595f448b8621168d531f86d51d5
GIT binary patch
literal 1017
zcmV<V0|xwwP)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px%_fSk!MS^xZ(#^N3p?R&Od+_Yu$ibj%T^6RorB#bmn!1_5x}2JiUH9|m=i$p{
zRt>7esZx(rkHMB&m|kp}YkF-i+S9qHoNTYCfx^|oK7c>H>Bx7neMEjjfO9vZnQgzg
znaszo-`vc-ww7K_0rBeAwyS=gm1jPCJeJI$Gj=n&vyq*XV~~VSeQ+?GkzLu)v+(KC
z?&Zpui%@D?5_o7K5L*te<Gd|$E&TiU`}p$t_U!fW;PdR+>*2!a-MZ%4vg6gM;L)L=
zk53w479M3BDQ+lfsBo&|xoE3!8)F$jd_LXGn5dUmHEJcE$)m~b(xK3*v*EqB=fkk&
zzN6u@o!_pI*P(X7g>k=tf76xP$dIj?SVe9ugSLpn>&}+lsF2#DiP)Wj)|h<Mlh449
zm4!uCPy}dL4Vs8UtCv!Hv4M5cjedPPC}$ujZ6{xNJBocXBx4(;)vk8Mhd_@=5nK-#
zWf(t|O0=YEF=rrzvxn93+^yBLT$f*Ao@PIeN0+#kWR+urcQMcJ*SF-sT&rnflVFjw
zkaKM%G=n;@$*_-qITl_M!syGG&7n||RE^1*D|;}$)4tfmhrz6AIbIZAnqq{yi*T%V
zHhVWtV;&u18I#tdIE_NpzkbrYc9VfMLtPX<ZYx1&B5Z9Om3}8VZ7FiPes9o-TDx%1
zw{6X{Wx%FXorW{5k~_krMv{3VMsze|t!`+}gJR5mHj+XnY9(-;Zb*VhCx$c1uU*Hh
zRmG=Gz@R^havV*1JY33nR>*Toyk;|+MLMZbK)_+XojHAH5m<yvR;FT5#&1i+YDL0k
zJHK6_k4uhwGlp|1i+C)!pi;J+M~ZYIf@~RtZ5D4_30jF#R;gq*y;+2KJ*J&=$it|K
zbtb^1PrI~}#=)h;zM@}|T^wm05?&D+Y8^J0MJ<m!6k`>&nL&4HC>d%SP=`*4d_$3X
zCoGaZfT(|KS__tZB7UZQKD%8OXc&KL7J+LO8+Rm0f=C!{9T#mJ7UDFE00001bW%=J
z06^y0W&i*H0b)x>L;#2d9Y_EG010qNS#tmY3ljhU3ljkVnw%H_000McNliru+XEXB
z6Cp(Rbcp}}0B%V{K~xyiU68R2z#t3+nS(tt!3~hY^vGg42FL=`EENNm0{M!6-y1;)
zQxFazvL_cK*b<_okFg0d8Hc#V=Eh`)h+yZmplehnlNZEdtgQ@hX0CNeIWEpxw!oyN
ndc~tsp9TlidOzOC&-)*|KH)3pD2ngr00000NkvXXu0mjf<KNUz

literal 0
HcmV?d00001

diff --git a/web/pgadmin/tools/grantwizard/static/img/extension.png b/web/pgadmin/tools/grantwizard/static/img/extension.png
new file mode 100644
index 0000000000000000000000000000000000000000..e3c533347883fc1dec73bcb21b32fc54e3c31732
GIT binary patch
literal 996
zcmV<A0~`E_P)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px%`cO<%MQ2zLf_6I6&9|zdd99>-@a*2m!Junh7N)|bRf|-bx|zVboSKha_w(lG
z;mc-L4XVVcQjb)R!IoN>UTm6cdTlP+)48aeY_F(+!qvh)fIq$I$ak@QM1Demb2p-y
zZNIpg%*U?Z+|0eUmR?Q)@#@yLtA3u9XFhv8mdv0tb~C!Ok)4xckc3Wsa4?*aUD?pH
z@afX-<;s|gP-<HecxWLITMn+{ye)Dq{QLL&`11Mo?Dg>A^X%E{;lk+My5`xk<JGC)
z(V?J^Pa0qr9%URUZYXJ}aH`|EXsd7=V;MkvKHbcisFznYY9*e@qsi^kq0p+c;k~!#
z!?5MPqv5li->#6?p?1NAale3n)0NuDkgb|nMQ$vEwur;)&X(P%klLe(*qwsbn0(Ze
z&%lqBg+*3S1ZY?dnutTImr{GNfpyZ2etkM9XCNqTCtr9wihVOAV;iN_u6D(TK#xcf
zTn`v!7(bOtw4`b=XCQ>Lht=`it<|(#mtSF?W<QQcm$;T>m1BZ;G0*SUx8%WGt7&4B
zV3D+tb8RIwgF3Luu#bN^7G4v==*yVRp-_@kjmeoSdoaG!zSzWv!K`UHUKC!MVuZSj
zaIAJVdpAyF9vxyClh&g+jY8GGe$u;klYuouT@*iVD?w)>Y;7EsekVC?DRR1gZ_tQZ
zyKv99ZOyY~z@}84hBK{_JHn(!l6fLVbTnhFZfMSfV$6Lul0qhGC2*Z?NP<TvhBL~q
zUB|0c#ivccpg)Ro98G#WT*`P>$a6}(W;2>aI;l}Wz+t|fIeljlScFSfreaXWZ%f2#
zMZ#t~zg?k^OOAUphI1*4cr3V}Qns8&igY1@Y#D@Y7H?b$T8UCtsbn_2S%i2!rk!)h
z!>EaMCcvamyR?(W!KK8$qF<3+9BCaAUJ)8<9X6LmEss1DV->ZTL3e2=8EP9)hfaum
zLy>wXERsEdsDEo(3zmE$ex`jsyImG&7=LOOfol~TcO*%INEmJ%7i}B|YRn1%0004W
zQchC<K<3zH00001VoOIv0Eh)0NB{r;32;bRa{vGf6951U69E94oEQKA00(qQO+^RW
z0~-xG4!&HBF8}}lR!KxbR2b7^U?3j2xS;VvLXbtwTz~>0W=QhVA)yH_FkL{}1;huk
zpaKCQKz@=7s`{i97i0@vl2TS8w1C7?R&G7y;)1Nm<<OZkC{A-h<6}A<CjbDaP8qDe
SR_4e60000<MNUMnLSTaX-^L37

literal 0
HcmV?d00001

diff --git a/web/pgadmin/tools/grantwizard/templates/grantwizard/js/grantwizard.js b/web/pgadmin/tools/grantwizard/templates/grantwizard/js/grantwizard.js
new file mode 100644
index 0000000..553a16a
--- /dev/null
+++ b/web/pgadmin/tools/grantwizard/templates/grantwizard/js/grantwizard.js
@@ -0,0 +1,863 @@
+define([
+      'jquery', 'underscore', 'underscore.string', 'alertify',
+      'pgadmin.browser', 'backbone', 'backgrid', 'pgadmin.browser.node',
+      'backgrid.select.all', 'backgrid.filter', 'backbone.paginator',
+      'backgrid.paginator', 'pgadmin.browser.server.privilege',
+      'pgadmin.browser.wizard',
+      ],
+
+function($, _, S, alertify, pgBrowser, Backbone, Backgrid, pgNode) {
+
+  if (pgBrowser.GrantWizard) {
+    return pgBrowser.GrantWizard;
+  }
+
+  /**
+    It is sub model for field "Objects". It has fields
+    for database object types such as Schemas, Views and
+    Sequence etc.
+  */
+  var DatabaseObjectModel = pgNode.Model.extend({
+    defaults: {
+      selected: false,
+      icon: 'icon-unknown',
+      name: undefined,
+      nspname: undefined,
+      proargs: undefined,
+      object_type: undefined,
+      object_id: undefined
+    },
+    idAttribute: 'object_id',
+    parse: function(res) {
+
+      // Create unique object id
+      res.object_id = res.object_type + res.nspname + '"' + res.name;
+
+      return res;
+    },
+
+    validate: function() {
+
+      /*
+      * Triggers error messages for object types "selected"
+      * if it is empty/undefined/null
+      */
+      var err = {},
+        errmsg,
+        node = this.get('objects').toJSON();
+      if (_.isEmpty(node)) {
+        err['selected'] = '{{ _("Please select any database object type") }}';
+        errmsg = errmsg || err['selected'];
+        this.errorModel.set('selected', errmsg);
+        return errmsg;
+      } else {
+        this.errorModel.unset('selected');
+      }
+      return null;
+    }
+  });
+
+  // Define privileges for various types of nodes
+  var privDict = {
+    'schema': ['a', 'r', 'w', 'd', 'D', 'x', 't', 'U', 'X'],
+    'coll-function': ['X'],
+    'coll-sequence': ['a', 'w', 'U'],
+    'coll-table':['a', 'w', 'U', 'd', 'D', 'x', 't'],
+    'coll-view': ['a', 'r', 'w', 'd', 'D', 'x', 't'],
+  };
+
+  // Define columns for the Db Object Types Grid
+  var columns = [{
+    name: "selected",
+
+    /*
+     Override render method of Backgrid.Extension.SelectRowCell
+     class. It has an issue: It doesn't mark rows checked if we move to next
+     page and then go back to previous page. but it must show.
+     so we handle this case by overriding the render method.
+    */
+    cell: Backgrid.Extension.SelectRowCell.extend({
+      render: function() {
+
+        // Use the Backform Control's render function
+        Backgrid.Extension.SelectRowCell.prototype.render.apply(this, arguments);
+
+        var col = this.column.get('name');
+        if (this.model && this.model.has(col)) {
+          if (this.model.get(col)) {
+            this.checkbox().prop("checked", true);
+            this.$el.parent().toggleClass("selected", true);
+            this.model.trigger("backgrid:selected", this.model, true);
+          }
+        }
+        return this;
+      }
+    }),
+
+    headerCell: "select-all",
+
+  },{
+    name: "object_type",
+    label: "Object Type",
+    editable: false,
+    cell: Backgrid.Cell.extend({
+      render: function() {
+
+        // Override render to add icon to Db Object column
+        Backgrid.Cell.prototype.render.apply(this, arguments);
+        this.$el.addClass(this.model.get('icon')).css({"padding-left": "22px"});
+
+        return this;
+      }
+    })
+  },{
+    name: "nspname",
+    label: "Schema",
+    cell: "string",
+    editable: false
+  },{
+    name: "name",
+    label: "Name",
+    cell: "string",
+    editable: false
+  }];
+
+  // Create an Object GrantWizard of pgBrowser class
+  pgBrowser.GrantWizard  = {
+    init: function() {
+      if (this.initialized) {
+        return;
+      }
+
+      this.initialized = true;
+
+      // Define list of nodes on which grant wizard option appears
+      var supported_nodes = [
+            'schema', 'function', 'coll-function',
+            'sequence', 'coll-sequcene',
+            'table', 'coll-table',
+            'view', 'coll-view'
+          ],
+
+          /**
+            Enable/disable grantwizard menu in tools based
+            on node selected
+            if selected node is present in supported_nodes,
+            menu will be enabled otherwise disabled.
+           */
+          menu_enabled = function(obj) {
+            if(!_.isUndefined(obj) && !_.isNull(obj))
+              return (_.indexOf(supported_nodes, obj._type) !== -1 ? true: false);
+            else
+              return false;
+          };
+
+      // Define the nodes on which the menus to be appear
+      var menus = [{
+        name: 'grant_wizard_schema', module: this,
+        applies: ['tools'], callback: 'start_grant_wizard',
+        priority: 10, label: '{{_("Grant Wizard...") }}',
+        icon: 'wcTabIcon', enable: menu_enabled
+      }];
+
+      // Add supported menus into the menus list
+      for (var idx = 0; idx < supported_nodes.length; idx++) {
+        menus.push({
+          name: 'grant_wizard_schema_context_' + supported_nodes[idx],
+          node: supported_nodes[idx], module: this,
+          applies: ['context'], callback: 'start_grant_wizard',
+          priority: 10, label: '{{_("Grant Wizard...") }}',
+          icon: 'wcTabIcon', enable: menu_enabled
+          });
+      }
+      pgAdmin.Browser.add_menus(menus);
+
+      return this;
+    },
+
+    // Callback to draw Wizard Dialog
+    start_grant_wizard: function(action, item) {
+      if (!alertify.wizardDialog) {
+        alertify.dialog('wizardDialog', function factory() {
+          return {
+            main:function(title) {
+              this.set('title', title);
+            },
+            setup:function() {
+              return {
+                options: {
+                  frameless: true,
+                  resizable: false,
+                  autoReset: false,
+                  maximizable: false,
+                  closableByDimmer: false
+                }
+              };
+            },
+            hooks:{
+              onshow: function() {
+
+                // Set dimensions for wizard
+                this.elements.dialog.style.width = '100%';
+                this.elements.dialog.style.height = '530px';
+              },
+            },
+            build:function() { },
+
+            /**
+              Returns a Collection used for rendering grid
+              and Pagination
+
+              @class {Backbone.PageableCollection}
+              @param {variable} baseUrl - from which data is fetched
+              @param {Backbone.Model} DatabaseObjectModel
+              @return {Object} coll
+            */
+            getPageableCollection: function(baseUrl){
+              var Coll = Backbone.PageableCollection.extend({
+                model: DatabaseObjectModel,
+                url: baseUrl,
+                mode: "client",
+                state: {
+                  pageSize: 100,
+                  sortKey: "object_type",
+                  order: -1
+                },
+                queryParams: {
+                  totalPages: null,
+                  totalRecords: null,
+                  sortKey: "sort"
+                }
+              });
+              return new Coll();
+            },
+
+            /**
+              Returns a Paginator Class Object which is again to be rendered
+
+              @class {Backgrid.Extension.Paginator}
+              @param {Backbone.Collection} coll - from which data is fetched
+              @return {Object} paginator
+            */
+            DbPaginator: function(coll){
+              var paginator = this.paginator = new Backgrid.Extension.Paginator({
+                collection: coll,
+                windowSize: 8
+              });
+              return paginator;
+            },
+
+            /**
+              Create new Filter which will filter the
+              rendered grid for Select Type Tabular Data
+              @param {Backbone.PageableCollection} coll
+            */
+            DbObjectFilter: function(coll){
+              var clientSideFilter = this.clientSideFilter = new Backgrid.Extension.ClientSideFilter({
+                collection: coll,
+                placeholder: _('Search by object type or name'),
+
+                // The model fields to search for matches
+                fields: ['object_type', 'name'],
+
+                // How long to wait after typing has stopped before searching can start
+                wait: 150
+              });
+              return clientSideFilter;
+            },
+
+            //Enable Disable Next button of Page 2
+            updateButtons: function(modified){
+              if(!modified)
+                $('.wizard-next').prop('disabled', true);
+              else
+                $('.wizard-next').prop('disabled', false);
+            },
+
+            /**
+              Callback called when an errorModel is set
+              with invalid value and errormsg is set into
+              status bar element and next button is disabled
+             */
+            onSessionInvalid: function(msg) {
+              $('.error_msg_div .pg-prop-status-bar').html(msg).css("display", "block");
+
+              // Enable disable Next button
+              this.updateButtons(false);
+              return true;
+            },
+
+            /**
+              Callback called when anything is set into model
+              thus hide error msg element and enable next button
+              status bar element and next button is disabled
+             */
+            onSessionValidated: function(sessHasChanged) {
+              $('.error_msg_div .pg-prop-status-bar').empty().css("display", "none");
+
+              // Enable disable Next button
+              this.updateButtons(sessHasChanged);
+            },
+
+            /*
+             Remove/Delete every object in wizard
+             on wizard close
+            */
+            releaseObjects: function(){
+
+              var self = this;
+
+              // Release/Delete every object
+              if(!_.isNull(self.dbObjectFilter)) {
+                self.dbObjectFilter.remove();
+                self.dbObjectFilter = null;
+              }
+
+              if(!_.isNull(self.clientSideFilter)) {
+                self.clientSideFilter.remove();
+                self.clientSideFilter = null;
+              }
+
+              // Remove paginator
+              if(!_.isNull(self.paginator)) {
+                self.paginator.remove();
+                self.paginator = null;
+              }
+
+              // Remove collection containing db object data
+              if(!_.isNull(self.coll)) {
+                self.coll.remove();
+                self.coll = null;
+              }
+
+            },
+
+            /**
+              Every time a wizard is opened, this function
+              is called everytime. It has Wizard Pages which
+              are rendered by the Wizard Class:
+
+              @class {pgBrowser.WizardPage} dbObjectType1 - This page
+              @extends {Backbone.Model}
+              renders a grid of Database Object Types such as
+                Schemas, Views and Sequences etc.
+
+              @class {pgBrowser.WizardPage} WizardPage2 - This page
+              @extends {Backbone.Model}
+              adds Privilege Control which provides grant privileges
+              such as "Create, Insert, Delete, Update" so on the
+              database objects selected on Wizard Pages.
+
+              @class {pgBrowser.WizardPage} WizardPage3 - This page
+              displays the generated GRANT SQL query for the Db
+              objects selected with the specific privileges added to it.
+              @extends {Backbone.Model}
+
+              @class {Backbone.Collection} WizardCollection - It is the
+              collection of wizard pages
+
+              @class {pgBrowser.Wizard} wizard - Its task is:
+              - Create a Wizard
+              - Add Buttons, Callbacks to it.
+              - Render WizardPages
+              @extends {Backbone.View}
+
+            */
+            prepare:function() {
+
+              // Remove extra container
+              if($('.ajs-content').size() > 1){
+                $('.ajs-content')[0].remove();
+              }
+
+              // Generate wizard main container
+              var wizard_html = '<div class="wizard_dlg">',
+                  wizard_parent = $('.ajs-content').empty().append(wizard_html),
+                  el = $(".ajs-content");
+
+              // Extract the data from the selected tree node
+              var t = pgBrowser.tree,
+                  i = t.selected(),
+                  d = i && i.length == 1 ? t.itemData(i) : undefined,
+                  info = pgBrowser.Node.getTreeNodeHierarchy(i),
+                  icon = d.icon;
+
+              /**
+                Generate a URL using:
+                gid, did, sid(server id), node_id(node id),
+                node_(node name), node_type(node type)
+                and pass it to collection which will fetch Object Type properties.
+               */
+              var gid = info['server-group']._id,
+                  sid = info.server._id,
+                  did = info.database._id,
+                  node_id = d._id,
+                  nspname = info.schema.label;
+                  node_type = d._type,
+                  node_label = d.label,
+
+                  // Collection url to fetch Database object types for objects field
+                  baseUrl = "{{ url_for('grantwizard.index') }}" + "grantwizard/properties/" +
+                      S('%s/%s/%s/%s/%s/%s/%s/').sprintf(
+                          encodeURI(gid), encodeURI(sid), encodeURI(did),
+                          encodeURI(node_id), encodeURI(node_label),
+                          encodeURI(node_type), encodeURI(nspname)).value();
+
+                  // Model's save url
+                  saveUrl = "{{ url_for('grantwizard.index') }}" + "grantwizard/save/" +
+                      S('%s/%s/%s/%s/%s/').sprintf(
+                          encodeURI(gid), encodeURI(sid), encodeURI(did),
+                          encodeURI(node_type), encodeURI(nspname)).value(),
+
+                  // Create instances of collection, pagination and filter
+                  coll = this.coll = this.getPageableCollection(baseUrl),
+                  paginator = this.paginator = this.DbPaginator(coll),
+                  dbObjectFilter = this.dbObjectFilter = this.DbObjectFilter(coll);
+
+              /**
+                It is the main model with schema defined
+                Every time a new wizard is opened,
+                a new model should create.
+               */
+              var GrantWizardModel = pgNode.Model.extend({
+                defaults: {
+                  objects: undefined,
+                  acl: undefined
+                },
+                schema: [
+                  {
+                    id: 'objects', label: '{{ _("Objects") }}', model: DatabaseObjectModel,
+                    type: 'collection', group: 'Objects'
+                  },
+                  {
+                    id: 'acl', label: '{{ _("Privileges") }}', node: 'role',
+                    model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend({
+                      privileges: privDict[node_type]
+                    }), uniqueCol : ['grantee', 'grantor'], editable: true,
+                    type: 'collection', group: 'Privileges', canAdd: true,
+                    canDelete: true, control: 'unique-col-collection'
+                  }
+                ],
+                urlRoot: saveUrl
+              });
+
+              /**
+                Create instance of GrantWizard Model, provide urlRoot
+                node_info object, Generate fields objects
+               */
+              var self = this,
+                  newModel = new GrantWizardModel({}, { node_info: info }),
+                  fields = Backform.generateViewSchema(
+                     info, newModel, 'create', d._type, d
+                     );
+
+              /**
+                Fetch data from server and set into grid
+                and show/hide progress bar
+               */
+              $('.wizard-progress-bar p').show();
+
+              coll.fetch({
+                success: function(collection, data) {
+                  $('.wizard-progress-bar p').html('');
+                },
+                reset: true
+              }, this);
+
+              /**
+                Override backgrid listener "backgrid:selected" to
+                Add/Remove model to/from objects collection
+               */
+              coll.on('backgrid:selected', function(model, selected) {
+                model.set('selected', selected);
+
+                /**
+                  if a row (checkbox) is checked, add that model
+                  into collection, when unchecked remove it from
+                  model.
+                 */
+                if(selected)
+                  newModel.get('objects').add(model, { silent: true });
+                else
+                  newModel.get('objects').remove(model);
+
+                // validate model on checkbox check/uncheck
+                var msg =  model.validate.call(newModel);
+
+                /**
+                  If no object type is selected, set error msg
+                  and disable next button, else enable next button
+                 */
+                if(msg)
+                  self.onSessionInvalid.call(self, msg);
+                else
+                  self.onSessionValidated.call(self, true);
+              });
+
+              // To track changes into model, start new session
+              newModel.startNewSession();
+
+              // Add event listener for privileges control
+              newModel.on('pgadmin-session:valid', self.onSessionValidated.bind(this));
+              newModel.on('pgadmin-session:invalid', self.onSessionInvalid.bind(this));
+
+              //////////////////////////////////////////////////////////////////////
+              //                                                                  //
+              //            Wizard Page for Db Object Type                        //
+              //                                                                  //
+              //////////////////////////////////////////////////////////////////////
+
+              /**
+                Create wizard page. It renders a grid of
+                Database Object Types such as
+                Schemas, Views and Sequences etc.
+                Set default values
+               */
+              var dbObjectTypePage = self.dbObjectTypePage = new pgBrowser.WizardPage({
+                id: 1,
+                page_title: 'Object Selection (step 1 of 3)',
+                disable_prev: true,
+                disable_next: true,
+                show_status_bar: _('Please select objects from the below list.'),
+                show_progress_bar: _('Please wait while fetching records...'),
+                model: newModel,
+                view: new (function() {
+
+                  // Set page Instance
+                  var pageView = this;
+
+                  _.extend(pageView, {
+
+                    // Remove grid if it is before render
+                    cleanup: function() {
+                      if (this.grid) {
+                        this.grid.remove();
+                        delete this.grid;
+                        this.grid = null;
+                      }
+
+                      // Remove grid element if exists
+                      if (this.el) {
+                        $(this.el).remove();
+                        delete this.el;
+                      }
+                    },
+
+                    // Delete grid before render
+                    grid: null,
+
+                    // Render the Page1
+                    render: function() {
+
+                      // Create a grid container
+                      var gridBody =
+                            $('<div><div id="grid_filter"></div></div>');
+
+                      // Remove grid if exits before render
+                      if (this.grid) {
+                        this.cleanup();
+                      }
+
+                      // Initialize a new Grid instance
+                      this.grid = new Backgrid.Grid({
+                        columns: _.clone(columns),
+                        collection: coll,
+                        className: "backgrid table-bordered object_type_table"
+                        });
+
+                      // Render selection Type grid and paginator
+                      gridBody.append(
+                        this.grid.render().$el
+                        ).append(self.paginator.render().el);
+
+                      // Render Search Filter
+                      gridBody.prepend(
+                        self.clientSideFilter.render().el);
+
+                      // Assign gridBody content to page element
+                      this.el = gridBody;
+
+                      /**
+                        Fetch selected models from collection and
+                        make rows checked in grid
+                       */
+                      newModel.get('objects').each(function(m) {
+                        var model = coll.get(m.get('object_id'));
+                        if (model) {
+                          coll.trigger('backgrid:selected', model, true);
+                        }
+                      });
+
+                      // Refresh grid to re render rows.
+                      coll.trigger('backgrid:refresh');
+
+                      return this;
+                    }
+                  });
+                }),
+
+                beforeNext: function(obj){
+                  var self = this,
+                      selectedRows = self.view.grid.getSelectedModels();
+                      obj.options.disable_next = true;
+
+                  /**
+                    Enable/Disable next button of page 2 if objects
+                    are present in model
+                   */
+                  if(newModel.get('acl').length > 0)
+                    obj.collection.at(1).set('disable_next', false);
+
+                  // Clean the view
+                  if (self.view) {
+                    self.view.cleanup();
+                    delete self.view;
+                    self.view = null;
+                  }
+                  return true;
+                },
+
+              });
+
+              //////////////////////////////////////////////////////////////////////
+              //                                                                  //
+              //            Wizard Page for Privilege Control                     //
+              //                                                                  //
+              //////////////////////////////////////////////////////////////////////
+
+              /**
+                Create Field Object which has properties like
+                node_data, node_info which is required for rendering
+                Privilege control
+               */
+              var privilegesField = new Backform.Field(fields[1].fields[0]);
+
+              // Wizard for Privelege control
+              var privilegePage = self.privilegePage = new pgBrowser.WizardPage({
+                id: 2,
+                page_title: _('Privileges Selection (step 2 of 3)'),
+                show_status_bar: _('Please select privileges for the selected objects.'),
+                disable_next: true,
+                model: newModel,
+
+                // Create a view function object
+                view: new (function() {
+                  var pageView = this;
+                    _.extend(pageView, {
+
+                      // Render Privelege control to generate its html markup
+                      render: function() {
+                        this.privControl = new (privilegesField.get('control')) ({
+                          field: privilegesField,
+                          model: newModel
+                        });
+                        return {el: this.privControl.render().$el};
+                      },
+
+                      // Remove the privilege control
+                      cleanup: function() {
+                        if (this.privControl) {
+                          this.privControl.remove();
+                          delete this.privControl;
+                          this.privControl = null;
+                        }
+                      }
+                    });
+                }),
+
+                beforePrev: function(wizardObj) {
+
+                  // Remove the privilege control
+                  if (this.view) {
+                    this.view.cleanup();
+                    delete this.view;
+                    this.view = null;
+                  }
+
+                  /**
+                    Enable/Disable next button of page 1 if objects
+                    are present in model
+                   */
+                  var objectsModel = newModel.get('objects');
+
+                  if(!_.isUndefined(objectsModel) && !_.isEmpty(objectsModel) &&
+                      objectsModel.length > 0) {
+                    wizardObj.collection.at(0).set('disable_next', false);
+
+                    // Don't show progress bar
+                    wizardObj.collection.at(0).set('show_progress_bar', '');
+                  }
+
+                  /**
+                    We're re-rendering the controls as they are deleted
+                    before heading to next page
+                    Refresh Backgrid to re-render the elements selected
+                    re-render paginator
+                    re-render Filter
+                   */
+                  newModel.trigger("backgrid:refresh", newModel, false);
+                  self.paginator.render();
+                  self.clientSideFilter.render();
+                  return true;
+                },
+
+                beforeNext: function() { return true; },
+
+                onNext: function(obj){
+
+                  // Remove the privilege control
+                  if (this.view) {
+                    this.view.cleanup();
+                    delete this.view;
+                    this.view = null;
+                  }
+
+                  // Enable finish button on Wizard Page 3
+                  self.wizard.options.disable_finish = false;
+
+                  // Triggers to get SQL queries data to render into the
+                  // Wiard SQL Tab Control
+                  this.model.trigger('pgadmin-wizard:nextpage:sql',
+                      {'wizard_type': 'grantwizard', 'node_type': node_type });
+                }
+              });
+
+
+              //////////////////////////////////////////////////////////////////////
+              //                                                                  //
+              //            Wizard Page for SQL Tab                               //
+              //                                                                  //
+              //////////////////////////////////////////////////////////////////////
+
+              //Create SqlField Object
+              var sqlField = new Backform.Field(
+                  {
+                    id: 'sqltab',
+                    label: 'Sql Tab',
+                    field: fields,
+                    control: Backform.SqlTabControl
+                  }),
+
+                /**
+                  Create sqlField view instance
+                  to render it into wizard page
+                 */
+                sqlControl = new (sqlField.get('control'))({
+                  field: sqlField,
+                  model: newModel
+                });
+
+              // Wizard for SQL tab control
+              var reviewSQLPage = self.reviewSQLPage = new pgBrowser.WizardPage({
+                id: 3,
+                page_title: _('Final (Review Selection) (step 3 of 3)'),
+                show_status_bar: _('Following query will be executed on the database'
+                              +' server for the selected objects, and privileges.'
+                              +' Please click on Finish to complete the process.'),
+
+                view: new(function() {
+
+                  // Render SqlTab control to generate its html markup
+                  var sqlTabHtml = sqlControl.render().$el;
+                  this.render = function() {
+                      return {el: sqlTabHtml};
+                    };
+                }),
+
+                beforePrev: function(wizardObj) {
+
+                  /**
+                    Enable next button on page 2
+                    if privilege model is not empty
+                    else disable next button
+                    */
+                  var aclModel = newModel.get('acl');
+
+                  if(!_.isUndefined(aclModel) && !_.isEmpty(aclModel) &&
+                      aclModel.length > 0)
+                    wizardObj.collection.at(1).set('disable_next', false);
+                  else
+                    wizardObj.collection.at(1).set('disable_next', true);
+
+                  return true;
+                },
+              });
+
+
+              // Create Wizard Collection of Wizard Pages
+              var WizardCollection = Backbone.Collection.extend({
+                model: pgBrowser.WizardPage
+              });
+
+              // It holds all the wizard pages to be rendered
+              this.wizardCollection = new WizardCollection(
+                [dbObjectTypePage, privilegePage, reviewSQLPage]
+              );
+
+              /**
+                Create wizard which has following operations:
+                - renders wizard pages
+                - defines the first page to render in wizard
+                - Save the model on finishbutton
+                - Remove wizard on cancel button
+                */
+              var wizard = self.wizard = new (pgBrowser.Wizard.extend({
+                options: {
+                  title: 'Grant Wizard', /* Main Wizard Title */
+                  width: '',
+                  height: '',
+                  curr_page: 0,
+                  show_left_panel: false,
+                  disable_finish: true
+                },
+                onFinish: function() {
+                  var m = newModel,
+                      d = m.toJSON('GET');
+
+                  if (d && !_.isEmpty(d)) {
+                    m.save({}, {
+                      attrs: d,
+                      validate: false,
+                      cache: false,
+                      success: function(res) {
+
+                        // Release wizard objects
+                        self.close();
+                      },
+                      error: function(m, jqxhr) {
+                        alertify.pgNotifier(
+                          "error", jqxhr,
+                          S(
+                            "{{ _('Error during saving properties - %%s!') }}"
+                            ).sprintf(jqxhr.statusText).value()
+                          );
+                      }
+                    });
+                  }
+                },
+                onCancel: function() {
+
+                  // Release wizard objects
+                  self.releaseObjects();
+                  self.close();
+                }
+              })) ({
+                collection: this.wizardCollection,
+                el: el,
+                model: newModel
+              });
+
+              // Render wizard
+              wizard.render();
+            }
+          };
+        });
+      }
+
+      // Call Grant Wizard Dialog
+      alertify.wizardDialog('');
+    }
+  };
+
+  return pgBrowser.GrantWizard;
+});
diff --git a/web/pgadmin/tools/grantwizard/templates/grantwizard/sql/allowed_acl.json b/web/pgadmin/tools/grantwizard/templates/grantwizard/sql/allowed_acl.json
new file mode 100644
index 0000000..cb73d2c
--- /dev/null
+++ b/web/pgadmin/tools/grantwizard/templates/grantwizard/sql/allowed_acl.json
@@ -0,0 +1,30 @@
+{# List of allowed privileges of Schema, table sequence, function and view #}
+{#
+    Format for allowed privileges are:
+    "node_name": {
+        "type": "name",
+        "acl": [...]
+    }
+#}
+{
+    "schema": {
+        "type": "SCHEMA",
+        "acl": ["a", "r", "w", "d", "D", "x", "t", "U", "X"]
+    },
+    "coll-table": {
+        "type": "TABLE",
+        "acl": ["a", "w", "U", "d", "D", "x", "t"]
+    },
+    "coll-sequence": {
+        "type": "SEQUENCE",
+        "acl": ["a", "w", "u"]
+    },
+    "coll-function": {
+        "type": "FUNCTION",
+        "acl": ["X"]
+    },
+    "coll-view": {
+        "type": "VIEW",
+        "acl": ["a", "r", "w", "d", "D", "x", "t"]
+    }
+}
diff --git a/web/pgadmin/tools/grantwizard/templates/grantwizard/sql/grant.sql b/web/pgadmin/tools/grantwizard/templates/grantwizard/sql/grant.sql
new file mode 100644
index 0000000..02ac1a7
--- /dev/null
+++ b/web/pgadmin/tools/grantwizard/templates/grantwizard/sql/grant.sql
@@ -0,0 +1,19 @@
+{#=====Grant Permissions on Database Objects Selected====#}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% import 'macros/functions/privilege.macros' as PRIVILEGE_FUNCTION %}
+{% if data.acl %}
+{% for obj in data.objects %}
+{% for priv in data.acl %}
+{% set obj_type = obj.object_type %}
+{% if obj_type.upper() != 'FUNCTION' and obj_type.upper() != 'TRIGGER FUNCTION' %}
+{% if obj_type.upper() == 'VIEW' %} {###### Views are also kind of table ######}
+{% set obj_type = 'TABLE' %}
+{% endif %}
+{{ PRIVILEGE.SET(conn, obj_type.upper(), priv.grantee, obj.name, priv.without_grant, priv.with_grant, nspname ) }}
+{% else %}
+{###### if object_type is Function then apply function marcros ######}
+{{ PRIVILEGE_FUNCTION.SET(conn, 'FUNCTION', priv.grantee, obj.name, priv.without_grant, priv.with_grant, nspname, obj.proargs)}}
+{% endif %}
+{% endfor %}
+{% endfor %}
+{% endif %}
diff --git a/web/pgadmin/tools/grantwizard/templates/grantwizard/sql/properties.sql b/web/pgadmin/tools/grantwizard/templates/grantwizard/sql/properties.sql
new file mode 100644
index 0000000..7a33c1f
--- /dev/null
+++ b/web/pgadmin/tools/grantwizard/templates/grantwizard/sql/properties.sql
@@ -0,0 +1,65 @@
+{#===================Fetch list of Database object types(Functions, Sequence, Tables or View)===================#}
+{% if type == 'schema' or type == 'trigger_function' and node_id and node_name %}
+{% set func_type = 'Trigger Function' if type == 'trigger_function' else 'Function' %}
+SELECT
+    pr.proname AS name,
+    pg_get_function_identity_arguments(pr.oid) AS proargs,
+    '{{ func_type }}' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-function' AS icon
+FROM pg_proc pr
+    JOIN pg_type typ ON typ.oid=prorettype
+    JOIN pg_namespace typns ON typns.oid=typ.typnamespace
+    JOIN pg_language lng ON lng.oid=prolang
+    LEFT OUTER JOIN pg_description des ON (des.objoid=pr.oid AND des.classoid='pg_proc'::regclass)
+WHERE proisagg = FALSE AND pronamespace = {{ node_id }}::oid
+    AND typname {{ 'NOT' if type != 'trigger_function' else '' }} IN ('trigger', 'event_trigger')
+ORDER BY proname
+{% endif %}
+
+{% if type == 'sequence' and node_id and node_name %}
+SELECT
+    cl.relname AS name,
+    'Sequence' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-sequence' AS icon
+FROM pg_class cl
+    LEFT OUTER JOIN pg_description des ON (des.objoid=cl.oid AND des.classoid='pg_class'::regclass)
+WHERE relkind = 'S' AND relnamespace  = {{ node_id }}::oid
+ORDER BY cl.relname
+{% endif %}
+
+{% if type == 'table' and node_id and node_name %}
+SELECT rel.relname AS name,
+    'Table' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-table' AS icon
+FROM pg_class rel
+    LEFT OUTER JOIN pg_tablespace spc ON spc.oid=rel.reltablespace
+    LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass)
+    LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'
+    LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid
+    LEFT JOIN pg_type typ ON rel.reloftype=typ.oid
+WHERE rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ node_id }}::oid
+ORDER BY rel.relname
+{% endif %}
+
+{% if type == 'coll-view' and node_id and node_name %}
+SELECT
+    c.relname AS name,
+    'View' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-coll-view' AS icon
+FROM pg_class c
+    LEFT OUTER JOIN pg_tablespace spc ON spc.oid=c.reltablespace
+    LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass)
+    LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid
+WHERE ((c.relhasrules AND (EXISTS (
+    SELECT r.rulename
+    FROM pg_rewrite r
+    WHERE ((r.ev_class = c.oid)
+        AND (bpchar(r.ev_type) = '1'::bpchar)) ))
+       ) OR (c.relkind = 'v'::char))
+AND c.relnamespace = {{ node_id }}::oid
+ORDER BY c.relname
+{% endif %}


^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-03-04 13:39  Dave Page <[email protected]>
  parent: Surinder Kumar <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Dave Page @ 2016-03-04 13:39 UTC (permalink / raw)
  To: Surinder Kumar <[email protected]>; +Cc: pgadmin-hackers

Hi

On Thu, Mar 3, 2016 at 12:49 PM, Surinder Kumar
<[email protected]> wrote:
> Hi,
>
> PFA patch for Grant Wizard.
>
> Before applying grant wizard patch:
>
>         1. Apply patch for "wizard JS file" which Khushboo had shared with
> Ashesh personally.
>             I am using that patch with few changes in that. Ashesh will
> review
>             and commit that patch.
>
>         2. Apply patches of "Sequence" and "Functions" macros.
>
>
> Please review the patch and Let me know for any comments.

Initial feedback:

- Why does this add specific knowledge of the Grant Wizard to the
Browser module? It should be a plugin that loads itself at runtime.
Any changes to the browser to support that should be entirely generic
and usable by any module.

- The comment above also applies to the core templates. CSS should be
advertised by the plugin, and the browser can add it into the rendered
output when the module is dynamically loaded.

- +""" Implements Grant Wizard""" - why the leading space? Please
review all comments and code for such small details.

- The Python code to detect and alias various Python 2/3 classes
should be centralised, as I've seen it in at least one other module.

- In other module names, we've separated multiple words with a hypen.
e.g. server-groups. s/grantwizard/grant-wizard/g

- The published routes for this module are all under

- "GET /browser/static/js/wizard.js HTTP/1.1" 404 -

- For consistency, when naming CSS/JS files that are core to a module,
please use the module name, e.g.
/web/pgadmin/tools/grant-wizard/static/css/grant-wizard.css

Thanks.

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers



^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-03-04 14:36  Dave Page <[email protected]>
  parent: Dave Page <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Dave Page @ 2016-03-04 14:36 UTC (permalink / raw)
  To: Surinder Kumar <[email protected]>; +Cc: pgadmin-hackers

A couple of corrections below <sigh>

On Fri, Mar 4, 2016 at 1:39 PM, Dave Page <[email protected]> wrote:
> Hi
>
> On Thu, Mar 3, 2016 at 12:49 PM, Surinder Kumar
> <[email protected]> wrote:
>> Hi,
>>
>> PFA patch for Grant Wizard.
>>
>> Before applying grant wizard patch:
>>
>>         1. Apply patch for "wizard JS file" which Khushboo had shared with
>> Ashesh personally.
>>             I am using that patch with few changes in that. Ashesh will
>> review
>>             and commit that patch.
>>
>>         2. Apply patches of "Sequence" and "Functions" macros.
>>
>>
>> Please review the patch and Let me know for any comments.
>
> Initial feedback:
>
> - Why does this add specific knowledge of the Grant Wizard to the
> Browser module? It should be a plugin that loads itself at runtime.
> Any changes to the browser to support that should be entirely generic
> and usable by any module.
>
> - The comment above also applies to the core templates. CSS should be
> advertised by the plugin, and the browser can add it into the rendered
> output when the module is dynamically loaded.
>
> - +""" Implements Grant Wizard""" - why the leading space? Please
> review all comments and code for such small details.
>
> - The Python code to detect and alias various Python 2/3 classes
> should be centralised, as I've seen it in at least one other module.
>
> - In other module names, we've separated multiple words with a hypen.
> e.g. server-groups. s/grantwizard/grant-wizard/g

That should be an underscore, not a hyphen:

s/grantwizard/grant_wizard/g

>
> - The published routes for this module are all under
>
> - "GET /browser/static/js/wizard.js HTTP/1.1" 404 -
>
> - For consistency, when naming CSS/JS files that are core to a module,
> please use the module name, e.g.
> /web/pgadmin/tools/grant-wizard/static/css/grant-wizard.css

/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers



^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-03-08 05:14  Surinder Kumar <[email protected]>
  parent: Dave Page <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Surinder Kumar @ 2016-03-08 05:14 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: pgadmin-hackers

Hi

PFA patch with following changes:

1. Extends SqlTabControl to write a new function 'onWizardChange',
instead of writing it in backform.pgadmin.js file.

Also, validations in privilegeControl are not working properly, when
validations gets fixed, I will send an updated patch.

This is complete patch, as Khusboo's patch also merged in attached patch and
patches of "Sequence" and "Functions" macros are already committed.


On Fri, Mar 4, 2016 at 8:06 PM, Dave Page <[email protected]> wrote:

> A couple of corrections below <sigh>
>
> On Fri, Mar 4, 2016 at 1:39 PM, Dave Page <[email protected]> wrote:
> > Hi
> >
> > On Thu, Mar 3, 2016 at 12:49 PM, Surinder Kumar
> > <[email protected]> wrote:
> >> Hi,
> >>
> >> PFA patch for Grant Wizard.
> >>
> >> Before applying grant wizard patch:
> >>
> >>         1. Apply patch for "wizard JS file" which Khushboo had shared
> with
> >> Ashesh personally.
> >>             I am using that patch with few changes in that. Ashesh will
> >> review
> >>             and commit that patch.
> >>
> >>         2. Apply patches of "Sequence" and "Functions" macros.
> >>
> >>
> >> Please review the patch and Let me know for any comments.
> >
> > Initial feedback:
> >
> > - Why does this add specific knowledge of the Grant Wizard to the
> > Browser module? It should be a plugin that loads itself at runtime.
> > Any changes to the browser to support that should be entirely generic
> > and usable by any module.
>
Fixed.

> >
> > - The comment above also applies to the core templates. CSS should be
> > advertised by the plugin, and the browser can add it into the rendered
> > output when the module is dynamically loaded.
>
Fixed.

> >
> > - +""" Implements Grant Wizard""" - why the leading space? Please
> > review all comments and code for such small details.
>
Done

> >
> > - The Python code to detect and alias various Python 2/3 classes
> > should be centralised, as I've seen it in at least one other module.
> >
>
Removed it, as far as it is not required.

> > - In other module names, we've separated multiple words with a hypen.
> > e.g. server-groups. s/grantwizard/grant-wizard/g
>
> That should be an underscore, not a hyphen:
>
> s/grantwizard/grant_wizard/g


> >
> > - The published routes for this module are all under
> >
> > - "GET /browser/static/js/wizard.js HTTP/1.1" 404 -
> >
> > - For consistency, when naming CSS/JS files that are core to a module,
> > please use the module name, e.g.
> > /web/pgadmin/tools/grant-wizard/static/css/grant-wizard.css
>
> /web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
>
Done

>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [application/octet-stream] grant_wizard_v2.patch (61.5K, 3-grant_wizard_v2.patch)
  download | inline diff:
diff --git a/web/pgadmin/browser/static/css/wizard.css b/web/pgadmin/browser/static/css/wizard.css
new file mode 100644
index 0000000..9934aac
--- /dev/null
+++ b/web/pgadmin/browser/static/css/wizard.css
@@ -0,0 +1,89 @@
+
+/** CSS for Wizard **/
+.wizard_dlg {
+  height: 300px;
+  padding: 0 10px;
+}
+
+.ajs-content {
+  padding-top: 0px !important;
+}
+
+/* Wizard Header CSS */
+.wizard-header {
+  background: #428bca;
+  padding-left: 15px;
+  padding-bottom: 7px;
+  color: #fff;
+  font-size: 18px;
+  height: 76px;
+  line-height: 76px;
+}
+
+.wizard-header h3 {
+  font-size: 18px;
+  display: inline-block;
+}
+
+.wizard-content {
+  position: relative;
+  overflow-y: auto;
+  float: left;
+  padding: 0;
+  min-height: 390px;
+  max-height: 390px;
+}
+
+/* Wizard Footer CSS */
+.footer {
+  position: absolute;
+  border-top: 1px solid #ccc;
+  bottom: 0px;
+  height: 71px;
+  right: 0px;
+  padding-top: 22px;
+}
+
+/* Wizard Button CSS */
+.wizard-buttons {
+  float: right;
+}
+
+.wizard-buttons button {
+  float: left;
+  padding: 7px 15.2px;
+  font-size: 14px;
+  margin-right: 5px;
+}
+
+.wizard-finish {
+  margin-right: 0 !important;
+}
+
+/* Wizard Status bar CSS */
+.wizard-description {
+  height: auto;
+  padding: 20px 0px;
+}
+
+/* Error message css */
+.error_msg_div {
+  display: block;
+  bottom: 20px;
+}
+
+.error_msg_div p {
+  background: #fff;
+  color: #b92c28;
+}
+
+/* In wizard select2 dropdown doesn't
+ * popup because z-index of alertify
+ * wizard is greater than the z-index
+ * of select2 dropdown. To make select2
+ * visible, set z-index of select2
+ * higher value than wizard's
+ */
+.select2-container--open {
+  z-index: 10000;
+}
diff --git a/web/pgadmin/browser/static/js/wizard.js b/web/pgadmin/browser/static/js/wizard.js
new file mode 100644
index 0000000..78458e4
--- /dev/null
+++ b/web/pgadmin/browser/static/js/wizard.js
@@ -0,0 +1,222 @@
+define(
+    ['underscore', 'backbone', 'pgadmin', 'pgadmin.browser'],
+function(_, Backbone, pgAdmin, pgBrowser) {
+
+  pgBrowser = pgBrowser || pgAdmin.Browser || {};
+
+  /* Wizard individual Page Model */
+  var WizardPage = pgBrowser.WizardPage = Backbone.Model.extend({
+    defaults: {
+      id: undefined, /* Id */
+      page_title: undefined, /* Page Title */
+      view: undefined, /* A Backbone View */
+      html: undefined, /* HTML tags to be rendered */
+      image: undefined, /* Left hand side image */
+      disable_prev: false, /*  Previous Button Flag */
+      disable_next: false, /*  Next Button Flag */
+      disable_cancel: false, /* Cancel Button Flag */
+      show_progress_bar: '',
+      /* Callback for OnLoad */
+      onLoad: function() {
+        return true;
+      },
+      /* Callback for before Next */
+      beforeNext: function() {
+        return true;
+      },
+      onNext: function(){},
+      onBefore: function() {},
+      /* Callback for before Previous */
+      beforePrev: function() {
+        return true;
+      }
+    }
+  });
+
+  var Wizard = pgBrowser.Wizard = Backbone.View.extend({
+    options: {
+      title: 'Wizard', /* Main Wizard Title */
+      image: 'left_panel.png', /* TODO:: We can use default image here */
+      curr_page: 0, /* Current Page to Load */
+      disable_next: false,
+      disable_prev: false,
+      disable_finish: false,
+      disable_cancel: false,
+      height: 400,
+      width: 650,
+      show_left_panel: true
+    },
+    tmpl: _.template(
+       "    <div class='pgadmin-wizard' style='height: <%= this.options.height %>px; width: <%= this.options.width %>px'>"
+       + "      <div class='wizard-header wizard-badge'>"
+       + "        <div class='row'>"
+       + "          <div class='col-sm-9'>"
+       + "              <h3><span id='main-title'><%= this.options.title %></span> - <span id='step-title'><%= page_title %></span></h3>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "      <div class='wizard-content col-sm-12'>"
+       + "        <% if(this.options.show_left_panel) { %>"
+       + "          <div class='col-sm-3 wizard-left-panel'>"
+       + "              <img src='<%= this.options.image %>'></div>"
+       + "        <% } %>"
+       + "        <div class='col-sm-<% if(this.options.show_left_panel){ %>6<% } else { %>12<% } %> wizard-right-panel'>"
+       + "          <% if( typeof show_description !=  'undefined'){ %>"
+       + "            <div class='wizard-description'>"
+       + "              <%= show_description %>"
+       + "            </div>"
+       + "          <% } %>"
+       + "          <div class='wizard-right-panel_content'>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "      <div class='col-sm-12 error_msg_div'>"
+       + "       <p></p>"
+       + "      </div>"
+       + "      <div class='footer col-sm-12'>"
+       + "        <div class='row'>"
+       + "          <div class='col-sm-4 wizard-progress-bar'>"
+       + "             <p><% if(show_progress_bar){ %><%= show_progress_bar %><% } %></p>"
+       + "          </div>"
+       + "          <div class='col-sm-8'>"
+       + "            <div class='wizard-buttons'>"
+       + "              <button class='btn-sm btn-primary wizard-back' <%=this.options.disable_prev ? 'disabled' : ''%>><i class='fa fa-backward'></i> Back</button>"
+       + "              <button class='btn-sm btn-primary wizard-next' <%=this.options.disable_next ? 'disabled' : ''%>>Next <i class='fa fa-forward'></i></button>"
+       + "              <button class='btn-sm btn-danger wizard-cancel' <%=this.options.disable_cancel ? 'disabled' : ''%>><i class='fa fa-lg fa-close'></i>Cancel</button>"
+       + "              <button class='btn-sm btn-primary wizard-finish' <%=this.options.disable_finish ? 'disabled' : ''%>>Finish</button>"
+       + "            </div>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "    </div>"),
+    events: {
+      "click button.wizard-next" : "nextPage",
+      "click button.wizard-back" : "prevPage",
+      "click button.wizard-cancel" : "onCancel",
+      "click button.wizard-finish" : "finishWizard",
+    },
+    initialize: function(options) {
+      this.options = _.extend({}, this.options, options.options);
+      this.currPage = this.collection.at(this.options.curr_page).toJSON();
+    },
+    render: function() {
+      var data = this.currPage;
+
+      /* Check Status of the buttons */
+      this.options.disable_next = (this.options.disable_next ? true : this.evalASFunc(this.currPage.disable_next));
+      this.options.disable_prev = (this.options.disable_prev ? true : this.evalASFunc(this.currPage.disable_prev));
+      this.options.disable_cancel = (this.currPage.canCancel ? true : this.evalASFunc(this.currPage.disable_cancel));
+
+      that = this;
+
+      /* HTML Content */
+      if (data.html) { data.content = data.html; }
+      /* Backbone View */
+      else if (data.view) { data.content = data.view.render().el;}
+
+      $(this.el).html(this.tmpl(data));
+      $(this.el).find(".wizard-right-panel_content").html(data.content);
+
+      /* OnLoad Callback */
+      this.onLoad();
+
+      return this;
+    },
+    nextPage: function() {
+      this.options.curr_page.el = this.$el;
+      if (!this.beforeNext()) { return false; }
+
+      page_id = this.onNext();
+
+      if (page_id ) {
+        this.currPage = this.collection.get(page_id).toJSON();
+        this.options.curr_page = this.collection.indexOf(this.collection.get(page_id));
+      }
+      else if (this.options.curr_page < (this.collection.length-1)) {
+        this.options.curr_page = this.options.curr_page + 1;
+        this.currPage = this.collection.at(this.options.curr_page).toJSON();
+      }
+
+      this.enableDisableNext();
+      this.enableDisablePrev();
+
+      return this.render();
+    },
+    prevPage: function() {
+      if (!this.beforePrev()) { return false; }
+
+      page_id = this.onPrev();
+
+      if (page_id){
+        this.currPage = this.collection.get(page_id).toJSON();
+        this.options.curr_page = this.collection.indexOf(this.collection.get(page_id));
+      }
+      else if (this.options.curr_page > 0) {
+        this.options.curr_page = this.options.curr_page - 1;
+        this.currPage = this.collection.at(this.options.curr_page).toJSON();
+      }
+
+      this.enableDisableNext();
+      this.enableDisablePrev();
+
+      return this.render();
+    },
+    finishWizard: function() {
+      this.onFinish();
+      return true;
+    },
+    enableDisableNext: function(disable) {
+        if (typeof(disable) != 'undefined') {
+          this.options.disable_next = disable;
+        }
+        else if (this.options.curr_page >= (this.collection.length-1)) {
+          this.options.disable_next = true;
+        }
+        else {
+          this.options.disable_next = false;
+        }
+    },
+    enableDisablePrev: function(disable) {
+        if (typeof(disable) != 'undefined') {
+          this.options.disable_prev = disable;
+        }
+        else if (this.options.curr_page <= 0) {
+          this.options.disable_prev = true;
+        }
+        else {
+          this.options.disable_prev = false;
+        }
+    },
+    beforeNext: function(){
+      return this.evalASFunc(this.currPage.beforeNext);
+    },
+    beforePrev: function(){
+      return this.evalASFunc(this.currPage.beforePrev);
+    },
+    onPrev: function(){
+      return this.evalASFunc(this.currPage.onPrev);
+    },
+    onNext: function(){
+      return this.evalASFunc(this.currPage.onNext);
+    },
+    onLoad: function() {
+      return this.evalASFunc(this.currPage.onLoad);
+    },
+    onFinish: function() {
+      return true;
+    },
+    onCancel: function() {
+      this.$el.remove();
+      return true;
+    },
+    evalASFunc: function(func, ctx) {
+      var self = this;
+      ctx = ctx || self.currPage;
+
+      return (_.isFunction(func) ? func.apply(ctx, [self]) : func);
+    }
+  });
+
+  return pgBrowser;
+
+});
diff --git a/web/pgadmin/tools/grant_wizard/__init__.py b/web/pgadmin/tools/grant_wizard/__init__.py
new file mode 100644
index 0000000..24c097a
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/__init__.py
@@ -0,0 +1,291 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Implements Grant Wizard"""
+
+import json
+from flask import render_template, request, current_app
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_response as ajax_response, \
+    make_json_response, internal_server_error
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from pgadmin.browser.server_groups.servers.utils import parse_priv_to_db
+from pgadmin.utils import PgAdminModule
+from flask import Response, url_for
+from flask.ext.security import login_required
+from urllib import unquote
+
+# set template path for sql scripts
+template_path = 'grant_wizard/sql'
+MODULE_NAME = 'grant_wizard'
+
+
+class GrantWizardModule(PgAdminModule):
+    """
+    class GrantWizardModule(Object):
+
+        It is a wizard which inherits PgAdminModule
+        class and define methods to load its own
+        javascript file.
+    """
+    def get_own_stylesheets(self):
+        """
+        Returns:
+            list: the stylesheets used by this module.
+        """
+        stylesheets = []
+        for (endpoint, filename) in [
+                ('browser.static', 'css/wizard.css'),
+                ('grant_wizard.static', 'css/grant_wizard.css'),
+                ]:
+            stylesheets.append(url_for(endpoint, filename=filename))
+        return stylesheets
+
+    def get_own_javascripts(self):
+        """"
+        Returns:
+            list: js files used by this module
+        """
+        scripts = []
+        scripts.append({
+            'name': 'pgadmin.tools.grant_wizard',
+            'path': url_for('grant_wizard.index') + 'grant_wizard',
+            'when': None
+        })
+        scripts.append({
+            'name': 'pgadmin.browser.wizard',
+            'path': url_for('browser.static', filename='js/wizard'),
+            'when': None
+        })
+        return scripts
+
+# Create blueprint for GrantWizardModule class
+blueprint = GrantWizardModule(
+    MODULE_NAME, __name__, static_url_path='',
+    url_prefix='/wizard')
+
+
[email protected]("/")
+@login_required
+def index():
+    pass
+
+
[email protected]("/grant_wizard.js")
+@login_required
+def script():
+    """render own javascript"""
+    return Response(response=render_template(
+                "grant_wizard/js/grant_wizard.js", _=gettext),
+                status=200,
+                mimetype="application/javascript")
+
+
[email protected](
+    '/grant_wizard/properties/<int:gid>/<int:sid>/<int:did>'
+    '/<int:node_id>/<node_name>/<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+@login_required
+def properties(gid, sid, did, node_id, node_name, node_type, nspname):
+    """It fetches the properties of object types
+       and render into selection page of wizard
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+    nspname = unquote(nspname)
+
+    res_data = []
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    # Fetch functions against schema
+    if node_type in ['schema']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type=node_type, nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch trigger functions
+    if node_type in ['schema', 'trigger_function']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='trigger_function', nspname=nspname)
+        status, res = conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch Sequences against schema
+    if node_type in ['schema', 'sequence']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='sequence', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+        res_data.extend(res['rows'])
+
+    # Fetch Tables against schema
+    if node_type in ['schema', 'table']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='table', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch Views against schema
+    if node_type in ['schema', 'coll-view']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='coll-view', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    return ajax_response(
+            response=res_data,
+            status=200
+            )
+
+
[email protected](
+    '/grant_wizard/msql/<int:gid>/<int:sid>/<int:did>/<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+def msql(gid, sid, did, node_type, nspname):
+    """
+    This function will return modified SQL
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+    nspname = unquote(nspname)
+
+    data = {}
+    for k, v in request.args.items():
+        try:
+            data[k] = json.loads(v)
+        except ValueError:
+            data[k] = v
+
+    # Form db connection
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    acls = []
+    try:
+        acls = render_template(
+            "/".join([template_path, 'allowed_acl.json'])
+            )
+        acls = json.loads(acls)
+    except Exception as e:
+        current_app.logger.exception(e)
+
+    try:
+
+        # Parse privileges
+        if 'acl' in data:
+            data['acl'] = parse_priv_to_db(data['acl'], acls[node_type]['acl'])
+
+        # Pass database objects and get SQL for privileges
+        SQL = render_template("/".join(
+            [template_path, 'grant.sql']),
+            data=data, nspname=nspname, conn=conn)
+
+        res = {'data': SQL}
+
+        return ajax_response(
+            response=res,
+            status=200
+            )
+
+    except Exception as e:
+        return make_json_response(
+            status=410,
+            success=0,
+            errormsg=e.message
+        )
+
+
[email protected](
+    '/grant_wizard/save/<int:gid>/<int:sid>/<int:did>/'
+    '<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+def save(gid, sid, did, node_type, nspname):
+    """
+    This function will apply the privileges to the selected
+    Database Objects
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+    nspname = unquote(nspname)
+
+    data = request.form if request.form else json.loads(request.data.decode())
+
+    # Form db connection and we use conn to execute sql
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    acls = []
+    try:
+        acls = render_template(
+            "/".join([template_path, 'allowed_acl.json'])
+            )
+        acls = json.loads(acls)
+    except Exception as e:
+        current_app.logger.exception(e)
+
+    try:
+
+        # Parse privileges
+        if 'acl' in data:
+            data['acl'] = parse_priv_to_db(
+                data['acl'],
+                acls[node_type]['acl']
+                )
+
+        # Pass database objects and get SQL for privileges
+        SQL = render_template("/".join(
+            [template_path, 'grant.sql']),
+            data=data, nspname=nspname, conn=conn)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        return make_json_response(
+            success=1,
+            info="Privileges Applied"
+        )
+
+    except Exception as e:
+        return internal_server_error(errormsg=e.message)
diff --git a/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
new file mode 100644
index 0000000..4403c68
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
@@ -0,0 +1,66 @@
+/** Grant Wizard CSS **/
+
+/**
+  CSS to make db object type table
+  fixed so that tbody content may
+  scroll
+ */
+.object_type_table thead tr {
+  position: relative;
+  display: block;
+}
+
+.object_type_table tbody {
+  display: block;
+  overflow-y: scroll;
+  height: 180px;
+}
+
+.object_type_table tbody tr td:nth-child(1),
+.object_type_table thead tr th:nth-child(1) {
+  width: 28px;
+  min-width: 28px;
+}
+
+.object_type_table tbody tr td:nth-child(2),
+.object_type_table thead tr th:nth-child(2) {
+  width: 185px;
+  min-width: 185px;
+}
+
+.object_type_table tbody tr td:nth-child(3),
+.object_type_table thead tr th:nth-child(3) {
+  width: 110px;
+  min-width: 110px;
+}
+
+.object_type_table tbody tr td:nth-child(4),
+.object_type_table thead tr th:nth-child(4) {
+  width: 244px;
+}
+
+/** Override Backgrid filter CSS **/
+.backgrid-filter.form-search {
+  float: left;
+  margin: 0px 5px 5px 0;
+}
+
+/** Custom styling for Codemirror field **/
+.wizard-right-panel_content {
+  border: 1px solide #ccc;
+}
+
+.wizard-right-panel_content .CodeMirror {
+  border: 1px solid #ccc;
+  height: 285px !important;
+  min-height: 285px !important;
+}
+
+.wizard-right-panel_content .CodeMirror-linenumber {
+  background: #F7F7F7;
+  border-right: 1px solid #DDDDDD;
+}
+
+.wizard-right-panel_content .CodeMirror-gutters {
+  min-height: 285px !important;
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
new file mode 100644
index 0000000..90287b2
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
@@ -0,0 +1,967 @@
+define([
+      'jquery', 'underscore', 'underscore.string', 'alertify',
+      'pgadmin.browser', 'backbone', 'backgrid', 'pgadmin.browser.node',
+      'backgrid.select.all', 'backgrid.filter', 'backbone.paginator',
+      'backgrid.paginator', 'pgadmin.browser.server.privilege',
+      'pgadmin.browser.wizard',
+      ],
+
+function($, _, S, alertify, pgBrowser, Backbone, Backgrid, pgNode) {
+
+  if (pgBrowser.GrantWizard) {
+    return pgBrowser.GrantWizard;
+  }
+
+  /**
+    It is sub model for field "Objects". It has fields
+    for database object types such as Schemas, Views and
+    Sequence etc.
+  */
+  var DatabaseObjectModel = pgNode.Model.extend({
+    defaults: {
+      selected: false,
+      icon: 'icon-unknown',
+      name: undefined,
+      nspname: undefined,
+      proargs: undefined,
+      object_type: undefined,
+      object_id: undefined
+    },
+    idAttribute: 'object_id',
+    parse: function(res) {
+
+      // Create unique object id
+      res.object_id = res.object_type + res.nspname + '"' + res.name;
+
+      return res;
+    },
+
+    validate: function() {
+
+      /*
+      * Triggers error messages for object types "selected"
+      * if it is empty/undefined/null
+      */
+      var err = {},
+        errmsg,
+        node = this.get('objects').toJSON();
+      if (_.isEmpty(node)) {
+        err['selected'] = '{{ _("Please select any database object type") }}';
+        errmsg = errmsg || err['selected'];
+        this.errorModel.set('selected', errmsg);
+        return errmsg;
+      } else {
+        this.errorModel.unset('selected');
+      }
+      return null;
+    }
+  });
+
+  // Define privileges for various types of nodes
+  var privDict = {
+    'schema': ['a', 'r', 'w', 'd', 'D', 'x', 't', 'U', 'X'],
+    'coll-function': ['X'],
+    'coll-sequence': ['a', 'w', 'U'],
+    'coll-table':['a', 'w', 'U', 'd', 'D', 'x', 't'],
+    'coll-view': ['a', 'r', 'w', 'd', 'D', 'x', 't'],
+  };
+
+  // Define columns for the Db Object Types Grid
+  var columns = [{
+    name: "selected",
+
+    /*
+     Override render method of Backgrid.Extension.SelectRowCell
+     class. It has an issue: It doesn't mark rows checked if we move to next
+     page and then go back to previous page. but it must show.
+     so we handle this case by overriding the render method.
+    */
+    cell: Backgrid.Extension.SelectRowCell.extend({
+      render: function() {
+
+        // Use the Backform Control's render function
+        Backgrid.Extension.SelectRowCell.prototype.render.apply(this, arguments);
+
+        var col = this.column.get('name');
+        if (this.model && this.model.has(col)) {
+          if (this.model.get(col)) {
+            this.checkbox().prop("checked", true);
+            this.$el.parent().toggleClass("selected", true);
+            this.model.trigger("backgrid:selected", this.model, true);
+          }
+        }
+        return this;
+      }
+    }),
+
+    headerCell: "select-all",
+
+  },{
+    name: "object_type",
+    label: "Object Type",
+    editable: false,
+    cell: Backgrid.Cell.extend({
+      render: function() {
+
+        // Override render to add icon to Db Object column
+        Backgrid.Cell.prototype.render.apply(this, arguments);
+        this.$el.addClass(this.model.get('icon')).css({"padding-left": "22px"});
+
+        return this;
+      }
+    })
+  },{
+    name: "nspname",
+    label: "Schema",
+    cell: "string",
+    editable: false
+  },{
+    name: "name",
+    label: "Name",
+    cell: "string",
+    editable: false
+  }];
+
+  // Create an Object GrantWizard of pgBrowser class
+  pgBrowser.GrantWizard  = {
+    init: function() {
+      if (this.initialized) {
+        return;
+      }
+
+      this.initialized = true;
+
+      // Define list of nodes on which grant wizard option appears
+      var supported_nodes = [
+            'schema', 'coll-function',
+            'coll-sequcene', 'coll-table',
+            'coll-view'
+          ],
+
+          /**
+            Enable/disable grantwizard menu in tools based
+            on node selected
+            if selected node is present in supported_nodes,
+            menu will be enabled otherwise disabled.
+           */
+          menu_enabled = function(obj) {
+            if(!_.isUndefined(obj) && !_.isNull(obj))
+              return (_.indexOf(supported_nodes, obj._type) !== -1 ? true: false);
+            else
+              return false;
+          };
+
+      // Define the nodes on which the menus to be appear
+      var menus = [{
+        name: 'grant_wizard_schema', module: this,
+        applies: ['tools'], callback: 'start_grant_wizard',
+        priority: 10, label: '{{_("Grant Wizard...") }}',
+        icon: 'wcTabIcon', enable: menu_enabled
+      }];
+
+      // Add supported menus into the menus list
+      for (var idx = 0; idx < supported_nodes.length; idx++) {
+        menus.push({
+          name: 'grant_wizard_schema_context_' + supported_nodes[idx],
+          node: supported_nodes[idx], module: this,
+          applies: ['context'], callback: 'start_grant_wizard',
+          priority: 10, label: '{{_("Grant Wizard...") }}',
+          icon: 'wcTabIcon', enable: menu_enabled
+          });
+      }
+      pgAdmin.Browser.add_menus(menus);
+
+      return this;
+    },
+
+    // Callback to draw Wizard Dialog
+    start_grant_wizard: function(action, item) {
+      if (!alertify.wizardDialog) {
+        alertify.dialog('wizardDialog', function factory() {
+          return {
+            main:function(title) {
+              this.set('title', title);
+            },
+            setup:function() {
+              return {
+                options: {
+                  frameless: true,
+                  resizable: false,
+                  autoReset: false,
+                  maximizable: false,
+                  closableByDimmer: false
+                }
+              };
+            },
+            hooks:{
+              onshow: function() {
+
+                // Set dimensions for wizard
+                this.elements.dialog.style.width = '100%';
+                this.elements.dialog.style.height = '540px';
+              },
+            },
+            build:function() { },
+
+            /**
+              Returns a Collection used for rendering grid
+              and Pagination
+
+              @class {Backbone.PageableCollection}
+              @param {variable} baseUrl - from which data is fetched
+              @param {Backbone.Model} DatabaseObjectModel
+              @return {Object} coll
+            */
+            getPageableCollection: function(baseUrl){
+              var Coll = Backbone.PageableCollection.extend({
+                model: DatabaseObjectModel,
+                url: baseUrl,
+                mode: "client",
+                state: {
+                  pageSize: 100,
+                  sortKey: "object_type",
+                  order: -1
+                },
+                queryParams: {
+                  totalPages: null,
+                  totalRecords: null,
+                  sortKey: "sort"
+                }
+              });
+              return new Coll();
+            },
+
+            /**
+              Returns a Paginator Class Object which is again to be rendered
+
+              @class {Backgrid.Extension.Paginator}
+              @param {Backbone.Collection} coll - from which data is fetched
+              @return {Object} paginator
+            */
+            DbPaginator: function(coll){
+              var paginator = this.paginator = new Backgrid.Extension.Paginator({
+                collection: coll,
+                windowSize: 8
+              });
+              return paginator;
+            },
+
+            /**
+              Create new Filter which will filter the
+              rendered grid for Select Type Tabular Data
+              @param {Backbone.PageableCollection} coll
+            */
+            DbObjectFilter: function(coll){
+              var clientSideFilter = this.clientSideFilter = new Backgrid.Extension.ClientSideFilter({
+                collection: coll,
+                placeholder: _('Search by object type or name'),
+
+                // The model fields to search for matches
+                fields: ['object_type', 'name'],
+
+                // How long to wait after typing has stopped before searching can start
+                wait: 150
+              });
+              return clientSideFilter;
+            },
+
+            //Enable Disable Next button of Page 2
+            updateButtons: function(modified){
+              if(!modified)
+                $('.wizard-next').prop('disabled', true);
+              else
+                $('.wizard-next').prop('disabled', false);
+            },
+
+            /**
+              Callback called when an errorModel is set
+              with invalid value and errormsg is set into
+              status bar element and next button is disabled
+             */
+            onSessionInvalid: function(msg) {
+              $('.error_msg_div p').html(msg).removeClass("hide");
+
+              // Enable disable Next button
+              this.updateButtons(false);
+              return true;
+            },
+
+            /**
+              Callback called when anything is set into model
+              thus hide error msg element and enable next button
+              status bar element and next button is disabled
+             */
+            onSessionValidated: function(sessHasChanged) {
+              $('.error_msg_div p').empty().addClass("hide");
+
+              // Enable disable Next button
+              this.updateButtons(sessHasChanged);
+            },
+
+            /*
+             Remove/Delete objects, attributes
+             in wizard on wizard close or finish
+             to reclaim memory
+            */
+            releaseObjects: function(){
+              var self = this;
+
+              if(!_.isUndefined(self.dbObjectFilter)) {
+                self.dbObjectFilter.remove();
+                self.dbObjectFilter = undefined;
+              }
+
+              if(!_.isUndefined(self.clientSideFilter)) {
+                self.clientSideFilter.remove();
+                self.clientSideFilter = undefined;
+              }
+
+              if(!_.isUndefined(self.paginator)) {
+                self.paginator.remove();
+                self.paginator = undefined;
+              }
+
+              // Delete Wizard Pages
+              if(!_.isUndefined(self.dbObjectTypePage) &&
+                !_.isNull(self.dbObjectTypePage)) {
+                if(!_.isUndefined(self.dbObjectTypePage.get('model')) &&
+                  !_.isNull(self.dbObjectTypePage.get('model'))) {
+                  self.dbObjectTypePage.get('model').clear();
+                  self.dbObjectTypePage = undefined;
+                }
+              }
+
+              if(!_.isUndefined(self.privilegePage) &&
+                !_.isNull(self.privilegePage)) {
+                if(!_.isUndefined(self.privilegePage.get('model')) &&
+                  !_.isNull(self.privilegePage.get('model'))) {
+                  self.privilegePage.get('model').clear();
+                  self.privilegePage = undefined;
+                }
+              }
+
+              if(!_.isUndefined(self.reviewSQLPage) &&
+                !_.isNull(self.reviewSQLPage)) {
+                if(!_.isUndefined(self.reviewSQLPage.get('model')) &&
+                  !_.isNull(self.reviewSQLPage.get('model'))) {
+                  self.reviewSQLPage.get('model').clear();
+                  self.reviewSQLPage = undefined;
+                }
+              }
+
+              // Remove collection containing db object data
+              if(!_.isNull(self.coll) &&
+                !_.isUndefined(self.coll)) {
+                self.coll.reset();
+                self.coll = undefined;
+              }
+
+              // Delete Wizard
+              if(!_.isNull(self.wizard) &&
+                !_.isUndefined(self.wizard)) {
+                self.wizard.collection.reset();
+                self.wizard.curr_page = undefined;
+                self.wizard.el = undefined;
+                self.wizard = undefined;
+              }
+
+            },
+
+            /**
+              Every time a wizard is opened, this function
+              is called everytime. It has Wizard Pages which
+              are rendered by the Wizard Class:
+
+              @class {pgBrowser.WizardPage} dbObjectType1 - This page
+              @extends {Backbone.Model}
+              renders a grid of Database Object Types such as
+                Schemas, Views and Sequences etc.
+
+              @class {pgBrowser.WizardPage} WizardPage2 - This page
+              @extends {Backbone.Model}
+              adds Privilege Control which provides grant privileges
+              such as "Create, Insert, Delete, Update" so on the
+              database objects selected on Wizard Pages.
+
+              @class {pgBrowser.WizardPage} WizardPage3 - This page
+              displays the generated GRANT SQL query for the Db
+              objects selected with the specific privileges added to it.
+              @extends {Backbone.Model}
+
+              @class {Backbone.Collection} WizardCollection - It is the
+              collection of wizard pages
+
+              @class {pgBrowser.Wizard} wizard - Its task is:
+              - Create a Wizard
+              - Add Buttons, Callbacks to it.
+              - Render WizardPages
+              @extends {Backbone.View}
+
+            */
+            prepare:function() {
+
+              // Remove extra container
+              if($('.ajs-content').size() > 1){
+                $('.ajs-content')[0].remove();
+              }
+
+              // Generate wizard main container
+              var wizard_html = '<div class="wizard_dlg">',
+                  wizard_parent = $('.ajs-content').empty().append(wizard_html),
+                  el = $(".ajs-content");
+
+              // Extract the data from the selected tree node
+              var t = pgBrowser.tree,
+                  i = t.selected(),
+                  d = i && i.length == 1 ? t.itemData(i) : undefined,
+                  info = pgBrowser.Node.getTreeNodeHierarchy(i),
+                  icon = d.icon;
+
+              /**
+                Generate a URL using:
+                gid, did, sid(server id), node_id(node id),
+                node_(node name), node_type(node type)
+                and pass it to collection which will fetch Object Type properties.
+               */
+              var gid = info['server-group']._id,
+                  sid = info.server._id,
+                  did = info.database._id,
+                  node_id = d._id,
+                  nspname = info.schema.label;
+                  node_type = d._type,
+                  node_label = d.label,
+
+                  // Collection url to fetch database object types for objects field
+                  baseUrl = "{{ url_for('grant_wizard.index') }}" + "grant_wizard/properties/" +
+                      S('%s/%s/%s/%s/%s/%s/%s/').sprintf(
+                          encodeURI(gid), encodeURI(sid), encodeURI(did),
+                          encodeURI(node_id), encodeURI(node_label),
+                          encodeURI(node_type), encodeURI(nspname)).value();
+
+                  // Model's save url
+                  saveUrl = "{{ url_for('grant_wizard.index') }}" + "grant_wizard/save/" +
+                      S('%s/%s/%s/%s/%s/').sprintf(
+                          encodeURI(gid), encodeURI(sid), encodeURI(did),
+                          encodeURI(node_type), encodeURI(nspname)).value(),
+
+                  // generate encoded url based on wizard type
+                  msql_url = this.msql_url = "/wizard/grant_wizard/msql/"+
+                    S('%s/%s/%s/%s/%s/').sprintf(
+                        encodeURI(gid), encodeURI(sid), encodeURI(did),
+                        encodeURI(node_type), encodeURI(nspname)).value(),
+
+                  // Create instances of collection, pagination and filter
+                  coll = this.coll = this.getPageableCollection(baseUrl),
+                  paginator = this.paginator = this.DbPaginator(coll),
+                  dbObjectFilter = this.dbObjectFilter = this.DbObjectFilter(coll);
+
+              /**
+                It is the main model with schema defined
+                Every time a new wizard is opened,
+                a new model should create.
+               */
+              var GrantWizardModel = pgNode.Model.extend({
+                defaults: {
+                  objects: undefined,
+                  acl: undefined
+                },
+                schema: [
+                  {
+                    id: 'objects', label: '{{ _("Objects") }}', model: DatabaseObjectModel,
+                    type: 'collection', group: 'Objects'
+                  },
+                  {
+                    id: 'acl', label: '{{ _("Privileges") }}', node: 'role',
+                    model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend({
+
+                    // privileges are selected based on node clicked
+                      privileges: privDict[node_type]
+                    }), uniqueCol : ['grantee', 'grantor'], editable: true,
+                    type: 'collection', group: 'Privileges', canAdd: true,
+                    canDelete: true, control: 'unique-col-collection'
+                  }
+                ],
+                urlRoot: saveUrl
+              });
+
+              /**
+                Create instance of GrantWizard Model, provide urlRoot
+                node_info object, Generate fields objects
+               */
+              var self = this,
+                  newModel = new GrantWizardModel({}, { node_info: info }),
+                  fields = Backform.generateViewSchema(
+                     info, newModel, 'create', d._type, d
+                     );
+
+              /**
+                Fetch data from server and set into grid
+                and show/hide progress bar
+               */
+              $('.wizard-progress-bar p').show();
+
+              coll.fetch({
+                success: function(collection, data) {
+                  $('.wizard-progress-bar p').html('');
+                },
+                reset: true
+              }, this);
+
+              /**
+                Override backgrid listener "backgrid:selected" to
+                Add/Remove model to/from objects collection
+               */
+              coll.on('backgrid:selected', function(model, selected) {
+                model.set('selected', selected);
+
+                /**
+                  if a row (checkbox) is checked, add that model
+                  into collection, when unchecked remove it from
+                  model.
+                 */
+                if(selected)
+                  newModel.get('objects').add(model, { silent: true });
+                else
+                  newModel.get('objects').remove(model);
+
+                // validate model on checkbox check/uncheck
+                var msg =  model.validate.call(newModel);
+
+                /**
+                  If no object type is selected, set error msg
+                  and disable next button, else enable next button
+                 */
+                if(msg)
+                  self.onSessionInvalid.call(self, msg);
+                else
+                  self.onSessionValidated.call(self, true);
+              });
+
+              // To track changes into model, start new session
+              newModel.startNewSession();
+
+              // Add event listener for privileges control
+              newModel.on('pgadmin-session:valid', self.onSessionValidated.bind(this));
+              newModel.on('pgadmin-session:invalid', self.onSessionInvalid.bind(this));
+
+              //////////////////////////////////////////////////////////////////////
+              //                                                                  //
+              //            Wizard Page for Db Object Type                        //
+              //                                                                  //
+              //////////////////////////////////////////////////////////////////////
+
+              /**
+                Create wizard page. It renders a grid of
+                Database Object Types such as
+                Schemas, Views and Sequences etc.
+                Set default values
+               */
+              var dbObjectTypePage = self.dbObjectTypePage = new pgBrowser.WizardPage({
+                id: 1,
+                page_title: 'Object Selection (step 1 of 3)',
+                disable_prev: true,
+                disable_next: true,
+                show_description: _('Please select objects from the below list.'),
+                show_progress_bar: _('Please wait while fetching records...'),
+                model: newModel,
+                view: new (function() {
+
+                  // Set page Instance
+                  var pageView = this;
+
+                  _.extend(pageView, {
+
+                    // Remove grid if it is before render
+                    cleanup: function() {
+                      if (this.grid) {
+                        this.grid.remove();
+                        delete this.grid;
+                        this.grid = null;
+                      }
+
+                      // Remove grid element if exists
+                      if (this.el) {
+                        $(this.el).remove();
+                        delete this.el;
+                      }
+                    },
+
+                    // Delete grid before render
+                    grid: null,
+
+                    render: function() {
+
+                      // Create a grid container
+                      var gridBody =
+                            $('<div class="select_db_objects_container"></div>');
+
+                      // Remove grid if exits before render
+                      if (this.grid) {
+                        this.cleanup();
+                      }
+
+                      // Initialize a new Grid instance
+                      this.grid = new Backgrid.Grid({
+                        columns: _.clone(columns),
+                        collection: coll,
+                        className: "backgrid table-bordered object_type_table"
+                        });
+
+                      // Render selection Type grid and paginator
+                      gridBody.append(
+                        this.grid.render().$el
+                        ).append(self.paginator.render().el);
+
+                      // Render Search Filter
+                      gridBody.prepend(
+                        self.clientSideFilter.render().el);
+
+                      // Assign gridBody content to page element
+                      this.el = gridBody;
+
+                      /**
+                        Fetch selected models from collection and
+                        make rows checked in grid
+                       */
+                      newModel.get('objects').each(function(m) {
+                        var model = coll.get(m.get('object_id'));
+                        if (model) {
+                          coll.trigger('backgrid:selected', model, true);
+                        }
+                      });
+
+                      // Refresh grid to re render rows.
+                      coll.trigger('backgrid:refresh');
+
+                      return this;
+                    }
+                  });
+                }),
+
+                beforeNext: function(obj){
+                  var self = this;
+                  obj.options.disable_next = true;
+
+                  /**
+                    Enable/Disable next button of privilegePage if objects
+                    are present in model
+                   */
+                  if(!_.isNull(newModel.get('acl')) &&
+                    !_.isUndefined(newModel.get('acl'))) {
+                    if(newModel.get('acl').length > 0)
+                      obj.collection.at(1).set('disable_next', false);
+                    }
+
+                  // Clean the view
+                  if (self.view) {
+                    self.view.cleanup();
+                    delete self.view;
+                    self.view = null;
+                  }
+                  return true;
+                },
+
+              });
+
+              //////////////////////////////////////////////////////////////////////
+              //                                                                  //
+              //            Wizard Page for Privilege Control                     //
+              //                                                                  //
+              //////////////////////////////////////////////////////////////////////
+
+              /**
+                Create Field Object which has properties like
+                node_data, node_info which is required for rendering
+                Privilege control
+               */
+              var privilegesField = new Backform.Field(fields[1].fields[0]);
+
+              // Wizard for Privelege control
+              var privilegePage = self.privilegePage = new pgBrowser.WizardPage({
+                id: 2,
+                page_title: _('Privileges Selection (step 2 of 3)'),
+                show_description: _('Please select privileges for the selected objects.'),
+                disable_next: true,
+                model: newModel,
+
+                // Create a view function object
+                view: new (function() {
+                  var pageView = this;
+                    _.extend(pageView, {
+
+                      // Render Privelege control to generate its html markup
+                      render: function() {
+                        this.privControl = new (privilegesField.get('control')) ({
+                          field: privilegesField,
+                          model: newModel
+                        });
+                        return {el: this.privControl.render().$el};
+                      },
+
+                      // Remove the privilege control
+                      cleanup: function() {
+                        if (this.privControl) {
+                          this.privControl.remove();
+                          delete this.privControl;
+                          this.privControl = null;
+                        }
+                      }
+                    });
+                }),
+
+                beforePrev: function(wizardObj) {
+
+                  // Remove the privilege control
+                  if (this.view) {
+                    this.view.cleanup();
+                    delete this.view;
+                    this.view = null;
+                  }
+
+                  /**
+                    Enable/Disable next button of page 1 if objects
+                    are present in model
+                   */
+                  var objectsModel = newModel.get('objects');
+
+                  if(!_.isUndefined(objectsModel) && !_.isEmpty(objectsModel) &&
+                      objectsModel.length > 0) {
+                    wizardObj.collection.at(0).set('disable_next', false);
+
+                    // Don't show progress bar
+                    wizardObj.collection.at(0).set('show_progress_bar', '');
+                  }
+
+                  /**
+                    We're re-rendering the controls as they are deleted
+                    before heading to next page
+                    Refresh Backgrid to re-render the elements selected
+                    re-render paginator
+                    re-render Filter
+                   */
+                  newModel.trigger("backgrid:refresh", newModel, false);
+                  self.paginator.render();
+                  self.clientSideFilter.render();
+                  return true;
+                },
+
+                beforeNext: function() { return true; },
+
+                onNext: function(obj){
+
+                  // Remove the privilege control
+                  if (this.view) {
+                    this.view.cleanup();
+                    delete this.view;
+                    this.view = null;
+                  }
+
+                  // Enable finish button
+                  self.wizard.options.disable_finish = false;
+
+                  /**
+                    triggers to get SQL queries data to render
+                    into the reviewSQLPage
+                   */
+                  newModel.trigger('pgadmin-wizard:nextpage:sql', {'node_type': node_type });
+                }
+              });
+
+
+              //////////////////////////////////////////////////////////////////////
+              //                                                                  //
+              //            Review SQL Query Page                                 //
+              //                                                                  //
+              //////////////////////////////////////////////////////////////////////
+
+              //Create SqlField Object
+              var sqlField = new Backform.Field(
+                  {
+                    id: 'sqltab',
+                    label: _('Sql Tab'),
+                    field: fields,
+
+                    /**
+                      Extend 'SqlTabControl' to define new
+                      function 'onWizardNextPageChange'
+                      which gets triggered on next button
+                      click to fetch generated SQL query
+                      for the selected db objects.
+                     */
+                    control: Backform.SqlTabControl.extend({
+                      initialize: function() {
+
+                        // Initialize parent class
+                        Backform.SqlTabControl.prototype.initialize.apply(this, arguments);
+
+                        this.msql_url = self.msql_url;
+
+                        // define trigger events for prev and next page
+                        this.model.on('pgadmin-wizard:nextpage:sql', this.onWizardNextPageChange, this);
+                        this.model.on('pgadmin-wizard:prevpage:sql', this.onWizardPrevPageChange, this);
+                      },
+
+                      // This method fetches the modified SQL for the wizard
+                      onWizardNextPageChange: function(){
+
+                        var self = this;
+
+                        // Fetches modified SQL
+                        $.ajax({
+                          url: this.msql_url,
+                          type: 'GET',
+                          cache: false,
+                          data: self.model.toJSON(true, 'GET'),
+                          dataType: "json",
+                          contentType: "application/json"
+                        }).done(function(res) {
+                          self.sqlTab.clearHistory();
+                          self.sqlTab.setValue(res.data);
+                        }).fail(function() {
+                          self.model.trigger('pgadmin-view:msql:error');
+                        }).always(function() {
+                          self.model.trigger('pgadmin-view:msql:fetched');
+                        });
+                      },
+
+                      remove: function() {
+
+                        // Stop listen to next/prev page events
+                        this.model.off('pgadmin-wizard:nextpage:sql', this.onWizardNextPageChange, this);
+                        this.model.off('pgadmin-wizard:prevpage:sql', this.onWizardPrevPageChange, this);
+                        Backform.SqlTabControl.__super__.remove.apply(this, arguments);
+                      }
+
+                    })
+                  }),
+
+                /**
+                  Create sqlField view instance
+                  to render it into wizard page
+                 */
+                sqlControl = new (sqlField.get('control'))({
+                  field: sqlField,
+                  model: newModel
+                });
+
+              // Wizard for SQL tab control
+              var reviewSQLPage = self.reviewSQLPage = new pgBrowser.WizardPage({
+                id: 3,
+                page_title: _('Final (Review Selection) (step 3 of 3)'),
+                show_description: _('Following query will be executed on the database'
+                              +' server for the selected objects, and privileges.'
+                              +' Please click on Finish to complete the process.'),
+                model: newModel,
+                view: new(function() {
+
+                  // Render SqlTab control to generate its html markup
+                  var sqlTabHtml = sqlControl.render().$el;
+                  this.render = function() {
+                      return { el: sqlTabHtml };
+                  };
+                }),
+
+                beforePrev: function(wizardObj) {
+
+                  /**
+                    Enable next button if privilege
+                    model is not empty else disable
+                    next button
+                    */
+                  var aclModel = newModel.get('acl');
+
+                  if(!_.isUndefined(wizardObj.collection) &&
+                    wizardObj.collection.models.length > 0) {
+                    if(!_.isUndefined(aclModel) && !_.isEmpty(aclModel) &&
+                        aclModel.length > 0) {
+                      wizardObj.collection.at(1).set('disable_next', false);
+                    }
+                    else {
+                      wizardObj.collection.at(1).set('disable_next', true);
+                    }
+
+                    return true;
+                  }
+                },
+              });
+
+
+              // Create Wizard Collection of Wizard Pages
+              var WizardCollection = Backbone.Collection.extend({
+                model: pgBrowser.WizardPage
+              });
+
+              // It holds all the wizard pages to be rendered
+              this.wizardCollection = new WizardCollection(
+                [dbObjectTypePage, privilegePage, reviewSQLPage]
+              );
+
+              /**
+                Create wizard which has following operations:
+                - renders wizard pages
+                - defines the first page to render in wizard
+                - Save the model on finishbutton
+                - Remove wizard on cancel button
+                */
+              var wizard = self.wizard = new (pgBrowser.Wizard.extend({
+                options: {
+                  title: 'Grant Wizard', /* Main Wizard Title */
+                  width: '',
+                  height: '',
+                  curr_page: 0,
+                  show_left_panel: false,
+                  disable_finish: true
+                },
+
+                onFinish: function() {
+                  var m = newModel,
+                      d = m.toJSON('GET');
+
+                  if (d && !_.isEmpty(d) && !_.isUndefined(d.objects)) {
+                    m.save({}, {
+                      attrs: d,
+                      validate: false,
+                      cache: false,
+                      success: function(res) {
+
+                        // Release wizard objects
+                        self.releaseObjects();
+                        self.close();
+                      },
+                      error: function(m, jqxhr) {
+                        alertify.pgNotifier(
+                          "error", jqxhr,
+                          S(
+                            "{{ _('Error during saving properties - %%s!') }}"
+                            ).sprintf(jqxhr.statusText).value()
+                          );
+                      }
+                    });
+                  }
+                },
+                onCancel: function() {
+
+                  // Release wizard objects
+                  self.releaseObjects();
+                  self.close();
+                }
+              })) ({
+                collection: this.wizardCollection,
+                el: el,
+                model: newModel
+              });
+
+              // Render wizard
+              wizard.render();
+            }
+          };
+        });
+      }
+
+      // Call Grant Wizard Dialog
+      alertify.wizardDialog('');
+    }
+  };
+
+  return pgBrowser.GrantWizard;
+});
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/allowed_acl.json b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/allowed_acl.json
new file mode 100644
index 0000000..cb73d2c
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/allowed_acl.json
@@ -0,0 +1,30 @@
+{# List of allowed privileges of Schema, table sequence, function and view #}
+{#
+    Format for allowed privileges are:
+    "node_name": {
+        "type": "name",
+        "acl": [...]
+    }
+#}
+{
+    "schema": {
+        "type": "SCHEMA",
+        "acl": ["a", "r", "w", "d", "D", "x", "t", "U", "X"]
+    },
+    "coll-table": {
+        "type": "TABLE",
+        "acl": ["a", "w", "U", "d", "D", "x", "t"]
+    },
+    "coll-sequence": {
+        "type": "SEQUENCE",
+        "acl": ["a", "w", "u"]
+    },
+    "coll-function": {
+        "type": "FUNCTION",
+        "acl": ["X"]
+    },
+    "coll-view": {
+        "type": "VIEW",
+        "acl": ["a", "r", "w", "d", "D", "x", "t"]
+    }
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/grant.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/grant.sql
new file mode 100644
index 0000000..02ac1a7
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/grant.sql
@@ -0,0 +1,19 @@
+{#=====Grant Permissions on Database Objects Selected====#}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% import 'macros/functions/privilege.macros' as PRIVILEGE_FUNCTION %}
+{% if data.acl %}
+{% for obj in data.objects %}
+{% for priv in data.acl %}
+{% set obj_type = obj.object_type %}
+{% if obj_type.upper() != 'FUNCTION' and obj_type.upper() != 'TRIGGER FUNCTION' %}
+{% if obj_type.upper() == 'VIEW' %} {###### Views are also kind of table ######}
+{% set obj_type = 'TABLE' %}
+{% endif %}
+{{ PRIVILEGE.SET(conn, obj_type.upper(), priv.grantee, obj.name, priv.without_grant, priv.with_grant, nspname ) }}
+{% else %}
+{###### if object_type is Function then apply function marcros ######}
+{{ PRIVILEGE_FUNCTION.SET(conn, 'FUNCTION', priv.grantee, obj.name, priv.without_grant, priv.with_grant, nspname, obj.proargs)}}
+{% endif %}
+{% endfor %}
+{% endfor %}
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/properties.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/properties.sql
new file mode 100644
index 0000000..7a33c1f
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/properties.sql
@@ -0,0 +1,65 @@
+{#===================Fetch list of Database object types(Functions, Sequence, Tables or View)===================#}
+{% if type == 'schema' or type == 'trigger_function' and node_id and node_name %}
+{% set func_type = 'Trigger Function' if type == 'trigger_function' else 'Function' %}
+SELECT
+    pr.proname AS name,
+    pg_get_function_identity_arguments(pr.oid) AS proargs,
+    '{{ func_type }}' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-function' AS icon
+FROM pg_proc pr
+    JOIN pg_type typ ON typ.oid=prorettype
+    JOIN pg_namespace typns ON typns.oid=typ.typnamespace
+    JOIN pg_language lng ON lng.oid=prolang
+    LEFT OUTER JOIN pg_description des ON (des.objoid=pr.oid AND des.classoid='pg_proc'::regclass)
+WHERE proisagg = FALSE AND pronamespace = {{ node_id }}::oid
+    AND typname {{ 'NOT' if type != 'trigger_function' else '' }} IN ('trigger', 'event_trigger')
+ORDER BY proname
+{% endif %}
+
+{% if type == 'sequence' and node_id and node_name %}
+SELECT
+    cl.relname AS name,
+    'Sequence' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-sequence' AS icon
+FROM pg_class cl
+    LEFT OUTER JOIN pg_description des ON (des.objoid=cl.oid AND des.classoid='pg_class'::regclass)
+WHERE relkind = 'S' AND relnamespace  = {{ node_id }}::oid
+ORDER BY cl.relname
+{% endif %}
+
+{% if type == 'table' and node_id and node_name %}
+SELECT rel.relname AS name,
+    'Table' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-table' AS icon
+FROM pg_class rel
+    LEFT OUTER JOIN pg_tablespace spc ON spc.oid=rel.reltablespace
+    LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass)
+    LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'
+    LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid
+    LEFT JOIN pg_type typ ON rel.reloftype=typ.oid
+WHERE rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ node_id }}::oid
+ORDER BY rel.relname
+{% endif %}
+
+{% if type == 'coll-view' and node_id and node_name %}
+SELECT
+    c.relname AS name,
+    'View' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-coll-view' AS icon
+FROM pg_class c
+    LEFT OUTER JOIN pg_tablespace spc ON spc.oid=c.reltablespace
+    LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass)
+    LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid
+WHERE ((c.relhasrules AND (EXISTS (
+    SELECT r.rulename
+    FROM pg_rewrite r
+    WHERE ((r.ev_class = c.oid)
+        AND (bpchar(r.ev_type) = '1'::bpchar)) ))
+       ) OR (c.relkind = 'v'::char))
+AND c.relnamespace = {{ node_id }}::oid
+ORDER BY c.relname
+{% endif %}


^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-03-08 10:00  Surinder Kumar <[email protected]>
  parent: Surinder Kumar <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Surinder Kumar @ 2016-03-08 10:00 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: pgadmin-hackers

Hi,

I forgot to add 'node.ui.js' file in the previous patch, please ignore the
previous patch.
Attached is the new patch,

Please review the patch and let me know for any comments.


On Tue, Mar 8, 2016 at 10:44 AM, Surinder Kumar <
[email protected]> wrote:

> Hi
>
> PFA patch with following changes:
>
> 1. Extends SqlTabControl to write a new function 'onWizardChange',
> instead of writing it in backform.pgadmin.js file.
>
> Also, validations in privilegeControl are not working properly, when
> validations gets fixed, I will send an updated patch.
>
> This is complete patch, as Khusboo's patch also merged in attached patch
> and
> patches of "Sequence" and "Functions" macros are already committed.
>
>
> On Fri, Mar 4, 2016 at 8:06 PM, Dave Page <[email protected]> wrote:
>
>> A couple of corrections below <sigh>
>>
>> On Fri, Mar 4, 2016 at 1:39 PM, Dave Page <[email protected]> wrote:
>> > Hi
>> >
>> > On Thu, Mar 3, 2016 at 12:49 PM, Surinder Kumar
>> > <[email protected]> wrote:
>> >> Hi,
>> >>
>> >> PFA patch for Grant Wizard.
>> >>
>> >> Before applying grant wizard patch:
>> >>
>> >>         1. Apply patch for "wizard JS file" which Khushboo had shared
>> with
>> >> Ashesh personally.
>> >>             I am using that patch with few changes in that. Ashesh will
>> >> review
>> >>             and commit that patch.
>> >>
>> >>         2. Apply patches of "Sequence" and "Functions" macros.
>> >>
>> >>
>> >> Please review the patch and Let me know for any comments.
>> >
>> > Initial feedback:
>> >
>> > - Why does this add specific knowledge of the Grant Wizard to the
>> > Browser module? It should be a plugin that loads itself at runtime.
>> > Any changes to the browser to support that should be entirely generic
>> > and usable by any module.
>>
> Fixed.
>
>> >
>> > - The comment above also applies to the core templates. CSS should be
>> > advertised by the plugin, and the browser can add it into the rendered
>> > output when the module is dynamically loaded.
>>
> Fixed.
>
>> >
>> > - +""" Implements Grant Wizard""" - why the leading space? Please
>> > review all comments and code for such small details.
>>
> Done
>
>> >
>> > - The Python code to detect and alias various Python 2/3 classes
>> > should be centralised, as I've seen it in at least one other module.
>> >
>>
> Removed it, as far as it is not required.
>
>> > - In other module names, we've separated multiple words with a hypen.
>> > e.g. server-groups. s/grantwizard/grant-wizard/g
>>
>> That should be an underscore, not a hyphen:
>>
>> s/grantwizard/grant_wizard/g
>
>
>> >
>> > - The published routes for this module are all under
>> >
>> > - "GET /browser/static/js/wizard.js HTTP/1.1" 404 -
>> >
>> > - For consistency, when naming CSS/JS files that are core to a module,
>> > please use the module name, e.g.
>> > /web/pgadmin/tools/grant-wizard/static/css/grant-wizard.css
>>
>> /web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
>>
> Done
>
>>
>> --
>> Dave Page
>> Blog: http://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EnterpriseDB UK: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>>
>
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [application/octet-stream] grant_wizard_v3.patch (62.0K, 3-grant_wizard_v3.patch)
  download | inline diff:
diff --git a/web/pgadmin/browser/static/css/wizard.css b/web/pgadmin/browser/static/css/wizard.css
new file mode 100644
index 0000000..9934aac
--- /dev/null
+++ b/web/pgadmin/browser/static/css/wizard.css
@@ -0,0 +1,89 @@
+
+/** CSS for Wizard **/
+.wizard_dlg {
+  height: 300px;
+  padding: 0 10px;
+}
+
+.ajs-content {
+  padding-top: 0px !important;
+}
+
+/* Wizard Header CSS */
+.wizard-header {
+  background: #428bca;
+  padding-left: 15px;
+  padding-bottom: 7px;
+  color: #fff;
+  font-size: 18px;
+  height: 76px;
+  line-height: 76px;
+}
+
+.wizard-header h3 {
+  font-size: 18px;
+  display: inline-block;
+}
+
+.wizard-content {
+  position: relative;
+  overflow-y: auto;
+  float: left;
+  padding: 0;
+  min-height: 390px;
+  max-height: 390px;
+}
+
+/* Wizard Footer CSS */
+.footer {
+  position: absolute;
+  border-top: 1px solid #ccc;
+  bottom: 0px;
+  height: 71px;
+  right: 0px;
+  padding-top: 22px;
+}
+
+/* Wizard Button CSS */
+.wizard-buttons {
+  float: right;
+}
+
+.wizard-buttons button {
+  float: left;
+  padding: 7px 15.2px;
+  font-size: 14px;
+  margin-right: 5px;
+}
+
+.wizard-finish {
+  margin-right: 0 !important;
+}
+
+/* Wizard Status bar CSS */
+.wizard-description {
+  height: auto;
+  padding: 20px 0px;
+}
+
+/* Error message css */
+.error_msg_div {
+  display: block;
+  bottom: 20px;
+}
+
+.error_msg_div p {
+  background: #fff;
+  color: #b92c28;
+}
+
+/* In wizard select2 dropdown doesn't
+ * popup because z-index of alertify
+ * wizard is greater than the z-index
+ * of select2 dropdown. To make select2
+ * visible, set z-index of select2
+ * higher value than wizard's
+ */
+.select2-container--open {
+  z-index: 10000;
+}
diff --git a/web/pgadmin/browser/static/js/node.ui.js b/web/pgadmin/browser/static/js/node.ui.js
index b84b6ee..47f1266 100644
--- a/web/pgadmin/browser/static/js/node.ui.js
+++ b/web/pgadmin/browser/static/js/node.ui.js
@@ -279,7 +279,7 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
         return false;
       };
 
-      while(p) {
+      while(p && p.length > 0) {
         top = p.get(0).offsetTop + p.height();
         p = p.parent();
         if (hasScrollbar(p)) {
diff --git a/web/pgadmin/browser/static/js/wizard.js b/web/pgadmin/browser/static/js/wizard.js
new file mode 100644
index 0000000..78458e4
--- /dev/null
+++ b/web/pgadmin/browser/static/js/wizard.js
@@ -0,0 +1,222 @@
+define(
+    ['underscore', 'backbone', 'pgadmin', 'pgadmin.browser'],
+function(_, Backbone, pgAdmin, pgBrowser) {
+
+  pgBrowser = pgBrowser || pgAdmin.Browser || {};
+
+  /* Wizard individual Page Model */
+  var WizardPage = pgBrowser.WizardPage = Backbone.Model.extend({
+    defaults: {
+      id: undefined, /* Id */
+      page_title: undefined, /* Page Title */
+      view: undefined, /* A Backbone View */
+      html: undefined, /* HTML tags to be rendered */
+      image: undefined, /* Left hand side image */
+      disable_prev: false, /*  Previous Button Flag */
+      disable_next: false, /*  Next Button Flag */
+      disable_cancel: false, /* Cancel Button Flag */
+      show_progress_bar: '',
+      /* Callback for OnLoad */
+      onLoad: function() {
+        return true;
+      },
+      /* Callback for before Next */
+      beforeNext: function() {
+        return true;
+      },
+      onNext: function(){},
+      onBefore: function() {},
+      /* Callback for before Previous */
+      beforePrev: function() {
+        return true;
+      }
+    }
+  });
+
+  var Wizard = pgBrowser.Wizard = Backbone.View.extend({
+    options: {
+      title: 'Wizard', /* Main Wizard Title */
+      image: 'left_panel.png', /* TODO:: We can use default image here */
+      curr_page: 0, /* Current Page to Load */
+      disable_next: false,
+      disable_prev: false,
+      disable_finish: false,
+      disable_cancel: false,
+      height: 400,
+      width: 650,
+      show_left_panel: true
+    },
+    tmpl: _.template(
+       "    <div class='pgadmin-wizard' style='height: <%= this.options.height %>px; width: <%= this.options.width %>px'>"
+       + "      <div class='wizard-header wizard-badge'>"
+       + "        <div class='row'>"
+       + "          <div class='col-sm-9'>"
+       + "              <h3><span id='main-title'><%= this.options.title %></span> - <span id='step-title'><%= page_title %></span></h3>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "      <div class='wizard-content col-sm-12'>"
+       + "        <% if(this.options.show_left_panel) { %>"
+       + "          <div class='col-sm-3 wizard-left-panel'>"
+       + "              <img src='<%= this.options.image %>'></div>"
+       + "        <% } %>"
+       + "        <div class='col-sm-<% if(this.options.show_left_panel){ %>6<% } else { %>12<% } %> wizard-right-panel'>"
+       + "          <% if( typeof show_description !=  'undefined'){ %>"
+       + "            <div class='wizard-description'>"
+       + "              <%= show_description %>"
+       + "            </div>"
+       + "          <% } %>"
+       + "          <div class='wizard-right-panel_content'>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "      <div class='col-sm-12 error_msg_div'>"
+       + "       <p></p>"
+       + "      </div>"
+       + "      <div class='footer col-sm-12'>"
+       + "        <div class='row'>"
+       + "          <div class='col-sm-4 wizard-progress-bar'>"
+       + "             <p><% if(show_progress_bar){ %><%= show_progress_bar %><% } %></p>"
+       + "          </div>"
+       + "          <div class='col-sm-8'>"
+       + "            <div class='wizard-buttons'>"
+       + "              <button class='btn-sm btn-primary wizard-back' <%=this.options.disable_prev ? 'disabled' : ''%>><i class='fa fa-backward'></i> Back</button>"
+       + "              <button class='btn-sm btn-primary wizard-next' <%=this.options.disable_next ? 'disabled' : ''%>>Next <i class='fa fa-forward'></i></button>"
+       + "              <button class='btn-sm btn-danger wizard-cancel' <%=this.options.disable_cancel ? 'disabled' : ''%>><i class='fa fa-lg fa-close'></i>Cancel</button>"
+       + "              <button class='btn-sm btn-primary wizard-finish' <%=this.options.disable_finish ? 'disabled' : ''%>>Finish</button>"
+       + "            </div>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "    </div>"),
+    events: {
+      "click button.wizard-next" : "nextPage",
+      "click button.wizard-back" : "prevPage",
+      "click button.wizard-cancel" : "onCancel",
+      "click button.wizard-finish" : "finishWizard",
+    },
+    initialize: function(options) {
+      this.options = _.extend({}, this.options, options.options);
+      this.currPage = this.collection.at(this.options.curr_page).toJSON();
+    },
+    render: function() {
+      var data = this.currPage;
+
+      /* Check Status of the buttons */
+      this.options.disable_next = (this.options.disable_next ? true : this.evalASFunc(this.currPage.disable_next));
+      this.options.disable_prev = (this.options.disable_prev ? true : this.evalASFunc(this.currPage.disable_prev));
+      this.options.disable_cancel = (this.currPage.canCancel ? true : this.evalASFunc(this.currPage.disable_cancel));
+
+      that = this;
+
+      /* HTML Content */
+      if (data.html) { data.content = data.html; }
+      /* Backbone View */
+      else if (data.view) { data.content = data.view.render().el;}
+
+      $(this.el).html(this.tmpl(data));
+      $(this.el).find(".wizard-right-panel_content").html(data.content);
+
+      /* OnLoad Callback */
+      this.onLoad();
+
+      return this;
+    },
+    nextPage: function() {
+      this.options.curr_page.el = this.$el;
+      if (!this.beforeNext()) { return false; }
+
+      page_id = this.onNext();
+
+      if (page_id ) {
+        this.currPage = this.collection.get(page_id).toJSON();
+        this.options.curr_page = this.collection.indexOf(this.collection.get(page_id));
+      }
+      else if (this.options.curr_page < (this.collection.length-1)) {
+        this.options.curr_page = this.options.curr_page + 1;
+        this.currPage = this.collection.at(this.options.curr_page).toJSON();
+      }
+
+      this.enableDisableNext();
+      this.enableDisablePrev();
+
+      return this.render();
+    },
+    prevPage: function() {
+      if (!this.beforePrev()) { return false; }
+
+      page_id = this.onPrev();
+
+      if (page_id){
+        this.currPage = this.collection.get(page_id).toJSON();
+        this.options.curr_page = this.collection.indexOf(this.collection.get(page_id));
+      }
+      else if (this.options.curr_page > 0) {
+        this.options.curr_page = this.options.curr_page - 1;
+        this.currPage = this.collection.at(this.options.curr_page).toJSON();
+      }
+
+      this.enableDisableNext();
+      this.enableDisablePrev();
+
+      return this.render();
+    },
+    finishWizard: function() {
+      this.onFinish();
+      return true;
+    },
+    enableDisableNext: function(disable) {
+        if (typeof(disable) != 'undefined') {
+          this.options.disable_next = disable;
+        }
+        else if (this.options.curr_page >= (this.collection.length-1)) {
+          this.options.disable_next = true;
+        }
+        else {
+          this.options.disable_next = false;
+        }
+    },
+    enableDisablePrev: function(disable) {
+        if (typeof(disable) != 'undefined') {
+          this.options.disable_prev = disable;
+        }
+        else if (this.options.curr_page <= 0) {
+          this.options.disable_prev = true;
+        }
+        else {
+          this.options.disable_prev = false;
+        }
+    },
+    beforeNext: function(){
+      return this.evalASFunc(this.currPage.beforeNext);
+    },
+    beforePrev: function(){
+      return this.evalASFunc(this.currPage.beforePrev);
+    },
+    onPrev: function(){
+      return this.evalASFunc(this.currPage.onPrev);
+    },
+    onNext: function(){
+      return this.evalASFunc(this.currPage.onNext);
+    },
+    onLoad: function() {
+      return this.evalASFunc(this.currPage.onLoad);
+    },
+    onFinish: function() {
+      return true;
+    },
+    onCancel: function() {
+      this.$el.remove();
+      return true;
+    },
+    evalASFunc: function(func, ctx) {
+      var self = this;
+      ctx = ctx || self.currPage;
+
+      return (_.isFunction(func) ? func.apply(ctx, [self]) : func);
+    }
+  });
+
+  return pgBrowser;
+
+});
diff --git a/web/pgadmin/tools/grant_wizard/__init__.py b/web/pgadmin/tools/grant_wizard/__init__.py
new file mode 100644
index 0000000..24c097a
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/__init__.py
@@ -0,0 +1,291 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Implements Grant Wizard"""
+
+import json
+from flask import render_template, request, current_app
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_response as ajax_response, \
+    make_json_response, internal_server_error
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from pgadmin.browser.server_groups.servers.utils import parse_priv_to_db
+from pgadmin.utils import PgAdminModule
+from flask import Response, url_for
+from flask.ext.security import login_required
+from urllib import unquote
+
+# set template path for sql scripts
+template_path = 'grant_wizard/sql'
+MODULE_NAME = 'grant_wizard'
+
+
+class GrantWizardModule(PgAdminModule):
+    """
+    class GrantWizardModule(Object):
+
+        It is a wizard which inherits PgAdminModule
+        class and define methods to load its own
+        javascript file.
+    """
+    def get_own_stylesheets(self):
+        """
+        Returns:
+            list: the stylesheets used by this module.
+        """
+        stylesheets = []
+        for (endpoint, filename) in [
+                ('browser.static', 'css/wizard.css'),
+                ('grant_wizard.static', 'css/grant_wizard.css'),
+                ]:
+            stylesheets.append(url_for(endpoint, filename=filename))
+        return stylesheets
+
+    def get_own_javascripts(self):
+        """"
+        Returns:
+            list: js files used by this module
+        """
+        scripts = []
+        scripts.append({
+            'name': 'pgadmin.tools.grant_wizard',
+            'path': url_for('grant_wizard.index') + 'grant_wizard',
+            'when': None
+        })
+        scripts.append({
+            'name': 'pgadmin.browser.wizard',
+            'path': url_for('browser.static', filename='js/wizard'),
+            'when': None
+        })
+        return scripts
+
+# Create blueprint for GrantWizardModule class
+blueprint = GrantWizardModule(
+    MODULE_NAME, __name__, static_url_path='',
+    url_prefix='/wizard')
+
+
[email protected]("/")
+@login_required
+def index():
+    pass
+
+
[email protected]("/grant_wizard.js")
+@login_required
+def script():
+    """render own javascript"""
+    return Response(response=render_template(
+                "grant_wizard/js/grant_wizard.js", _=gettext),
+                status=200,
+                mimetype="application/javascript")
+
+
[email protected](
+    '/grant_wizard/properties/<int:gid>/<int:sid>/<int:did>'
+    '/<int:node_id>/<node_name>/<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+@login_required
+def properties(gid, sid, did, node_id, node_name, node_type, nspname):
+    """It fetches the properties of object types
+       and render into selection page of wizard
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+    nspname = unquote(nspname)
+
+    res_data = []
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    # Fetch functions against schema
+    if node_type in ['schema']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type=node_type, nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch trigger functions
+    if node_type in ['schema', 'trigger_function']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='trigger_function', nspname=nspname)
+        status, res = conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch Sequences against schema
+    if node_type in ['schema', 'sequence']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='sequence', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+        res_data.extend(res['rows'])
+
+    # Fetch Tables against schema
+    if node_type in ['schema', 'table']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='table', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch Views against schema
+    if node_type in ['schema', 'coll-view']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='coll-view', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    return ajax_response(
+            response=res_data,
+            status=200
+            )
+
+
[email protected](
+    '/grant_wizard/msql/<int:gid>/<int:sid>/<int:did>/<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+def msql(gid, sid, did, node_type, nspname):
+    """
+    This function will return modified SQL
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+    nspname = unquote(nspname)
+
+    data = {}
+    for k, v in request.args.items():
+        try:
+            data[k] = json.loads(v)
+        except ValueError:
+            data[k] = v
+
+    # Form db connection
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    acls = []
+    try:
+        acls = render_template(
+            "/".join([template_path, 'allowed_acl.json'])
+            )
+        acls = json.loads(acls)
+    except Exception as e:
+        current_app.logger.exception(e)
+
+    try:
+
+        # Parse privileges
+        if 'acl' in data:
+            data['acl'] = parse_priv_to_db(data['acl'], acls[node_type]['acl'])
+
+        # Pass database objects and get SQL for privileges
+        SQL = render_template("/".join(
+            [template_path, 'grant.sql']),
+            data=data, nspname=nspname, conn=conn)
+
+        res = {'data': SQL}
+
+        return ajax_response(
+            response=res,
+            status=200
+            )
+
+    except Exception as e:
+        return make_json_response(
+            status=410,
+            success=0,
+            errormsg=e.message
+        )
+
+
[email protected](
+    '/grant_wizard/save/<int:gid>/<int:sid>/<int:did>/'
+    '<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+def save(gid, sid, did, node_type, nspname):
+    """
+    This function will apply the privileges to the selected
+    Database Objects
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+    nspname = unquote(nspname)
+
+    data = request.form if request.form else json.loads(request.data.decode())
+
+    # Form db connection and we use conn to execute sql
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    acls = []
+    try:
+        acls = render_template(
+            "/".join([template_path, 'allowed_acl.json'])
+            )
+        acls = json.loads(acls)
+    except Exception as e:
+        current_app.logger.exception(e)
+
+    try:
+
+        # Parse privileges
+        if 'acl' in data:
+            data['acl'] = parse_priv_to_db(
+                data['acl'],
+                acls[node_type]['acl']
+                )
+
+        # Pass database objects and get SQL for privileges
+        SQL = render_template("/".join(
+            [template_path, 'grant.sql']),
+            data=data, nspname=nspname, conn=conn)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        return make_json_response(
+            success=1,
+            info="Privileges Applied"
+        )
+
+    except Exception as e:
+        return internal_server_error(errormsg=e.message)
diff --git a/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
new file mode 100644
index 0000000..4403c68
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
@@ -0,0 +1,66 @@
+/** Grant Wizard CSS **/
+
+/**
+  CSS to make db object type table
+  fixed so that tbody content may
+  scroll
+ */
+.object_type_table thead tr {
+  position: relative;
+  display: block;
+}
+
+.object_type_table tbody {
+  display: block;
+  overflow-y: scroll;
+  height: 180px;
+}
+
+.object_type_table tbody tr td:nth-child(1),
+.object_type_table thead tr th:nth-child(1) {
+  width: 28px;
+  min-width: 28px;
+}
+
+.object_type_table tbody tr td:nth-child(2),
+.object_type_table thead tr th:nth-child(2) {
+  width: 185px;
+  min-width: 185px;
+}
+
+.object_type_table tbody tr td:nth-child(3),
+.object_type_table thead tr th:nth-child(3) {
+  width: 110px;
+  min-width: 110px;
+}
+
+.object_type_table tbody tr td:nth-child(4),
+.object_type_table thead tr th:nth-child(4) {
+  width: 244px;
+}
+
+/** Override Backgrid filter CSS **/
+.backgrid-filter.form-search {
+  float: left;
+  margin: 0px 5px 5px 0;
+}
+
+/** Custom styling for Codemirror field **/
+.wizard-right-panel_content {
+  border: 1px solide #ccc;
+}
+
+.wizard-right-panel_content .CodeMirror {
+  border: 1px solid #ccc;
+  height: 285px !important;
+  min-height: 285px !important;
+}
+
+.wizard-right-panel_content .CodeMirror-linenumber {
+  background: #F7F7F7;
+  border-right: 1px solid #DDDDDD;
+}
+
+.wizard-right-panel_content .CodeMirror-gutters {
+  min-height: 285px !important;
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
new file mode 100644
index 0000000..90287b2
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
@@ -0,0 +1,967 @@
+define([
+      'jquery', 'underscore', 'underscore.string', 'alertify',
+      'pgadmin.browser', 'backbone', 'backgrid', 'pgadmin.browser.node',
+      'backgrid.select.all', 'backgrid.filter', 'backbone.paginator',
+      'backgrid.paginator', 'pgadmin.browser.server.privilege',
+      'pgadmin.browser.wizard',
+      ],
+
+function($, _, S, alertify, pgBrowser, Backbone, Backgrid, pgNode) {
+
+  if (pgBrowser.GrantWizard) {
+    return pgBrowser.GrantWizard;
+  }
+
+  /**
+    It is sub model for field "Objects". It has fields
+    for database object types such as Schemas, Views and
+    Sequence etc.
+  */
+  var DatabaseObjectModel = pgNode.Model.extend({
+    defaults: {
+      selected: false,
+      icon: 'icon-unknown',
+      name: undefined,
+      nspname: undefined,
+      proargs: undefined,
+      object_type: undefined,
+      object_id: undefined
+    },
+    idAttribute: 'object_id',
+    parse: function(res) {
+
+      // Create unique object id
+      res.object_id = res.object_type + res.nspname + '"' + res.name;
+
+      return res;
+    },
+
+    validate: function() {
+
+      /*
+      * Triggers error messages for object types "selected"
+      * if it is empty/undefined/null
+      */
+      var err = {},
+        errmsg,
+        node = this.get('objects').toJSON();
+      if (_.isEmpty(node)) {
+        err['selected'] = '{{ _("Please select any database object type") }}';
+        errmsg = errmsg || err['selected'];
+        this.errorModel.set('selected', errmsg);
+        return errmsg;
+      } else {
+        this.errorModel.unset('selected');
+      }
+      return null;
+    }
+  });
+
+  // Define privileges for various types of nodes
+  var privDict = {
+    'schema': ['a', 'r', 'w', 'd', 'D', 'x', 't', 'U', 'X'],
+    'coll-function': ['X'],
+    'coll-sequence': ['a', 'w', 'U'],
+    'coll-table':['a', 'w', 'U', 'd', 'D', 'x', 't'],
+    'coll-view': ['a', 'r', 'w', 'd', 'D', 'x', 't'],
+  };
+
+  // Define columns for the Db Object Types Grid
+  var columns = [{
+    name: "selected",
+
+    /*
+     Override render method of Backgrid.Extension.SelectRowCell
+     class. It has an issue: It doesn't mark rows checked if we move to next
+     page and then go back to previous page. but it must show.
+     so we handle this case by overriding the render method.
+    */
+    cell: Backgrid.Extension.SelectRowCell.extend({
+      render: function() {
+
+        // Use the Backform Control's render function
+        Backgrid.Extension.SelectRowCell.prototype.render.apply(this, arguments);
+
+        var col = this.column.get('name');
+        if (this.model && this.model.has(col)) {
+          if (this.model.get(col)) {
+            this.checkbox().prop("checked", true);
+            this.$el.parent().toggleClass("selected", true);
+            this.model.trigger("backgrid:selected", this.model, true);
+          }
+        }
+        return this;
+      }
+    }),
+
+    headerCell: "select-all",
+
+  },{
+    name: "object_type",
+    label: "Object Type",
+    editable: false,
+    cell: Backgrid.Cell.extend({
+      render: function() {
+
+        // Override render to add icon to Db Object column
+        Backgrid.Cell.prototype.render.apply(this, arguments);
+        this.$el.addClass(this.model.get('icon')).css({"padding-left": "22px"});
+
+        return this;
+      }
+    })
+  },{
+    name: "nspname",
+    label: "Schema",
+    cell: "string",
+    editable: false
+  },{
+    name: "name",
+    label: "Name",
+    cell: "string",
+    editable: false
+  }];
+
+  // Create an Object GrantWizard of pgBrowser class
+  pgBrowser.GrantWizard  = {
+    init: function() {
+      if (this.initialized) {
+        return;
+      }
+
+      this.initialized = true;
+
+      // Define list of nodes on which grant wizard option appears
+      var supported_nodes = [
+            'schema', 'coll-function',
+            'coll-sequcene', 'coll-table',
+            'coll-view'
+          ],
+
+          /**
+            Enable/disable grantwizard menu in tools based
+            on node selected
+            if selected node is present in supported_nodes,
+            menu will be enabled otherwise disabled.
+           */
+          menu_enabled = function(obj) {
+            if(!_.isUndefined(obj) && !_.isNull(obj))
+              return (_.indexOf(supported_nodes, obj._type) !== -1 ? true: false);
+            else
+              return false;
+          };
+
+      // Define the nodes on which the menus to be appear
+      var menus = [{
+        name: 'grant_wizard_schema', module: this,
+        applies: ['tools'], callback: 'start_grant_wizard',
+        priority: 10, label: '{{_("Grant Wizard...") }}',
+        icon: 'wcTabIcon', enable: menu_enabled
+      }];
+
+      // Add supported menus into the menus list
+      for (var idx = 0; idx < supported_nodes.length; idx++) {
+        menus.push({
+          name: 'grant_wizard_schema_context_' + supported_nodes[idx],
+          node: supported_nodes[idx], module: this,
+          applies: ['context'], callback: 'start_grant_wizard',
+          priority: 10, label: '{{_("Grant Wizard...") }}',
+          icon: 'wcTabIcon', enable: menu_enabled
+          });
+      }
+      pgAdmin.Browser.add_menus(menus);
+
+      return this;
+    },
+
+    // Callback to draw Wizard Dialog
+    start_grant_wizard: function(action, item) {
+      if (!alertify.wizardDialog) {
+        alertify.dialog('wizardDialog', function factory() {
+          return {
+            main:function(title) {
+              this.set('title', title);
+            },
+            setup:function() {
+              return {
+                options: {
+                  frameless: true,
+                  resizable: false,
+                  autoReset: false,
+                  maximizable: false,
+                  closableByDimmer: false
+                }
+              };
+            },
+            hooks:{
+              onshow: function() {
+
+                // Set dimensions for wizard
+                this.elements.dialog.style.width = '100%';
+                this.elements.dialog.style.height = '540px';
+              },
+            },
+            build:function() { },
+
+            /**
+              Returns a Collection used for rendering grid
+              and Pagination
+
+              @class {Backbone.PageableCollection}
+              @param {variable} baseUrl - from which data is fetched
+              @param {Backbone.Model} DatabaseObjectModel
+              @return {Object} coll
+            */
+            getPageableCollection: function(baseUrl){
+              var Coll = Backbone.PageableCollection.extend({
+                model: DatabaseObjectModel,
+                url: baseUrl,
+                mode: "client",
+                state: {
+                  pageSize: 100,
+                  sortKey: "object_type",
+                  order: -1
+                },
+                queryParams: {
+                  totalPages: null,
+                  totalRecords: null,
+                  sortKey: "sort"
+                }
+              });
+              return new Coll();
+            },
+
+            /**
+              Returns a Paginator Class Object which is again to be rendered
+
+              @class {Backgrid.Extension.Paginator}
+              @param {Backbone.Collection} coll - from which data is fetched
+              @return {Object} paginator
+            */
+            DbPaginator: function(coll){
+              var paginator = this.paginator = new Backgrid.Extension.Paginator({
+                collection: coll,
+                windowSize: 8
+              });
+              return paginator;
+            },
+
+            /**
+              Create new Filter which will filter the
+              rendered grid for Select Type Tabular Data
+              @param {Backbone.PageableCollection} coll
+            */
+            DbObjectFilter: function(coll){
+              var clientSideFilter = this.clientSideFilter = new Backgrid.Extension.ClientSideFilter({
+                collection: coll,
+                placeholder: _('Search by object type or name'),
+
+                // The model fields to search for matches
+                fields: ['object_type', 'name'],
+
+                // How long to wait after typing has stopped before searching can start
+                wait: 150
+              });
+              return clientSideFilter;
+            },
+
+            //Enable Disable Next button of Page 2
+            updateButtons: function(modified){
+              if(!modified)
+                $('.wizard-next').prop('disabled', true);
+              else
+                $('.wizard-next').prop('disabled', false);
+            },
+
+            /**
+              Callback called when an errorModel is set
+              with invalid value and errormsg is set into
+              status bar element and next button is disabled
+             */
+            onSessionInvalid: function(msg) {
+              $('.error_msg_div p').html(msg).removeClass("hide");
+
+              // Enable disable Next button
+              this.updateButtons(false);
+              return true;
+            },
+
+            /**
+              Callback called when anything is set into model
+              thus hide error msg element and enable next button
+              status bar element and next button is disabled
+             */
+            onSessionValidated: function(sessHasChanged) {
+              $('.error_msg_div p').empty().addClass("hide");
+
+              // Enable disable Next button
+              this.updateButtons(sessHasChanged);
+            },
+
+            /*
+             Remove/Delete objects, attributes
+             in wizard on wizard close or finish
+             to reclaim memory
+            */
+            releaseObjects: function(){
+              var self = this;
+
+              if(!_.isUndefined(self.dbObjectFilter)) {
+                self.dbObjectFilter.remove();
+                self.dbObjectFilter = undefined;
+              }
+
+              if(!_.isUndefined(self.clientSideFilter)) {
+                self.clientSideFilter.remove();
+                self.clientSideFilter = undefined;
+              }
+
+              if(!_.isUndefined(self.paginator)) {
+                self.paginator.remove();
+                self.paginator = undefined;
+              }
+
+              // Delete Wizard Pages
+              if(!_.isUndefined(self.dbObjectTypePage) &&
+                !_.isNull(self.dbObjectTypePage)) {
+                if(!_.isUndefined(self.dbObjectTypePage.get('model')) &&
+                  !_.isNull(self.dbObjectTypePage.get('model'))) {
+                  self.dbObjectTypePage.get('model').clear();
+                  self.dbObjectTypePage = undefined;
+                }
+              }
+
+              if(!_.isUndefined(self.privilegePage) &&
+                !_.isNull(self.privilegePage)) {
+                if(!_.isUndefined(self.privilegePage.get('model')) &&
+                  !_.isNull(self.privilegePage.get('model'))) {
+                  self.privilegePage.get('model').clear();
+                  self.privilegePage = undefined;
+                }
+              }
+
+              if(!_.isUndefined(self.reviewSQLPage) &&
+                !_.isNull(self.reviewSQLPage)) {
+                if(!_.isUndefined(self.reviewSQLPage.get('model')) &&
+                  !_.isNull(self.reviewSQLPage.get('model'))) {
+                  self.reviewSQLPage.get('model').clear();
+                  self.reviewSQLPage = undefined;
+                }
+              }
+
+              // Remove collection containing db object data
+              if(!_.isNull(self.coll) &&
+                !_.isUndefined(self.coll)) {
+                self.coll.reset();
+                self.coll = undefined;
+              }
+
+              // Delete Wizard
+              if(!_.isNull(self.wizard) &&
+                !_.isUndefined(self.wizard)) {
+                self.wizard.collection.reset();
+                self.wizard.curr_page = undefined;
+                self.wizard.el = undefined;
+                self.wizard = undefined;
+              }
+
+            },
+
+            /**
+              Every time a wizard is opened, this function
+              is called everytime. It has Wizard Pages which
+              are rendered by the Wizard Class:
+
+              @class {pgBrowser.WizardPage} dbObjectType1 - This page
+              @extends {Backbone.Model}
+              renders a grid of Database Object Types such as
+                Schemas, Views and Sequences etc.
+
+              @class {pgBrowser.WizardPage} WizardPage2 - This page
+              @extends {Backbone.Model}
+              adds Privilege Control which provides grant privileges
+              such as "Create, Insert, Delete, Update" so on the
+              database objects selected on Wizard Pages.
+
+              @class {pgBrowser.WizardPage} WizardPage3 - This page
+              displays the generated GRANT SQL query for the Db
+              objects selected with the specific privileges added to it.
+              @extends {Backbone.Model}
+
+              @class {Backbone.Collection} WizardCollection - It is the
+              collection of wizard pages
+
+              @class {pgBrowser.Wizard} wizard - Its task is:
+              - Create a Wizard
+              - Add Buttons, Callbacks to it.
+              - Render WizardPages
+              @extends {Backbone.View}
+
+            */
+            prepare:function() {
+
+              // Remove extra container
+              if($('.ajs-content').size() > 1){
+                $('.ajs-content')[0].remove();
+              }
+
+              // Generate wizard main container
+              var wizard_html = '<div class="wizard_dlg">',
+                  wizard_parent = $('.ajs-content').empty().append(wizard_html),
+                  el = $(".ajs-content");
+
+              // Extract the data from the selected tree node
+              var t = pgBrowser.tree,
+                  i = t.selected(),
+                  d = i && i.length == 1 ? t.itemData(i) : undefined,
+                  info = pgBrowser.Node.getTreeNodeHierarchy(i),
+                  icon = d.icon;
+
+              /**
+                Generate a URL using:
+                gid, did, sid(server id), node_id(node id),
+                node_(node name), node_type(node type)
+                and pass it to collection which will fetch Object Type properties.
+               */
+              var gid = info['server-group']._id,
+                  sid = info.server._id,
+                  did = info.database._id,
+                  node_id = d._id,
+                  nspname = info.schema.label;
+                  node_type = d._type,
+                  node_label = d.label,
+
+                  // Collection url to fetch database object types for objects field
+                  baseUrl = "{{ url_for('grant_wizard.index') }}" + "grant_wizard/properties/" +
+                      S('%s/%s/%s/%s/%s/%s/%s/').sprintf(
+                          encodeURI(gid), encodeURI(sid), encodeURI(did),
+                          encodeURI(node_id), encodeURI(node_label),
+                          encodeURI(node_type), encodeURI(nspname)).value();
+
+                  // Model's save url
+                  saveUrl = "{{ url_for('grant_wizard.index') }}" + "grant_wizard/save/" +
+                      S('%s/%s/%s/%s/%s/').sprintf(
+                          encodeURI(gid), encodeURI(sid), encodeURI(did),
+                          encodeURI(node_type), encodeURI(nspname)).value(),
+
+                  // generate encoded url based on wizard type
+                  msql_url = this.msql_url = "/wizard/grant_wizard/msql/"+
+                    S('%s/%s/%s/%s/%s/').sprintf(
+                        encodeURI(gid), encodeURI(sid), encodeURI(did),
+                        encodeURI(node_type), encodeURI(nspname)).value(),
+
+                  // Create instances of collection, pagination and filter
+                  coll = this.coll = this.getPageableCollection(baseUrl),
+                  paginator = this.paginator = this.DbPaginator(coll),
+                  dbObjectFilter = this.dbObjectFilter = this.DbObjectFilter(coll);
+
+              /**
+                It is the main model with schema defined
+                Every time a new wizard is opened,
+                a new model should create.
+               */
+              var GrantWizardModel = pgNode.Model.extend({
+                defaults: {
+                  objects: undefined,
+                  acl: undefined
+                },
+                schema: [
+                  {
+                    id: 'objects', label: '{{ _("Objects") }}', model: DatabaseObjectModel,
+                    type: 'collection', group: 'Objects'
+                  },
+                  {
+                    id: 'acl', label: '{{ _("Privileges") }}', node: 'role',
+                    model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend({
+
+                    // privileges are selected based on node clicked
+                      privileges: privDict[node_type]
+                    }), uniqueCol : ['grantee', 'grantor'], editable: true,
+                    type: 'collection', group: 'Privileges', canAdd: true,
+                    canDelete: true, control: 'unique-col-collection'
+                  }
+                ],
+                urlRoot: saveUrl
+              });
+
+              /**
+                Create instance of GrantWizard Model, provide urlRoot
+                node_info object, Generate fields objects
+               */
+              var self = this,
+                  newModel = new GrantWizardModel({}, { node_info: info }),
+                  fields = Backform.generateViewSchema(
+                     info, newModel, 'create', d._type, d
+                     );
+
+              /**
+                Fetch data from server and set into grid
+                and show/hide progress bar
+               */
+              $('.wizard-progress-bar p').show();
+
+              coll.fetch({
+                success: function(collection, data) {
+                  $('.wizard-progress-bar p').html('');
+                },
+                reset: true
+              }, this);
+
+              /**
+                Override backgrid listener "backgrid:selected" to
+                Add/Remove model to/from objects collection
+               */
+              coll.on('backgrid:selected', function(model, selected) {
+                model.set('selected', selected);
+
+                /**
+                  if a row (checkbox) is checked, add that model
+                  into collection, when unchecked remove it from
+                  model.
+                 */
+                if(selected)
+                  newModel.get('objects').add(model, { silent: true });
+                else
+                  newModel.get('objects').remove(model);
+
+                // validate model on checkbox check/uncheck
+                var msg =  model.validate.call(newModel);
+
+                /**
+                  If no object type is selected, set error msg
+                  and disable next button, else enable next button
+                 */
+                if(msg)
+                  self.onSessionInvalid.call(self, msg);
+                else
+                  self.onSessionValidated.call(self, true);
+              });
+
+              // To track changes into model, start new session
+              newModel.startNewSession();
+
+              // Add event listener for privileges control
+              newModel.on('pgadmin-session:valid', self.onSessionValidated.bind(this));
+              newModel.on('pgadmin-session:invalid', self.onSessionInvalid.bind(this));
+
+              //////////////////////////////////////////////////////////////////////
+              //                                                                  //
+              //            Wizard Page for Db Object Type                        //
+              //                                                                  //
+              //////////////////////////////////////////////////////////////////////
+
+              /**
+                Create wizard page. It renders a grid of
+                Database Object Types such as
+                Schemas, Views and Sequences etc.
+                Set default values
+               */
+              var dbObjectTypePage = self.dbObjectTypePage = new pgBrowser.WizardPage({
+                id: 1,
+                page_title: 'Object Selection (step 1 of 3)',
+                disable_prev: true,
+                disable_next: true,
+                show_description: _('Please select objects from the below list.'),
+                show_progress_bar: _('Please wait while fetching records...'),
+                model: newModel,
+                view: new (function() {
+
+                  // Set page Instance
+                  var pageView = this;
+
+                  _.extend(pageView, {
+
+                    // Remove grid if it is before render
+                    cleanup: function() {
+                      if (this.grid) {
+                        this.grid.remove();
+                        delete this.grid;
+                        this.grid = null;
+                      }
+
+                      // Remove grid element if exists
+                      if (this.el) {
+                        $(this.el).remove();
+                        delete this.el;
+                      }
+                    },
+
+                    // Delete grid before render
+                    grid: null,
+
+                    render: function() {
+
+                      // Create a grid container
+                      var gridBody =
+                            $('<div class="select_db_objects_container"></div>');
+
+                      // Remove grid if exits before render
+                      if (this.grid) {
+                        this.cleanup();
+                      }
+
+                      // Initialize a new Grid instance
+                      this.grid = new Backgrid.Grid({
+                        columns: _.clone(columns),
+                        collection: coll,
+                        className: "backgrid table-bordered object_type_table"
+                        });
+
+                      // Render selection Type grid and paginator
+                      gridBody.append(
+                        this.grid.render().$el
+                        ).append(self.paginator.render().el);
+
+                      // Render Search Filter
+                      gridBody.prepend(
+                        self.clientSideFilter.render().el);
+
+                      // Assign gridBody content to page element
+                      this.el = gridBody;
+
+                      /**
+                        Fetch selected models from collection and
+                        make rows checked in grid
+                       */
+                      newModel.get('objects').each(function(m) {
+                        var model = coll.get(m.get('object_id'));
+                        if (model) {
+                          coll.trigger('backgrid:selected', model, true);
+                        }
+                      });
+
+                      // Refresh grid to re render rows.
+                      coll.trigger('backgrid:refresh');
+
+                      return this;
+                    }
+                  });
+                }),
+
+                beforeNext: function(obj){
+                  var self = this;
+                  obj.options.disable_next = true;
+
+                  /**
+                    Enable/Disable next button of privilegePage if objects
+                    are present in model
+                   */
+                  if(!_.isNull(newModel.get('acl')) &&
+                    !_.isUndefined(newModel.get('acl'))) {
+                    if(newModel.get('acl').length > 0)
+                      obj.collection.at(1).set('disable_next', false);
+                    }
+
+                  // Clean the view
+                  if (self.view) {
+                    self.view.cleanup();
+                    delete self.view;
+                    self.view = null;
+                  }
+                  return true;
+                },
+
+              });
+
+              //////////////////////////////////////////////////////////////////////
+              //                                                                  //
+              //            Wizard Page for Privilege Control                     //
+              //                                                                  //
+              //////////////////////////////////////////////////////////////////////
+
+              /**
+                Create Field Object which has properties like
+                node_data, node_info which is required for rendering
+                Privilege control
+               */
+              var privilegesField = new Backform.Field(fields[1].fields[0]);
+
+              // Wizard for Privelege control
+              var privilegePage = self.privilegePage = new pgBrowser.WizardPage({
+                id: 2,
+                page_title: _('Privileges Selection (step 2 of 3)'),
+                show_description: _('Please select privileges for the selected objects.'),
+                disable_next: true,
+                model: newModel,
+
+                // Create a view function object
+                view: new (function() {
+                  var pageView = this;
+                    _.extend(pageView, {
+
+                      // Render Privelege control to generate its html markup
+                      render: function() {
+                        this.privControl = new (privilegesField.get('control')) ({
+                          field: privilegesField,
+                          model: newModel
+                        });
+                        return {el: this.privControl.render().$el};
+                      },
+
+                      // Remove the privilege control
+                      cleanup: function() {
+                        if (this.privControl) {
+                          this.privControl.remove();
+                          delete this.privControl;
+                          this.privControl = null;
+                        }
+                      }
+                    });
+                }),
+
+                beforePrev: function(wizardObj) {
+
+                  // Remove the privilege control
+                  if (this.view) {
+                    this.view.cleanup();
+                    delete this.view;
+                    this.view = null;
+                  }
+
+                  /**
+                    Enable/Disable next button of page 1 if objects
+                    are present in model
+                   */
+                  var objectsModel = newModel.get('objects');
+
+                  if(!_.isUndefined(objectsModel) && !_.isEmpty(objectsModel) &&
+                      objectsModel.length > 0) {
+                    wizardObj.collection.at(0).set('disable_next', false);
+
+                    // Don't show progress bar
+                    wizardObj.collection.at(0).set('show_progress_bar', '');
+                  }
+
+                  /**
+                    We're re-rendering the controls as they are deleted
+                    before heading to next page
+                    Refresh Backgrid to re-render the elements selected
+                    re-render paginator
+                    re-render Filter
+                   */
+                  newModel.trigger("backgrid:refresh", newModel, false);
+                  self.paginator.render();
+                  self.clientSideFilter.render();
+                  return true;
+                },
+
+                beforeNext: function() { return true; },
+
+                onNext: function(obj){
+
+                  // Remove the privilege control
+                  if (this.view) {
+                    this.view.cleanup();
+                    delete this.view;
+                    this.view = null;
+                  }
+
+                  // Enable finish button
+                  self.wizard.options.disable_finish = false;
+
+                  /**
+                    triggers to get SQL queries data to render
+                    into the reviewSQLPage
+                   */
+                  newModel.trigger('pgadmin-wizard:nextpage:sql', {'node_type': node_type });
+                }
+              });
+
+
+              //////////////////////////////////////////////////////////////////////
+              //                                                                  //
+              //            Review SQL Query Page                                 //
+              //                                                                  //
+              //////////////////////////////////////////////////////////////////////
+
+              //Create SqlField Object
+              var sqlField = new Backform.Field(
+                  {
+                    id: 'sqltab',
+                    label: _('Sql Tab'),
+                    field: fields,
+
+                    /**
+                      Extend 'SqlTabControl' to define new
+                      function 'onWizardNextPageChange'
+                      which gets triggered on next button
+                      click to fetch generated SQL query
+                      for the selected db objects.
+                     */
+                    control: Backform.SqlTabControl.extend({
+                      initialize: function() {
+
+                        // Initialize parent class
+                        Backform.SqlTabControl.prototype.initialize.apply(this, arguments);
+
+                        this.msql_url = self.msql_url;
+
+                        // define trigger events for prev and next page
+                        this.model.on('pgadmin-wizard:nextpage:sql', this.onWizardNextPageChange, this);
+                        this.model.on('pgadmin-wizard:prevpage:sql', this.onWizardPrevPageChange, this);
+                      },
+
+                      // This method fetches the modified SQL for the wizard
+                      onWizardNextPageChange: function(){
+
+                        var self = this;
+
+                        // Fetches modified SQL
+                        $.ajax({
+                          url: this.msql_url,
+                          type: 'GET',
+                          cache: false,
+                          data: self.model.toJSON(true, 'GET'),
+                          dataType: "json",
+                          contentType: "application/json"
+                        }).done(function(res) {
+                          self.sqlTab.clearHistory();
+                          self.sqlTab.setValue(res.data);
+                        }).fail(function() {
+                          self.model.trigger('pgadmin-view:msql:error');
+                        }).always(function() {
+                          self.model.trigger('pgadmin-view:msql:fetched');
+                        });
+                      },
+
+                      remove: function() {
+
+                        // Stop listen to next/prev page events
+                        this.model.off('pgadmin-wizard:nextpage:sql', this.onWizardNextPageChange, this);
+                        this.model.off('pgadmin-wizard:prevpage:sql', this.onWizardPrevPageChange, this);
+                        Backform.SqlTabControl.__super__.remove.apply(this, arguments);
+                      }
+
+                    })
+                  }),
+
+                /**
+                  Create sqlField view instance
+                  to render it into wizard page
+                 */
+                sqlControl = new (sqlField.get('control'))({
+                  field: sqlField,
+                  model: newModel
+                });
+
+              // Wizard for SQL tab control
+              var reviewSQLPage = self.reviewSQLPage = new pgBrowser.WizardPage({
+                id: 3,
+                page_title: _('Final (Review Selection) (step 3 of 3)'),
+                show_description: _('Following query will be executed on the database'
+                              +' server for the selected objects, and privileges.'
+                              +' Please click on Finish to complete the process.'),
+                model: newModel,
+                view: new(function() {
+
+                  // Render SqlTab control to generate its html markup
+                  var sqlTabHtml = sqlControl.render().$el;
+                  this.render = function() {
+                      return { el: sqlTabHtml };
+                  };
+                }),
+
+                beforePrev: function(wizardObj) {
+
+                  /**
+                    Enable next button if privilege
+                    model is not empty else disable
+                    next button
+                    */
+                  var aclModel = newModel.get('acl');
+
+                  if(!_.isUndefined(wizardObj.collection) &&
+                    wizardObj.collection.models.length > 0) {
+                    if(!_.isUndefined(aclModel) && !_.isEmpty(aclModel) &&
+                        aclModel.length > 0) {
+                      wizardObj.collection.at(1).set('disable_next', false);
+                    }
+                    else {
+                      wizardObj.collection.at(1).set('disable_next', true);
+                    }
+
+                    return true;
+                  }
+                },
+              });
+
+
+              // Create Wizard Collection of Wizard Pages
+              var WizardCollection = Backbone.Collection.extend({
+                model: pgBrowser.WizardPage
+              });
+
+              // It holds all the wizard pages to be rendered
+              this.wizardCollection = new WizardCollection(
+                [dbObjectTypePage, privilegePage, reviewSQLPage]
+              );
+
+              /**
+                Create wizard which has following operations:
+                - renders wizard pages
+                - defines the first page to render in wizard
+                - Save the model on finishbutton
+                - Remove wizard on cancel button
+                */
+              var wizard = self.wizard = new (pgBrowser.Wizard.extend({
+                options: {
+                  title: 'Grant Wizard', /* Main Wizard Title */
+                  width: '',
+                  height: '',
+                  curr_page: 0,
+                  show_left_panel: false,
+                  disable_finish: true
+                },
+
+                onFinish: function() {
+                  var m = newModel,
+                      d = m.toJSON('GET');
+
+                  if (d && !_.isEmpty(d) && !_.isUndefined(d.objects)) {
+                    m.save({}, {
+                      attrs: d,
+                      validate: false,
+                      cache: false,
+                      success: function(res) {
+
+                        // Release wizard objects
+                        self.releaseObjects();
+                        self.close();
+                      },
+                      error: function(m, jqxhr) {
+                        alertify.pgNotifier(
+                          "error", jqxhr,
+                          S(
+                            "{{ _('Error during saving properties - %%s!') }}"
+                            ).sprintf(jqxhr.statusText).value()
+                          );
+                      }
+                    });
+                  }
+                },
+                onCancel: function() {
+
+                  // Release wizard objects
+                  self.releaseObjects();
+                  self.close();
+                }
+              })) ({
+                collection: this.wizardCollection,
+                el: el,
+                model: newModel
+              });
+
+              // Render wizard
+              wizard.render();
+            }
+          };
+        });
+      }
+
+      // Call Grant Wizard Dialog
+      alertify.wizardDialog('');
+    }
+  };
+
+  return pgBrowser.GrantWizard;
+});
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/allowed_acl.json b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/allowed_acl.json
new file mode 100644
index 0000000..cb73d2c
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/allowed_acl.json
@@ -0,0 +1,30 @@
+{# List of allowed privileges of Schema, table sequence, function and view #}
+{#
+    Format for allowed privileges are:
+    "node_name": {
+        "type": "name",
+        "acl": [...]
+    }
+#}
+{
+    "schema": {
+        "type": "SCHEMA",
+        "acl": ["a", "r", "w", "d", "D", "x", "t", "U", "X"]
+    },
+    "coll-table": {
+        "type": "TABLE",
+        "acl": ["a", "w", "U", "d", "D", "x", "t"]
+    },
+    "coll-sequence": {
+        "type": "SEQUENCE",
+        "acl": ["a", "w", "u"]
+    },
+    "coll-function": {
+        "type": "FUNCTION",
+        "acl": ["X"]
+    },
+    "coll-view": {
+        "type": "VIEW",
+        "acl": ["a", "r", "w", "d", "D", "x", "t"]
+    }
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/grant.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/grant.sql
new file mode 100644
index 0000000..02ac1a7
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/grant.sql
@@ -0,0 +1,19 @@
+{#=====Grant Permissions on Database Objects Selected====#}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% import 'macros/functions/privilege.macros' as PRIVILEGE_FUNCTION %}
+{% if data.acl %}
+{% for obj in data.objects %}
+{% for priv in data.acl %}
+{% set obj_type = obj.object_type %}
+{% if obj_type.upper() != 'FUNCTION' and obj_type.upper() != 'TRIGGER FUNCTION' %}
+{% if obj_type.upper() == 'VIEW' %} {###### Views are also kind of table ######}
+{% set obj_type = 'TABLE' %}
+{% endif %}
+{{ PRIVILEGE.SET(conn, obj_type.upper(), priv.grantee, obj.name, priv.without_grant, priv.with_grant, nspname ) }}
+{% else %}
+{###### if object_type is Function then apply function marcros ######}
+{{ PRIVILEGE_FUNCTION.SET(conn, 'FUNCTION', priv.grantee, obj.name, priv.without_grant, priv.with_grant, nspname, obj.proargs)}}
+{% endif %}
+{% endfor %}
+{% endfor %}
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/properties.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/properties.sql
new file mode 100644
index 0000000..7a33c1f
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/properties.sql
@@ -0,0 +1,65 @@
+{#===================Fetch list of Database object types(Functions, Sequence, Tables or View)===================#}
+{% if type == 'schema' or type == 'trigger_function' and node_id and node_name %}
+{% set func_type = 'Trigger Function' if type == 'trigger_function' else 'Function' %}
+SELECT
+    pr.proname AS name,
+    pg_get_function_identity_arguments(pr.oid) AS proargs,
+    '{{ func_type }}' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-function' AS icon
+FROM pg_proc pr
+    JOIN pg_type typ ON typ.oid=prorettype
+    JOIN pg_namespace typns ON typns.oid=typ.typnamespace
+    JOIN pg_language lng ON lng.oid=prolang
+    LEFT OUTER JOIN pg_description des ON (des.objoid=pr.oid AND des.classoid='pg_proc'::regclass)
+WHERE proisagg = FALSE AND pronamespace = {{ node_id }}::oid
+    AND typname {{ 'NOT' if type != 'trigger_function' else '' }} IN ('trigger', 'event_trigger')
+ORDER BY proname
+{% endif %}
+
+{% if type == 'sequence' and node_id and node_name %}
+SELECT
+    cl.relname AS name,
+    'Sequence' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-sequence' AS icon
+FROM pg_class cl
+    LEFT OUTER JOIN pg_description des ON (des.objoid=cl.oid AND des.classoid='pg_class'::regclass)
+WHERE relkind = 'S' AND relnamespace  = {{ node_id }}::oid
+ORDER BY cl.relname
+{% endif %}
+
+{% if type == 'table' and node_id and node_name %}
+SELECT rel.relname AS name,
+    'Table' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-table' AS icon
+FROM pg_class rel
+    LEFT OUTER JOIN pg_tablespace spc ON spc.oid=rel.reltablespace
+    LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass)
+    LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'
+    LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid
+    LEFT JOIN pg_type typ ON rel.reloftype=typ.oid
+WHERE rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ node_id }}::oid
+ORDER BY rel.relname
+{% endif %}
+
+{% if type == 'coll-view' and node_id and node_name %}
+SELECT
+    c.relname AS name,
+    'View' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-coll-view' AS icon
+FROM pg_class c
+    LEFT OUTER JOIN pg_tablespace spc ON spc.oid=c.reltablespace
+    LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass)
+    LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid
+WHERE ((c.relhasrules AND (EXISTS (
+    SELECT r.rulename
+    FROM pg_rewrite r
+    WHERE ((r.ev_class = c.oid)
+        AND (bpchar(r.ev_type) = '1'::bpchar)) ))
+       ) OR (c.relkind = 'v'::char))
+AND c.relnamespace = {{ node_id }}::oid
+ORDER BY c.relname
+{% endif %}


^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-03-08 13:27  Dave Page <[email protected]>
  parent: Surinder Kumar <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Dave Page @ 2016-03-08 13:27 UTC (permalink / raw)
  To: Surinder Kumar <[email protected]>; +Cc: pgadmin-hackers

On Tue, Mar 8, 2016 at 10:00 AM, Surinder Kumar
<[email protected]> wrote:
> Hi,
>
> I forgot to add 'node.ui.js' file in the previous patch, please ignore the
> previous patch.
> Attached is the new patch,
>
> Please review the patch and let me know for any comments.

Is this dependent on any other patches? The menu option never seems to
be enabled for me when I test against git-master.

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers



^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-03-08 13:48  Surinder Kumar <[email protected]>
  parent: Dave Page <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Surinder Kumar @ 2016-03-08 13:48 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: pgadmin-hackers

The menu option is dependent only on certain nodes like:

1. Schema Node
2. Views Collection Node
3. Tables Collection Node
4. Sequences Collection Node
5. Functions Collection Node

It will be enabled on click of above listed nodes.

On Tue, Mar 8, 2016 at 6:57 PM, Dave Page <[email protected]> wrote:

> On Tue, Mar 8, 2016 at 10:00 AM, Surinder Kumar
> <[email protected]> wrote:
> > Hi,
> >
> > I forgot to add 'node.ui.js' file in the previous patch, please ignore
> the
> > previous patch.
> > Attached is the new patch,
> >
> > Please review the patch and let me know for any comments.
>
> Is this dependent on any other patches? The menu option never seems to
> be enabled for me when I test against git-master.
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>


^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-03-09 17:34  Dave Page <[email protected]>
  parent: Surinder Kumar <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Dave Page @ 2016-03-09 17:34 UTC (permalink / raw)
  To: Surinder Kumar <[email protected]>; +Cc: pgadmin-hackers

Sorry Surinder - can you rebase this please? I think it was broken by
8a7ec6b45221f042bc39c9bce2c577e12b43cc3a :-(

How much work would it be to enable it to work at database level as well?

On Tue, Mar 8, 2016 at 1:48 PM, Surinder Kumar
<[email protected]> wrote:
> The menu option is dependent only on certain nodes like:
>
> 1. Schema Node
> 2. Views Collection Node
> 3. Tables Collection Node
> 4. Sequences Collection Node
> 5. Functions Collection Node
>
> It will be enabled on click of above listed nodes.
>
> On Tue, Mar 8, 2016 at 6:57 PM, Dave Page <[email protected]> wrote:
>>
>> On Tue, Mar 8, 2016 at 10:00 AM, Surinder Kumar
>> <[email protected]> wrote:
>> > Hi,
>> >
>> > I forgot to add 'node.ui.js' file in the previous patch, please ignore
>> > the
>> > previous patch.
>> > Attached is the new patch,
>> >
>> > Please review the patch and let me know for any comments.
>>
>> Is this dependent on any other patches? The menu option never seems to
>> be enabled for me when I test against git-master.
>>
>> --
>> Dave Page
>> Blog: http://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EnterpriseDB UK: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>
>



-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers



^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-03-10 09:22  Surinder Kumar <[email protected]>
  parent: Dave Page <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Surinder Kumar @ 2016-03-10 09:22 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: pgadmin-hackers

Hi,

PFA patch after rebasing.

On Wed, Mar 9, 2016 at 11:04 PM, Dave Page <[email protected]> wrote:

> Sorry Surinder - can you rebase this please? I think it was broken by
> 8a7ec6b45221f042bc39c9bce2c577e12b43cc3a :-(


>
How much work would it be to enable it to work at database level as well?

It depends on whether we need to enable grant wizard for all nodes under
database level or
enable it for specific nodes under database level.

>
> On Tue, Mar 8, 2016 at 1:48 PM, Surinder Kumar
> <[email protected]> wrote:
> > The menu option is dependent only on certain nodes like:
> >
> > 1. Schema Node
> > 2. Views Collection Node
> > 3. Tables Collection Node
> > 4. Sequences Collection Node
> > 5. Functions Collection Node
> >
> > It will be enabled on click of above listed nodes.
> >
> > On Tue, Mar 8, 2016 at 6:57 PM, Dave Page <[email protected]> wrote:
> >>
> >> On Tue, Mar 8, 2016 at 10:00 AM, Surinder Kumar
> >> <[email protected]> wrote:
> >> > Hi,
> >> >
> >> > I forgot to add 'node.ui.js' file in the previous patch, please ignore
> >> > the
> >> > previous patch.
> >> > Attached is the new patch,
> >> >
> >> > Please review the patch and let me know for any comments.
> >>
> >> Is this dependent on any other patches? The menu option never seems to
> >> be enabled for me when I test against git-master.
> >>
> >> --
> >> Dave Page
> >> Blog: http://pgsnake.blogspot.com
> >> Twitter: @pgsnake
> >>
> >> EnterpriseDB UK: http://www.enterprisedb.com
> >> The Enterprise PostgreSQL Company
> >
> >
>
>
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [application/octet-stream] grant_wizard_v4.patch (62.0K, 3-grant_wizard_v4.patch)
  download | inline diff:
diff --git a/web/pgadmin/browser/static/css/wizard.css b/web/pgadmin/browser/static/css/wizard.css
new file mode 100644
index 0000000..9934aac
--- /dev/null
+++ b/web/pgadmin/browser/static/css/wizard.css
@@ -0,0 +1,89 @@
+
+/** CSS for Wizard **/
+.wizard_dlg {
+  height: 300px;
+  padding: 0 10px;
+}
+
+.ajs-content {
+  padding-top: 0px !important;
+}
+
+/* Wizard Header CSS */
+.wizard-header {
+  background: #428bca;
+  padding-left: 15px;
+  padding-bottom: 7px;
+  color: #fff;
+  font-size: 18px;
+  height: 76px;
+  line-height: 76px;
+}
+
+.wizard-header h3 {
+  font-size: 18px;
+  display: inline-block;
+}
+
+.wizard-content {
+  position: relative;
+  overflow-y: auto;
+  float: left;
+  padding: 0;
+  min-height: 390px;
+  max-height: 390px;
+}
+
+/* Wizard Footer CSS */
+.footer {
+  position: absolute;
+  border-top: 1px solid #ccc;
+  bottom: 0px;
+  height: 71px;
+  right: 0px;
+  padding-top: 22px;
+}
+
+/* Wizard Button CSS */
+.wizard-buttons {
+  float: right;
+}
+
+.wizard-buttons button {
+  float: left;
+  padding: 7px 15.2px;
+  font-size: 14px;
+  margin-right: 5px;
+}
+
+.wizard-finish {
+  margin-right: 0 !important;
+}
+
+/* Wizard Status bar CSS */
+.wizard-description {
+  height: auto;
+  padding: 20px 0px;
+}
+
+/* Error message css */
+.error_msg_div {
+  display: block;
+  bottom: 20px;
+}
+
+.error_msg_div p {
+  background: #fff;
+  color: #b92c28;
+}
+
+/* In wizard select2 dropdown doesn't
+ * popup because z-index of alertify
+ * wizard is greater than the z-index
+ * of select2 dropdown. To make select2
+ * visible, set z-index of select2
+ * higher value than wizard's
+ */
+.select2-container--open {
+  z-index: 10000;
+}
diff --git a/web/pgadmin/browser/static/js/node.ui.js b/web/pgadmin/browser/static/js/node.ui.js
index b84b6ee..47f1266 100644
--- a/web/pgadmin/browser/static/js/node.ui.js
+++ b/web/pgadmin/browser/static/js/node.ui.js
@@ -279,7 +279,7 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
         return false;
       };
 
-      while(p) {
+      while(p && p.length > 0) {
         top = p.get(0).offsetTop + p.height();
         p = p.parent();
         if (hasScrollbar(p)) {
diff --git a/web/pgadmin/browser/static/js/wizard.js b/web/pgadmin/browser/static/js/wizard.js
new file mode 100644
index 0000000..78458e4
--- /dev/null
+++ b/web/pgadmin/browser/static/js/wizard.js
@@ -0,0 +1,222 @@
+define(
+    ['underscore', 'backbone', 'pgadmin', 'pgadmin.browser'],
+function(_, Backbone, pgAdmin, pgBrowser) {
+
+  pgBrowser = pgBrowser || pgAdmin.Browser || {};
+
+  /* Wizard individual Page Model */
+  var WizardPage = pgBrowser.WizardPage = Backbone.Model.extend({
+    defaults: {
+      id: undefined, /* Id */
+      page_title: undefined, /* Page Title */
+      view: undefined, /* A Backbone View */
+      html: undefined, /* HTML tags to be rendered */
+      image: undefined, /* Left hand side image */
+      disable_prev: false, /*  Previous Button Flag */
+      disable_next: false, /*  Next Button Flag */
+      disable_cancel: false, /* Cancel Button Flag */
+      show_progress_bar: '',
+      /* Callback for OnLoad */
+      onLoad: function() {
+        return true;
+      },
+      /* Callback for before Next */
+      beforeNext: function() {
+        return true;
+      },
+      onNext: function(){},
+      onBefore: function() {},
+      /* Callback for before Previous */
+      beforePrev: function() {
+        return true;
+      }
+    }
+  });
+
+  var Wizard = pgBrowser.Wizard = Backbone.View.extend({
+    options: {
+      title: 'Wizard', /* Main Wizard Title */
+      image: 'left_panel.png', /* TODO:: We can use default image here */
+      curr_page: 0, /* Current Page to Load */
+      disable_next: false,
+      disable_prev: false,
+      disable_finish: false,
+      disable_cancel: false,
+      height: 400,
+      width: 650,
+      show_left_panel: true
+    },
+    tmpl: _.template(
+       "    <div class='pgadmin-wizard' style='height: <%= this.options.height %>px; width: <%= this.options.width %>px'>"
+       + "      <div class='wizard-header wizard-badge'>"
+       + "        <div class='row'>"
+       + "          <div class='col-sm-9'>"
+       + "              <h3><span id='main-title'><%= this.options.title %></span> - <span id='step-title'><%= page_title %></span></h3>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "      <div class='wizard-content col-sm-12'>"
+       + "        <% if(this.options.show_left_panel) { %>"
+       + "          <div class='col-sm-3 wizard-left-panel'>"
+       + "              <img src='<%= this.options.image %>'></div>"
+       + "        <% } %>"
+       + "        <div class='col-sm-<% if(this.options.show_left_panel){ %>6<% } else { %>12<% } %> wizard-right-panel'>"
+       + "          <% if( typeof show_description !=  'undefined'){ %>"
+       + "            <div class='wizard-description'>"
+       + "              <%= show_description %>"
+       + "            </div>"
+       + "          <% } %>"
+       + "          <div class='wizard-right-panel_content'>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "      <div class='col-sm-12 error_msg_div'>"
+       + "       <p></p>"
+       + "      </div>"
+       + "      <div class='footer col-sm-12'>"
+       + "        <div class='row'>"
+       + "          <div class='col-sm-4 wizard-progress-bar'>"
+       + "             <p><% if(show_progress_bar){ %><%= show_progress_bar %><% } %></p>"
+       + "          </div>"
+       + "          <div class='col-sm-8'>"
+       + "            <div class='wizard-buttons'>"
+       + "              <button class='btn-sm btn-primary wizard-back' <%=this.options.disable_prev ? 'disabled' : ''%>><i class='fa fa-backward'></i> Back</button>"
+       + "              <button class='btn-sm btn-primary wizard-next' <%=this.options.disable_next ? 'disabled' : ''%>>Next <i class='fa fa-forward'></i></button>"
+       + "              <button class='btn-sm btn-danger wizard-cancel' <%=this.options.disable_cancel ? 'disabled' : ''%>><i class='fa fa-lg fa-close'></i>Cancel</button>"
+       + "              <button class='btn-sm btn-primary wizard-finish' <%=this.options.disable_finish ? 'disabled' : ''%>>Finish</button>"
+       + "            </div>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "    </div>"),
+    events: {
+      "click button.wizard-next" : "nextPage",
+      "click button.wizard-back" : "prevPage",
+      "click button.wizard-cancel" : "onCancel",
+      "click button.wizard-finish" : "finishWizard",
+    },
+    initialize: function(options) {
+      this.options = _.extend({}, this.options, options.options);
+      this.currPage = this.collection.at(this.options.curr_page).toJSON();
+    },
+    render: function() {
+      var data = this.currPage;
+
+      /* Check Status of the buttons */
+      this.options.disable_next = (this.options.disable_next ? true : this.evalASFunc(this.currPage.disable_next));
+      this.options.disable_prev = (this.options.disable_prev ? true : this.evalASFunc(this.currPage.disable_prev));
+      this.options.disable_cancel = (this.currPage.canCancel ? true : this.evalASFunc(this.currPage.disable_cancel));
+
+      that = this;
+
+      /* HTML Content */
+      if (data.html) { data.content = data.html; }
+      /* Backbone View */
+      else if (data.view) { data.content = data.view.render().el;}
+
+      $(this.el).html(this.tmpl(data));
+      $(this.el).find(".wizard-right-panel_content").html(data.content);
+
+      /* OnLoad Callback */
+      this.onLoad();
+
+      return this;
+    },
+    nextPage: function() {
+      this.options.curr_page.el = this.$el;
+      if (!this.beforeNext()) { return false; }
+
+      page_id = this.onNext();
+
+      if (page_id ) {
+        this.currPage = this.collection.get(page_id).toJSON();
+        this.options.curr_page = this.collection.indexOf(this.collection.get(page_id));
+      }
+      else if (this.options.curr_page < (this.collection.length-1)) {
+        this.options.curr_page = this.options.curr_page + 1;
+        this.currPage = this.collection.at(this.options.curr_page).toJSON();
+      }
+
+      this.enableDisableNext();
+      this.enableDisablePrev();
+
+      return this.render();
+    },
+    prevPage: function() {
+      if (!this.beforePrev()) { return false; }
+
+      page_id = this.onPrev();
+
+      if (page_id){
+        this.currPage = this.collection.get(page_id).toJSON();
+        this.options.curr_page = this.collection.indexOf(this.collection.get(page_id));
+      }
+      else if (this.options.curr_page > 0) {
+        this.options.curr_page = this.options.curr_page - 1;
+        this.currPage = this.collection.at(this.options.curr_page).toJSON();
+      }
+
+      this.enableDisableNext();
+      this.enableDisablePrev();
+
+      return this.render();
+    },
+    finishWizard: function() {
+      this.onFinish();
+      return true;
+    },
+    enableDisableNext: function(disable) {
+        if (typeof(disable) != 'undefined') {
+          this.options.disable_next = disable;
+        }
+        else if (this.options.curr_page >= (this.collection.length-1)) {
+          this.options.disable_next = true;
+        }
+        else {
+          this.options.disable_next = false;
+        }
+    },
+    enableDisablePrev: function(disable) {
+        if (typeof(disable) != 'undefined') {
+          this.options.disable_prev = disable;
+        }
+        else if (this.options.curr_page <= 0) {
+          this.options.disable_prev = true;
+        }
+        else {
+          this.options.disable_prev = false;
+        }
+    },
+    beforeNext: function(){
+      return this.evalASFunc(this.currPage.beforeNext);
+    },
+    beforePrev: function(){
+      return this.evalASFunc(this.currPage.beforePrev);
+    },
+    onPrev: function(){
+      return this.evalASFunc(this.currPage.onPrev);
+    },
+    onNext: function(){
+      return this.evalASFunc(this.currPage.onNext);
+    },
+    onLoad: function() {
+      return this.evalASFunc(this.currPage.onLoad);
+    },
+    onFinish: function() {
+      return true;
+    },
+    onCancel: function() {
+      this.$el.remove();
+      return true;
+    },
+    evalASFunc: function(func, ctx) {
+      var self = this;
+      ctx = ctx || self.currPage;
+
+      return (_.isFunction(func) ? func.apply(ctx, [self]) : func);
+    }
+  });
+
+  return pgBrowser;
+
+});
diff --git a/web/pgadmin/tools/grant_wizard/__init__.py b/web/pgadmin/tools/grant_wizard/__init__.py
new file mode 100644
index 0000000..24c097a
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/__init__.py
@@ -0,0 +1,291 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Implements Grant Wizard"""
+
+import json
+from flask import render_template, request, current_app
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_response as ajax_response, \
+    make_json_response, internal_server_error
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from pgadmin.browser.server_groups.servers.utils import parse_priv_to_db
+from pgadmin.utils import PgAdminModule
+from flask import Response, url_for
+from flask.ext.security import login_required
+from urllib import unquote
+
+# set template path for sql scripts
+template_path = 'grant_wizard/sql'
+MODULE_NAME = 'grant_wizard'
+
+
+class GrantWizardModule(PgAdminModule):
+    """
+    class GrantWizardModule(Object):
+
+        It is a wizard which inherits PgAdminModule
+        class and define methods to load its own
+        javascript file.
+    """
+    def get_own_stylesheets(self):
+        """
+        Returns:
+            list: the stylesheets used by this module.
+        """
+        stylesheets = []
+        for (endpoint, filename) in [
+                ('browser.static', 'css/wizard.css'),
+                ('grant_wizard.static', 'css/grant_wizard.css'),
+                ]:
+            stylesheets.append(url_for(endpoint, filename=filename))
+        return stylesheets
+
+    def get_own_javascripts(self):
+        """"
+        Returns:
+            list: js files used by this module
+        """
+        scripts = []
+        scripts.append({
+            'name': 'pgadmin.tools.grant_wizard',
+            'path': url_for('grant_wizard.index') + 'grant_wizard',
+            'when': None
+        })
+        scripts.append({
+            'name': 'pgadmin.browser.wizard',
+            'path': url_for('browser.static', filename='js/wizard'),
+            'when': None
+        })
+        return scripts
+
+# Create blueprint for GrantWizardModule class
+blueprint = GrantWizardModule(
+    MODULE_NAME, __name__, static_url_path='',
+    url_prefix='/wizard')
+
+
[email protected]("/")
+@login_required
+def index():
+    pass
+
+
[email protected]("/grant_wizard.js")
+@login_required
+def script():
+    """render own javascript"""
+    return Response(response=render_template(
+                "grant_wizard/js/grant_wizard.js", _=gettext),
+                status=200,
+                mimetype="application/javascript")
+
+
[email protected](
+    '/grant_wizard/properties/<int:gid>/<int:sid>/<int:did>'
+    '/<int:node_id>/<node_name>/<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+@login_required
+def properties(gid, sid, did, node_id, node_name, node_type, nspname):
+    """It fetches the properties of object types
+       and render into selection page of wizard
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+    nspname = unquote(nspname)
+
+    res_data = []
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    # Fetch functions against schema
+    if node_type in ['schema']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type=node_type, nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch trigger functions
+    if node_type in ['schema', 'trigger_function']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='trigger_function', nspname=nspname)
+        status, res = conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch Sequences against schema
+    if node_type in ['schema', 'sequence']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='sequence', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+        res_data.extend(res['rows'])
+
+    # Fetch Tables against schema
+    if node_type in ['schema', 'table']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='table', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch Views against schema
+    if node_type in ['schema', 'coll-view']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='coll-view', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    return ajax_response(
+            response=res_data,
+            status=200
+            )
+
+
[email protected](
+    '/grant_wizard/msql/<int:gid>/<int:sid>/<int:did>/<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+def msql(gid, sid, did, node_type, nspname):
+    """
+    This function will return modified SQL
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+    nspname = unquote(nspname)
+
+    data = {}
+    for k, v in request.args.items():
+        try:
+            data[k] = json.loads(v)
+        except ValueError:
+            data[k] = v
+
+    # Form db connection
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    acls = []
+    try:
+        acls = render_template(
+            "/".join([template_path, 'allowed_acl.json'])
+            )
+        acls = json.loads(acls)
+    except Exception as e:
+        current_app.logger.exception(e)
+
+    try:
+
+        # Parse privileges
+        if 'acl' in data:
+            data['acl'] = parse_priv_to_db(data['acl'], acls[node_type]['acl'])
+
+        # Pass database objects and get SQL for privileges
+        SQL = render_template("/".join(
+            [template_path, 'grant.sql']),
+            data=data, nspname=nspname, conn=conn)
+
+        res = {'data': SQL}
+
+        return ajax_response(
+            response=res,
+            status=200
+            )
+
+    except Exception as e:
+        return make_json_response(
+            status=410,
+            success=0,
+            errormsg=e.message
+        )
+
+
[email protected](
+    '/grant_wizard/save/<int:gid>/<int:sid>/<int:did>/'
+    '<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+def save(gid, sid, did, node_type, nspname):
+    """
+    This function will apply the privileges to the selected
+    Database Objects
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+    nspname = unquote(nspname)
+
+    data = request.form if request.form else json.loads(request.data.decode())
+
+    # Form db connection and we use conn to execute sql
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    acls = []
+    try:
+        acls = render_template(
+            "/".join([template_path, 'allowed_acl.json'])
+            )
+        acls = json.loads(acls)
+    except Exception as e:
+        current_app.logger.exception(e)
+
+    try:
+
+        # Parse privileges
+        if 'acl' in data:
+            data['acl'] = parse_priv_to_db(
+                data['acl'],
+                acls[node_type]['acl']
+                )
+
+        # Pass database objects and get SQL for privileges
+        SQL = render_template("/".join(
+            [template_path, 'grant.sql']),
+            data=data, nspname=nspname, conn=conn)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        return make_json_response(
+            success=1,
+            info="Privileges Applied"
+        )
+
+    except Exception as e:
+        return internal_server_error(errormsg=e.message)
diff --git a/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
new file mode 100644
index 0000000..4403c68
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
@@ -0,0 +1,66 @@
+/** Grant Wizard CSS **/
+
+/**
+  CSS to make db object type table
+  fixed so that tbody content may
+  scroll
+ */
+.object_type_table thead tr {
+  position: relative;
+  display: block;
+}
+
+.object_type_table tbody {
+  display: block;
+  overflow-y: scroll;
+  height: 180px;
+}
+
+.object_type_table tbody tr td:nth-child(1),
+.object_type_table thead tr th:nth-child(1) {
+  width: 28px;
+  min-width: 28px;
+}
+
+.object_type_table tbody tr td:nth-child(2),
+.object_type_table thead tr th:nth-child(2) {
+  width: 185px;
+  min-width: 185px;
+}
+
+.object_type_table tbody tr td:nth-child(3),
+.object_type_table thead tr th:nth-child(3) {
+  width: 110px;
+  min-width: 110px;
+}
+
+.object_type_table tbody tr td:nth-child(4),
+.object_type_table thead tr th:nth-child(4) {
+  width: 244px;
+}
+
+/** Override Backgrid filter CSS **/
+.backgrid-filter.form-search {
+  float: left;
+  margin: 0px 5px 5px 0;
+}
+
+/** Custom styling for Codemirror field **/
+.wizard-right-panel_content {
+  border: 1px solide #ccc;
+}
+
+.wizard-right-panel_content .CodeMirror {
+  border: 1px solid #ccc;
+  height: 285px !important;
+  min-height: 285px !important;
+}
+
+.wizard-right-panel_content .CodeMirror-linenumber {
+  background: #F7F7F7;
+  border-right: 1px solid #DDDDDD;
+}
+
+.wizard-right-panel_content .CodeMirror-gutters {
+  min-height: 285px !important;
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
new file mode 100644
index 0000000..90287b2
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
@@ -0,0 +1,967 @@
+define([
+      'jquery', 'underscore', 'underscore.string', 'alertify',
+      'pgadmin.browser', 'backbone', 'backgrid', 'pgadmin.browser.node',
+      'backgrid.select.all', 'backgrid.filter', 'backbone.paginator',
+      'backgrid.paginator', 'pgadmin.browser.server.privilege',
+      'pgadmin.browser.wizard',
+      ],
+
+function($, _, S, alertify, pgBrowser, Backbone, Backgrid, pgNode) {
+
+  if (pgBrowser.GrantWizard) {
+    return pgBrowser.GrantWizard;
+  }
+
+  /**
+    It is sub model for field "Objects". It has fields
+    for database object types such as Schemas, Views and
+    Sequence etc.
+  */
+  var DatabaseObjectModel = pgNode.Model.extend({
+    defaults: {
+      selected: false,
+      icon: 'icon-unknown',
+      name: undefined,
+      nspname: undefined,
+      proargs: undefined,
+      object_type: undefined,
+      object_id: undefined
+    },
+    idAttribute: 'object_id',
+    parse: function(res) {
+
+      // Create unique object id
+      res.object_id = res.object_type + res.nspname + '"' + res.name;
+
+      return res;
+    },
+
+    validate: function() {
+
+      /*
+      * Triggers error messages for object types "selected"
+      * if it is empty/undefined/null
+      */
+      var err = {},
+        errmsg,
+        node = this.get('objects').toJSON();
+      if (_.isEmpty(node)) {
+        err['selected'] = '{{ _("Please select any database object type") }}';
+        errmsg = errmsg || err['selected'];
+        this.errorModel.set('selected', errmsg);
+        return errmsg;
+      } else {
+        this.errorModel.unset('selected');
+      }
+      return null;
+    }
+  });
+
+  // Define privileges for various types of nodes
+  var privDict = {
+    'schema': ['a', 'r', 'w', 'd', 'D', 'x', 't', 'U', 'X'],
+    'coll-function': ['X'],
+    'coll-sequence': ['a', 'w', 'U'],
+    'coll-table':['a', 'w', 'U', 'd', 'D', 'x', 't'],
+    'coll-view': ['a', 'r', 'w', 'd', 'D', 'x', 't'],
+  };
+
+  // Define columns for the Db Object Types Grid
+  var columns = [{
+    name: "selected",
+
+    /*
+     Override render method of Backgrid.Extension.SelectRowCell
+     class. It has an issue: It doesn't mark rows checked if we move to next
+     page and then go back to previous page. but it must show.
+     so we handle this case by overriding the render method.
+    */
+    cell: Backgrid.Extension.SelectRowCell.extend({
+      render: function() {
+
+        // Use the Backform Control's render function
+        Backgrid.Extension.SelectRowCell.prototype.render.apply(this, arguments);
+
+        var col = this.column.get('name');
+        if (this.model && this.model.has(col)) {
+          if (this.model.get(col)) {
+            this.checkbox().prop("checked", true);
+            this.$el.parent().toggleClass("selected", true);
+            this.model.trigger("backgrid:selected", this.model, true);
+          }
+        }
+        return this;
+      }
+    }),
+
+    headerCell: "select-all",
+
+  },{
+    name: "object_type",
+    label: "Object Type",
+    editable: false,
+    cell: Backgrid.Cell.extend({
+      render: function() {
+
+        // Override render to add icon to Db Object column
+        Backgrid.Cell.prototype.render.apply(this, arguments);
+        this.$el.addClass(this.model.get('icon')).css({"padding-left": "22px"});
+
+        return this;
+      }
+    })
+  },{
+    name: "nspname",
+    label: "Schema",
+    cell: "string",
+    editable: false
+  },{
+    name: "name",
+    label: "Name",
+    cell: "string",
+    editable: false
+  }];
+
+  // Create an Object GrantWizard of pgBrowser class
+  pgBrowser.GrantWizard  = {
+    init: function() {
+      if (this.initialized) {
+        return;
+      }
+
+      this.initialized = true;
+
+      // Define list of nodes on which grant wizard option appears
+      var supported_nodes = [
+            'schema', 'coll-function',
+            'coll-sequcene', 'coll-table',
+            'coll-view'
+          ],
+
+          /**
+            Enable/disable grantwizard menu in tools based
+            on node selected
+            if selected node is present in supported_nodes,
+            menu will be enabled otherwise disabled.
+           */
+          menu_enabled = function(obj) {
+            if(!_.isUndefined(obj) && !_.isNull(obj))
+              return (_.indexOf(supported_nodes, obj._type) !== -1 ? true: false);
+            else
+              return false;
+          };
+
+      // Define the nodes on which the menus to be appear
+      var menus = [{
+        name: 'grant_wizard_schema', module: this,
+        applies: ['tools'], callback: 'start_grant_wizard',
+        priority: 10, label: '{{_("Grant Wizard...") }}',
+        icon: 'wcTabIcon', enable: menu_enabled
+      }];
+
+      // Add supported menus into the menus list
+      for (var idx = 0; idx < supported_nodes.length; idx++) {
+        menus.push({
+          name: 'grant_wizard_schema_context_' + supported_nodes[idx],
+          node: supported_nodes[idx], module: this,
+          applies: ['context'], callback: 'start_grant_wizard',
+          priority: 10, label: '{{_("Grant Wizard...") }}',
+          icon: 'wcTabIcon', enable: menu_enabled
+          });
+      }
+      pgAdmin.Browser.add_menus(menus);
+
+      return this;
+    },
+
+    // Callback to draw Wizard Dialog
+    start_grant_wizard: function(action, item) {
+      if (!alertify.wizardDialog) {
+        alertify.dialog('wizardDialog', function factory() {
+          return {
+            main:function(title) {
+              this.set('title', title);
+            },
+            setup:function() {
+              return {
+                options: {
+                  frameless: true,
+                  resizable: false,
+                  autoReset: false,
+                  maximizable: false,
+                  closableByDimmer: false
+                }
+              };
+            },
+            hooks:{
+              onshow: function() {
+
+                // Set dimensions for wizard
+                this.elements.dialog.style.width = '100%';
+                this.elements.dialog.style.height = '540px';
+              },
+            },
+            build:function() { },
+
+            /**
+              Returns a Collection used for rendering grid
+              and Pagination
+
+              @class {Backbone.PageableCollection}
+              @param {variable} baseUrl - from which data is fetched
+              @param {Backbone.Model} DatabaseObjectModel
+              @return {Object} coll
+            */
+            getPageableCollection: function(baseUrl){
+              var Coll = Backbone.PageableCollection.extend({
+                model: DatabaseObjectModel,
+                url: baseUrl,
+                mode: "client",
+                state: {
+                  pageSize: 100,
+                  sortKey: "object_type",
+                  order: -1
+                },
+                queryParams: {
+                  totalPages: null,
+                  totalRecords: null,
+                  sortKey: "sort"
+                }
+              });
+              return new Coll();
+            },
+
+            /**
+              Returns a Paginator Class Object which is again to be rendered
+
+              @class {Backgrid.Extension.Paginator}
+              @param {Backbone.Collection} coll - from which data is fetched
+              @return {Object} paginator
+            */
+            DbPaginator: function(coll){
+              var paginator = this.paginator = new Backgrid.Extension.Paginator({
+                collection: coll,
+                windowSize: 8
+              });
+              return paginator;
+            },
+
+            /**
+              Create new Filter which will filter the
+              rendered grid for Select Type Tabular Data
+              @param {Backbone.PageableCollection} coll
+            */
+            DbObjectFilter: function(coll){
+              var clientSideFilter = this.clientSideFilter = new Backgrid.Extension.ClientSideFilter({
+                collection: coll,
+                placeholder: _('Search by object type or name'),
+
+                // The model fields to search for matches
+                fields: ['object_type', 'name'],
+
+                // How long to wait after typing has stopped before searching can start
+                wait: 150
+              });
+              return clientSideFilter;
+            },
+
+            //Enable Disable Next button of Page 2
+            updateButtons: function(modified){
+              if(!modified)
+                $('.wizard-next').prop('disabled', true);
+              else
+                $('.wizard-next').prop('disabled', false);
+            },
+
+            /**
+              Callback called when an errorModel is set
+              with invalid value and errormsg is set into
+              status bar element and next button is disabled
+             */
+            onSessionInvalid: function(msg) {
+              $('.error_msg_div p').html(msg).removeClass("hide");
+
+              // Enable disable Next button
+              this.updateButtons(false);
+              return true;
+            },
+
+            /**
+              Callback called when anything is set into model
+              thus hide error msg element and enable next button
+              status bar element and next button is disabled
+             */
+            onSessionValidated: function(sessHasChanged) {
+              $('.error_msg_div p').empty().addClass("hide");
+
+              // Enable disable Next button
+              this.updateButtons(sessHasChanged);
+            },
+
+            /*
+             Remove/Delete objects, attributes
+             in wizard on wizard close or finish
+             to reclaim memory
+            */
+            releaseObjects: function(){
+              var self = this;
+
+              if(!_.isUndefined(self.dbObjectFilter)) {
+                self.dbObjectFilter.remove();
+                self.dbObjectFilter = undefined;
+              }
+
+              if(!_.isUndefined(self.clientSideFilter)) {
+                self.clientSideFilter.remove();
+                self.clientSideFilter = undefined;
+              }
+
+              if(!_.isUndefined(self.paginator)) {
+                self.paginator.remove();
+                self.paginator = undefined;
+              }
+
+              // Delete Wizard Pages
+              if(!_.isUndefined(self.dbObjectTypePage) &&
+                !_.isNull(self.dbObjectTypePage)) {
+                if(!_.isUndefined(self.dbObjectTypePage.get('model')) &&
+                  !_.isNull(self.dbObjectTypePage.get('model'))) {
+                  self.dbObjectTypePage.get('model').clear();
+                  self.dbObjectTypePage = undefined;
+                }
+              }
+
+              if(!_.isUndefined(self.privilegePage) &&
+                !_.isNull(self.privilegePage)) {
+                if(!_.isUndefined(self.privilegePage.get('model')) &&
+                  !_.isNull(self.privilegePage.get('model'))) {
+                  self.privilegePage.get('model').clear();
+                  self.privilegePage = undefined;
+                }
+              }
+
+              if(!_.isUndefined(self.reviewSQLPage) &&
+                !_.isNull(self.reviewSQLPage)) {
+                if(!_.isUndefined(self.reviewSQLPage.get('model')) &&
+                  !_.isNull(self.reviewSQLPage.get('model'))) {
+                  self.reviewSQLPage.get('model').clear();
+                  self.reviewSQLPage = undefined;
+                }
+              }
+
+              // Remove collection containing db object data
+              if(!_.isNull(self.coll) &&
+                !_.isUndefined(self.coll)) {
+                self.coll.reset();
+                self.coll = undefined;
+              }
+
+              // Delete Wizard
+              if(!_.isNull(self.wizard) &&
+                !_.isUndefined(self.wizard)) {
+                self.wizard.collection.reset();
+                self.wizard.curr_page = undefined;
+                self.wizard.el = undefined;
+                self.wizard = undefined;
+              }
+
+            },
+
+            /**
+              Every time a wizard is opened, this function
+              is called everytime. It has Wizard Pages which
+              are rendered by the Wizard Class:
+
+              @class {pgBrowser.WizardPage} dbObjectType1 - This page
+              @extends {Backbone.Model}
+              renders a grid of Database Object Types such as
+                Schemas, Views and Sequences etc.
+
+              @class {pgBrowser.WizardPage} WizardPage2 - This page
+              @extends {Backbone.Model}
+              adds Privilege Control which provides grant privileges
+              such as "Create, Insert, Delete, Update" so on the
+              database objects selected on Wizard Pages.
+
+              @class {pgBrowser.WizardPage} WizardPage3 - This page
+              displays the generated GRANT SQL query for the Db
+              objects selected with the specific privileges added to it.
+              @extends {Backbone.Model}
+
+              @class {Backbone.Collection} WizardCollection - It is the
+              collection of wizard pages
+
+              @class {pgBrowser.Wizard} wizard - Its task is:
+              - Create a Wizard
+              - Add Buttons, Callbacks to it.
+              - Render WizardPages
+              @extends {Backbone.View}
+
+            */
+            prepare:function() {
+
+              // Remove extra container
+              if($('.ajs-content').size() > 1){
+                $('.ajs-content')[0].remove();
+              }
+
+              // Generate wizard main container
+              var wizard_html = '<div class="wizard_dlg">',
+                  wizard_parent = $('.ajs-content').empty().append(wizard_html),
+                  el = $(".ajs-content");
+
+              // Extract the data from the selected tree node
+              var t = pgBrowser.tree,
+                  i = t.selected(),
+                  d = i && i.length == 1 ? t.itemData(i) : undefined,
+                  info = pgBrowser.Node.getTreeNodeHierarchy(i),
+                  icon = d.icon;
+
+              /**
+                Generate a URL using:
+                gid, did, sid(server id), node_id(node id),
+                node_(node name), node_type(node type)
+                and pass it to collection which will fetch Object Type properties.
+               */
+              var gid = info['server-group']._id,
+                  sid = info.server._id,
+                  did = info.database._id,
+                  node_id = d._id,
+                  nspname = info.schema.label;
+                  node_type = d._type,
+                  node_label = d.label,
+
+                  // Collection url to fetch database object types for objects field
+                  baseUrl = "{{ url_for('grant_wizard.index') }}" + "grant_wizard/properties/" +
+                      S('%s/%s/%s/%s/%s/%s/%s/').sprintf(
+                          encodeURI(gid), encodeURI(sid), encodeURI(did),
+                          encodeURI(node_id), encodeURI(node_label),
+                          encodeURI(node_type), encodeURI(nspname)).value();
+
+                  // Model's save url
+                  saveUrl = "{{ url_for('grant_wizard.index') }}" + "grant_wizard/save/" +
+                      S('%s/%s/%s/%s/%s/').sprintf(
+                          encodeURI(gid), encodeURI(sid), encodeURI(did),
+                          encodeURI(node_type), encodeURI(nspname)).value(),
+
+                  // generate encoded url based on wizard type
+                  msql_url = this.msql_url = "/wizard/grant_wizard/msql/"+
+                    S('%s/%s/%s/%s/%s/').sprintf(
+                        encodeURI(gid), encodeURI(sid), encodeURI(did),
+                        encodeURI(node_type), encodeURI(nspname)).value(),
+
+                  // Create instances of collection, pagination and filter
+                  coll = this.coll = this.getPageableCollection(baseUrl),
+                  paginator = this.paginator = this.DbPaginator(coll),
+                  dbObjectFilter = this.dbObjectFilter = this.DbObjectFilter(coll);
+
+              /**
+                It is the main model with schema defined
+                Every time a new wizard is opened,
+                a new model should create.
+               */
+              var GrantWizardModel = pgNode.Model.extend({
+                defaults: {
+                  objects: undefined,
+                  acl: undefined
+                },
+                schema: [
+                  {
+                    id: 'objects', label: '{{ _("Objects") }}', model: DatabaseObjectModel,
+                    type: 'collection', group: 'Objects'
+                  },
+                  {
+                    id: 'acl', label: '{{ _("Privileges") }}', node: 'role',
+                    model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend({
+
+                    // privileges are selected based on node clicked
+                      privileges: privDict[node_type]
+                    }), uniqueCol : ['grantee', 'grantor'], editable: true,
+                    type: 'collection', group: 'Privileges', canAdd: true,
+                    canDelete: true, control: 'unique-col-collection'
+                  }
+                ],
+                urlRoot: saveUrl
+              });
+
+              /**
+                Create instance of GrantWizard Model, provide urlRoot
+                node_info object, Generate fields objects
+               */
+              var self = this,
+                  newModel = new GrantWizardModel({}, { node_info: info }),
+                  fields = Backform.generateViewSchema(
+                     info, newModel, 'create', d._type, d
+                     );
+
+              /**
+                Fetch data from server and set into grid
+                and show/hide progress bar
+               */
+              $('.wizard-progress-bar p').show();
+
+              coll.fetch({
+                success: function(collection, data) {
+                  $('.wizard-progress-bar p').html('');
+                },
+                reset: true
+              }, this);
+
+              /**
+                Override backgrid listener "backgrid:selected" to
+                Add/Remove model to/from objects collection
+               */
+              coll.on('backgrid:selected', function(model, selected) {
+                model.set('selected', selected);
+
+                /**
+                  if a row (checkbox) is checked, add that model
+                  into collection, when unchecked remove it from
+                  model.
+                 */
+                if(selected)
+                  newModel.get('objects').add(model, { silent: true });
+                else
+                  newModel.get('objects').remove(model);
+
+                // validate model on checkbox check/uncheck
+                var msg =  model.validate.call(newModel);
+
+                /**
+                  If no object type is selected, set error msg
+                  and disable next button, else enable next button
+                 */
+                if(msg)
+                  self.onSessionInvalid.call(self, msg);
+                else
+                  self.onSessionValidated.call(self, true);
+              });
+
+              // To track changes into model, start new session
+              newModel.startNewSession();
+
+              // Add event listener for privileges control
+              newModel.on('pgadmin-session:valid', self.onSessionValidated.bind(this));
+              newModel.on('pgadmin-session:invalid', self.onSessionInvalid.bind(this));
+
+              //////////////////////////////////////////////////////////////////////
+              //                                                                  //
+              //            Wizard Page for Db Object Type                        //
+              //                                                                  //
+              //////////////////////////////////////////////////////////////////////
+
+              /**
+                Create wizard page. It renders a grid of
+                Database Object Types such as
+                Schemas, Views and Sequences etc.
+                Set default values
+               */
+              var dbObjectTypePage = self.dbObjectTypePage = new pgBrowser.WizardPage({
+                id: 1,
+                page_title: 'Object Selection (step 1 of 3)',
+                disable_prev: true,
+                disable_next: true,
+                show_description: _('Please select objects from the below list.'),
+                show_progress_bar: _('Please wait while fetching records...'),
+                model: newModel,
+                view: new (function() {
+
+                  // Set page Instance
+                  var pageView = this;
+
+                  _.extend(pageView, {
+
+                    // Remove grid if it is before render
+                    cleanup: function() {
+                      if (this.grid) {
+                        this.grid.remove();
+                        delete this.grid;
+                        this.grid = null;
+                      }
+
+                      // Remove grid element if exists
+                      if (this.el) {
+                        $(this.el).remove();
+                        delete this.el;
+                      }
+                    },
+
+                    // Delete grid before render
+                    grid: null,
+
+                    render: function() {
+
+                      // Create a grid container
+                      var gridBody =
+                            $('<div class="select_db_objects_container"></div>');
+
+                      // Remove grid if exits before render
+                      if (this.grid) {
+                        this.cleanup();
+                      }
+
+                      // Initialize a new Grid instance
+                      this.grid = new Backgrid.Grid({
+                        columns: _.clone(columns),
+                        collection: coll,
+                        className: "backgrid table-bordered object_type_table"
+                        });
+
+                      // Render selection Type grid and paginator
+                      gridBody.append(
+                        this.grid.render().$el
+                        ).append(self.paginator.render().el);
+
+                      // Render Search Filter
+                      gridBody.prepend(
+                        self.clientSideFilter.render().el);
+
+                      // Assign gridBody content to page element
+                      this.el = gridBody;
+
+                      /**
+                        Fetch selected models from collection and
+                        make rows checked in grid
+                       */
+                      newModel.get('objects').each(function(m) {
+                        var model = coll.get(m.get('object_id'));
+                        if (model) {
+                          coll.trigger('backgrid:selected', model, true);
+                        }
+                      });
+
+                      // Refresh grid to re render rows.
+                      coll.trigger('backgrid:refresh');
+
+                      return this;
+                    }
+                  });
+                }),
+
+                beforeNext: function(obj){
+                  var self = this;
+                  obj.options.disable_next = true;
+
+                  /**
+                    Enable/Disable next button of privilegePage if objects
+                    are present in model
+                   */
+                  if(!_.isNull(newModel.get('acl')) &&
+                    !_.isUndefined(newModel.get('acl'))) {
+                    if(newModel.get('acl').length > 0)
+                      obj.collection.at(1).set('disable_next', false);
+                    }
+
+                  // Clean the view
+                  if (self.view) {
+                    self.view.cleanup();
+                    delete self.view;
+                    self.view = null;
+                  }
+                  return true;
+                },
+
+              });
+
+              //////////////////////////////////////////////////////////////////////
+              //                                                                  //
+              //            Wizard Page for Privilege Control                     //
+              //                                                                  //
+              //////////////////////////////////////////////////////////////////////
+
+              /**
+                Create Field Object which has properties like
+                node_data, node_info which is required for rendering
+                Privilege control
+               */
+              var privilegesField = new Backform.Field(fields[1].fields[0]);
+
+              // Wizard for Privelege control
+              var privilegePage = self.privilegePage = new pgBrowser.WizardPage({
+                id: 2,
+                page_title: _('Privileges Selection (step 2 of 3)'),
+                show_description: _('Please select privileges for the selected objects.'),
+                disable_next: true,
+                model: newModel,
+
+                // Create a view function object
+                view: new (function() {
+                  var pageView = this;
+                    _.extend(pageView, {
+
+                      // Render Privelege control to generate its html markup
+                      render: function() {
+                        this.privControl = new (privilegesField.get('control')) ({
+                          field: privilegesField,
+                          model: newModel
+                        });
+                        return {el: this.privControl.render().$el};
+                      },
+
+                      // Remove the privilege control
+                      cleanup: function() {
+                        if (this.privControl) {
+                          this.privControl.remove();
+                          delete this.privControl;
+                          this.privControl = null;
+                        }
+                      }
+                    });
+                }),
+
+                beforePrev: function(wizardObj) {
+
+                  // Remove the privilege control
+                  if (this.view) {
+                    this.view.cleanup();
+                    delete this.view;
+                    this.view = null;
+                  }
+
+                  /**
+                    Enable/Disable next button of page 1 if objects
+                    are present in model
+                   */
+                  var objectsModel = newModel.get('objects');
+
+                  if(!_.isUndefined(objectsModel) && !_.isEmpty(objectsModel) &&
+                      objectsModel.length > 0) {
+                    wizardObj.collection.at(0).set('disable_next', false);
+
+                    // Don't show progress bar
+                    wizardObj.collection.at(0).set('show_progress_bar', '');
+                  }
+
+                  /**
+                    We're re-rendering the controls as they are deleted
+                    before heading to next page
+                    Refresh Backgrid to re-render the elements selected
+                    re-render paginator
+                    re-render Filter
+                   */
+                  newModel.trigger("backgrid:refresh", newModel, false);
+                  self.paginator.render();
+                  self.clientSideFilter.render();
+                  return true;
+                },
+
+                beforeNext: function() { return true; },
+
+                onNext: function(obj){
+
+                  // Remove the privilege control
+                  if (this.view) {
+                    this.view.cleanup();
+                    delete this.view;
+                    this.view = null;
+                  }
+
+                  // Enable finish button
+                  self.wizard.options.disable_finish = false;
+
+                  /**
+                    triggers to get SQL queries data to render
+                    into the reviewSQLPage
+                   */
+                  newModel.trigger('pgadmin-wizard:nextpage:sql', {'node_type': node_type });
+                }
+              });
+
+
+              //////////////////////////////////////////////////////////////////////
+              //                                                                  //
+              //            Review SQL Query Page                                 //
+              //                                                                  //
+              //////////////////////////////////////////////////////////////////////
+
+              //Create SqlField Object
+              var sqlField = new Backform.Field(
+                  {
+                    id: 'sqltab',
+                    label: _('Sql Tab'),
+                    field: fields,
+
+                    /**
+                      Extend 'SqlTabControl' to define new
+                      function 'onWizardNextPageChange'
+                      which gets triggered on next button
+                      click to fetch generated SQL query
+                      for the selected db objects.
+                     */
+                    control: Backform.SqlTabControl.extend({
+                      initialize: function() {
+
+                        // Initialize parent class
+                        Backform.SqlTabControl.prototype.initialize.apply(this, arguments);
+
+                        this.msql_url = self.msql_url;
+
+                        // define trigger events for prev and next page
+                        this.model.on('pgadmin-wizard:nextpage:sql', this.onWizardNextPageChange, this);
+                        this.model.on('pgadmin-wizard:prevpage:sql', this.onWizardPrevPageChange, this);
+                      },
+
+                      // This method fetches the modified SQL for the wizard
+                      onWizardNextPageChange: function(){
+
+                        var self = this;
+
+                        // Fetches modified SQL
+                        $.ajax({
+                          url: this.msql_url,
+                          type: 'GET',
+                          cache: false,
+                          data: self.model.toJSON(true, 'GET'),
+                          dataType: "json",
+                          contentType: "application/json"
+                        }).done(function(res) {
+                          self.sqlTab.clearHistory();
+                          self.sqlTab.setValue(res.data);
+                        }).fail(function() {
+                          self.model.trigger('pgadmin-view:msql:error');
+                        }).always(function() {
+                          self.model.trigger('pgadmin-view:msql:fetched');
+                        });
+                      },
+
+                      remove: function() {
+
+                        // Stop listen to next/prev page events
+                        this.model.off('pgadmin-wizard:nextpage:sql', this.onWizardNextPageChange, this);
+                        this.model.off('pgadmin-wizard:prevpage:sql', this.onWizardPrevPageChange, this);
+                        Backform.SqlTabControl.__super__.remove.apply(this, arguments);
+                      }
+
+                    })
+                  }),
+
+                /**
+                  Create sqlField view instance
+                  to render it into wizard page
+                 */
+                sqlControl = new (sqlField.get('control'))({
+                  field: sqlField,
+                  model: newModel
+                });
+
+              // Wizard for SQL tab control
+              var reviewSQLPage = self.reviewSQLPage = new pgBrowser.WizardPage({
+                id: 3,
+                page_title: _('Final (Review Selection) (step 3 of 3)'),
+                show_description: _('Following query will be executed on the database'
+                              +' server for the selected objects, and privileges.'
+                              +' Please click on Finish to complete the process.'),
+                model: newModel,
+                view: new(function() {
+
+                  // Render SqlTab control to generate its html markup
+                  var sqlTabHtml = sqlControl.render().$el;
+                  this.render = function() {
+                      return { el: sqlTabHtml };
+                  };
+                }),
+
+                beforePrev: function(wizardObj) {
+
+                  /**
+                    Enable next button if privilege
+                    model is not empty else disable
+                    next button
+                    */
+                  var aclModel = newModel.get('acl');
+
+                  if(!_.isUndefined(wizardObj.collection) &&
+                    wizardObj.collection.models.length > 0) {
+                    if(!_.isUndefined(aclModel) && !_.isEmpty(aclModel) &&
+                        aclModel.length > 0) {
+                      wizardObj.collection.at(1).set('disable_next', false);
+                    }
+                    else {
+                      wizardObj.collection.at(1).set('disable_next', true);
+                    }
+
+                    return true;
+                  }
+                },
+              });
+
+
+              // Create Wizard Collection of Wizard Pages
+              var WizardCollection = Backbone.Collection.extend({
+                model: pgBrowser.WizardPage
+              });
+
+              // It holds all the wizard pages to be rendered
+              this.wizardCollection = new WizardCollection(
+                [dbObjectTypePage, privilegePage, reviewSQLPage]
+              );
+
+              /**
+                Create wizard which has following operations:
+                - renders wizard pages
+                - defines the first page to render in wizard
+                - Save the model on finishbutton
+                - Remove wizard on cancel button
+                */
+              var wizard = self.wizard = new (pgBrowser.Wizard.extend({
+                options: {
+                  title: 'Grant Wizard', /* Main Wizard Title */
+                  width: '',
+                  height: '',
+                  curr_page: 0,
+                  show_left_panel: false,
+                  disable_finish: true
+                },
+
+                onFinish: function() {
+                  var m = newModel,
+                      d = m.toJSON('GET');
+
+                  if (d && !_.isEmpty(d) && !_.isUndefined(d.objects)) {
+                    m.save({}, {
+                      attrs: d,
+                      validate: false,
+                      cache: false,
+                      success: function(res) {
+
+                        // Release wizard objects
+                        self.releaseObjects();
+                        self.close();
+                      },
+                      error: function(m, jqxhr) {
+                        alertify.pgNotifier(
+                          "error", jqxhr,
+                          S(
+                            "{{ _('Error during saving properties - %%s!') }}"
+                            ).sprintf(jqxhr.statusText).value()
+                          );
+                      }
+                    });
+                  }
+                },
+                onCancel: function() {
+
+                  // Release wizard objects
+                  self.releaseObjects();
+                  self.close();
+                }
+              })) ({
+                collection: this.wizardCollection,
+                el: el,
+                model: newModel
+              });
+
+              // Render wizard
+              wizard.render();
+            }
+          };
+        });
+      }
+
+      // Call Grant Wizard Dialog
+      alertify.wizardDialog('');
+    }
+  };
+
+  return pgBrowser.GrantWizard;
+});
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/allowed_acl.json b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/allowed_acl.json
new file mode 100644
index 0000000..cb73d2c
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/allowed_acl.json
@@ -0,0 +1,30 @@
+{# List of allowed privileges of Schema, table sequence, function and view #}
+{#
+    Format for allowed privileges are:
+    "node_name": {
+        "type": "name",
+        "acl": [...]
+    }
+#}
+{
+    "schema": {
+        "type": "SCHEMA",
+        "acl": ["a", "r", "w", "d", "D", "x", "t", "U", "X"]
+    },
+    "coll-table": {
+        "type": "TABLE",
+        "acl": ["a", "w", "U", "d", "D", "x", "t"]
+    },
+    "coll-sequence": {
+        "type": "SEQUENCE",
+        "acl": ["a", "w", "u"]
+    },
+    "coll-function": {
+        "type": "FUNCTION",
+        "acl": ["X"]
+    },
+    "coll-view": {
+        "type": "VIEW",
+        "acl": ["a", "r", "w", "d", "D", "x", "t"]
+    }
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/grant.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/grant.sql
new file mode 100644
index 0000000..02ac1a7
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/grant.sql
@@ -0,0 +1,19 @@
+{#=====Grant Permissions on Database Objects Selected====#}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% import 'macros/functions/privilege.macros' as PRIVILEGE_FUNCTION %}
+{% if data.acl %}
+{% for obj in data.objects %}
+{% for priv in data.acl %}
+{% set obj_type = obj.object_type %}
+{% if obj_type.upper() != 'FUNCTION' and obj_type.upper() != 'TRIGGER FUNCTION' %}
+{% if obj_type.upper() == 'VIEW' %} {###### Views are also kind of table ######}
+{% set obj_type = 'TABLE' %}
+{% endif %}
+{{ PRIVILEGE.SET(conn, obj_type.upper(), priv.grantee, obj.name, priv.without_grant, priv.with_grant, nspname ) }}
+{% else %}
+{###### if object_type is Function then apply function marcros ######}
+{{ PRIVILEGE_FUNCTION.SET(conn, 'FUNCTION', priv.grantee, obj.name, priv.without_grant, priv.with_grant, nspname, obj.proargs)}}
+{% endif %}
+{% endfor %}
+{% endfor %}
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/properties.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/properties.sql
new file mode 100644
index 0000000..7a33c1f
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/properties.sql
@@ -0,0 +1,65 @@
+{#===================Fetch list of Database object types(Functions, Sequence, Tables or View)===================#}
+{% if type == 'schema' or type == 'trigger_function' and node_id and node_name %}
+{% set func_type = 'Trigger Function' if type == 'trigger_function' else 'Function' %}
+SELECT
+    pr.proname AS name,
+    pg_get_function_identity_arguments(pr.oid) AS proargs,
+    '{{ func_type }}' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-function' AS icon
+FROM pg_proc pr
+    JOIN pg_type typ ON typ.oid=prorettype
+    JOIN pg_namespace typns ON typns.oid=typ.typnamespace
+    JOIN pg_language lng ON lng.oid=prolang
+    LEFT OUTER JOIN pg_description des ON (des.objoid=pr.oid AND des.classoid='pg_proc'::regclass)
+WHERE proisagg = FALSE AND pronamespace = {{ node_id }}::oid
+    AND typname {{ 'NOT' if type != 'trigger_function' else '' }} IN ('trigger', 'event_trigger')
+ORDER BY proname
+{% endif %}
+
+{% if type == 'sequence' and node_id and node_name %}
+SELECT
+    cl.relname AS name,
+    'Sequence' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-sequence' AS icon
+FROM pg_class cl
+    LEFT OUTER JOIN pg_description des ON (des.objoid=cl.oid AND des.classoid='pg_class'::regclass)
+WHERE relkind = 'S' AND relnamespace  = {{ node_id }}::oid
+ORDER BY cl.relname
+{% endif %}
+
+{% if type == 'table' and node_id and node_name %}
+SELECT rel.relname AS name,
+    'Table' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-table' AS icon
+FROM pg_class rel
+    LEFT OUTER JOIN pg_tablespace spc ON spc.oid=rel.reltablespace
+    LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass)
+    LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'
+    LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid
+    LEFT JOIN pg_type typ ON rel.reloftype=typ.oid
+WHERE rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ node_id }}::oid
+ORDER BY rel.relname
+{% endif %}
+
+{% if type == 'coll-view' and node_id and node_name %}
+SELECT
+    c.relname AS name,
+    'View' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-coll-view' AS icon
+FROM pg_class c
+    LEFT OUTER JOIN pg_tablespace spc ON spc.oid=c.reltablespace
+    LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass)
+    LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid
+WHERE ((c.relhasrules AND (EXISTS (
+    SELECT r.rulename
+    FROM pg_rewrite r
+    WHERE ((r.ev_class = c.oid)
+        AND (bpchar(r.ev_type) = '1'::bpchar)) ))
+       ) OR (c.relkind = 'v'::char))
+AND c.relnamespace = {{ node_id }}::oid
+ORDER BY c.relname
+{% endif %}


^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-03-10 09:50  Surinder Kumar <[email protected]>
  parent: Surinder Kumar <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Surinder Kumar @ 2016-03-10 09:50 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: pgadmin-hackers

Please apply Khusboo's patch for "Privileges macros under Schema" before
using grant wizard patch.

On Thu, Mar 10, 2016 at 2:52 PM, Surinder Kumar <
[email protected]> wrote:

> Hi,
>
> PFA patch after rebasing.
>
> On Wed, Mar 9, 2016 at 11:04 PM, Dave Page <[email protected]> wrote:
>
>> Sorry Surinder - can you rebase this please? I think it was broken by
>> 8a7ec6b45221f042bc39c9bce2c577e12b43cc3a :-(
>
>
>>
> How much work would it be to enable it to work at database level as well?
>
> It depends on whether we need to enable grant wizard for all nodes under
> database level or
> enable it for specific nodes under database level.
>
>>
>> On Tue, Mar 8, 2016 at 1:48 PM, Surinder Kumar
>> <[email protected]> wrote:
>> > The menu option is dependent only on certain nodes like:
>> >
>> > 1. Schema Node
>> > 2. Views Collection Node
>> > 3. Tables Collection Node
>> > 4. Sequences Collection Node
>> > 5. Functions Collection Node
>> >
>> > It will be enabled on click of above listed nodes.
>> >
>> > On Tue, Mar 8, 2016 at 6:57 PM, Dave Page <[email protected]> wrote:
>> >>
>> >> On Tue, Mar 8, 2016 at 10:00 AM, Surinder Kumar
>> >> <[email protected]> wrote:
>> >> > Hi,
>> >> >
>> >> > I forgot to add 'node.ui.js' file in the previous patch, please
>> ignore
>> >> > the
>> >> > previous patch.
>> >> > Attached is the new patch,
>> >> >
>> >> > Please review the patch and let me know for any comments.
>> >>
>> >> Is this dependent on any other patches? The menu option never seems to
>> >> be enabled for me when I test against git-master.
>> >>
>> >> --
>> >> Dave Page
>> >> Blog: http://pgsnake.blogspot.com
>> >> Twitter: @pgsnake
>> >>
>> >> EnterpriseDB UK: http://www.enterprisedb.com
>> >> The Enterprise PostgreSQL Company
>> >
>> >
>>
>>
>>
>> --
>> Dave Page
>> Blog: http://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EnterpriseDB UK: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>>
>
>


^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-03-10 11:56  Dave Page <[email protected]>
  parent: Surinder Kumar <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Dave Page @ 2016-03-10 11:56 UTC (permalink / raw)
  To: Surinder Kumar <[email protected]>; +Cc: pgadmin-hackers

On Thu, Mar 10, 2016 at 9:50 AM, Surinder Kumar
<[email protected]> wrote:
> Please apply Khusboo's patch for "Privileges macros under Schema" before
> using grant wizard patch.

Thanks, that works. Some (hopefully final) feedback:

- Can we add a side-image? Not sure what yet - just a placeholder for
now until I come up with something.

- Why is the closed button in an odd position (see File -> Test Alert
for comparison)

- Why are we using a scrolling list AND pagination? I think a
scrolling list alone should be fine.

- The grid sizing is wrong. See how the scrollbar on the right in the
screenshot is off the edge of the dialogue, and there's a horizontal
scrollbar?

- We shouldn't truncate object names as that can be ambiguous. The
column should extend as necessary, and there should be a horizontal
scrollbar on the grid itself (not at the bottom of the dialogue
content).

- Function names should include the parameters, as they are part of
the identifier. Without, it can be ambigous - e.g. do_stuff(int) vs.
do_stuff(text).

- If I select only functions (for example), the Privileges panel
should only list privileges available for functions. If I select
multiple object types, it should show the available options for only
those object types.

- If I select functions and tables, and then choose (for example),
usage and truncate, it will attempt to set usage on tables and
truncate on functions. It should only attempt to set privileges on the
objects for which they are appropriate.

- On the last page, the Next button is disabled. It is turned a
marginally darker blue, but also highlights on mouse-over. The change
in shade is so subtle it's hard to see, and the highlight implies the
button is active when it isn't.

- The buttons appear to have a smaller corner radius than those on the
main browser, or in other dialogues.

- Button labels should have an &nbsp; before them to properly space
the label from the icon (or better yet, this should be done in CSS,
though that would also need to be done elsewhere).

- Why do the URLs have a /wizard prefix? I think that should be removed.

- The available privileges for each object type seem to be defined in
both grant_wizard.js and allowed_acl.json. Can we just use
allowed_acl.json?

Thanks.

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [image/png] Screen Shot 2016-03-10 at 11.22.15.png (68.9K, 2-Screen%20Shot%202016-03-10%20at%2011.22.15.png)
  download | view image

^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-03-28 08:39  Surinder Kumar <[email protected]>
  parent: Dave Page <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Surinder Kumar @ 2016-03-28 08:39 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: pgadmin-hackers

Hi Dave,

Please find updated patch with suggested changes.

On Thu, Mar 10, 2016 at 5:26 PM, Dave Page <[email protected]> wrote:

> On Thu, Mar 10, 2016 at 9:50 AM, Surinder Kumar
> <[email protected]> wrote:
> > Please apply Khusboo's patch for "Privileges macros under Schema" before
> > using grant wizard patch.
>
> Thanks, that works. Some (hopefully final) feedback:
>
> - Can we add a side-image? Not sure what yet - just a placeholder for
> now until I come up with something.
>
Already a placeholder for left side image.

>
> - Why is the closed button in an odd position (see File -> Test Alert
> for comparison)
>
Fixed

>
> - Why are we using a scrolling list AND pagination? I think a
> scrolling list alone should be fine.
>
Pagination is removed.

>
> - The grid sizing is wrong. See how the scrollbar on the right in the
> screenshot is off the edge of the dialogue, and there's a horizontal
> scrollbar?
>
Fixed.

>
> - We shouldn't truncate object names as that can be ambiguous. The
> column should extend as necessary, and there should be a horizontal
> scrollbar on the grid itself (not at the bottom of the dialogue
> content).
>
I have fixed it and scrollbar is now on the grid itself.

>
> - Function names should include the parameters, as they are part of
> the identifier. Without, it can be ambigous - e.g. do_stuff(int) vs.
> do_stuff(text).
>
Fixed.

>
> - If I select only functions (for example), the Privileges panel
> should only list privileges available for functions. If I select
> multiple object types, it should show the available options for only
> those object types.
>
Implemented this feature.

>
> - If I select functions and tables, and then choose (for example),
> usage and truncate, it will attempt to set usage on tables and
> truncate on functions. It should only attempt to set privileges on the
> objects for which they are appropriate.
>
Done

>
> - On the last page, the Next button is disabled. It is turned a
> marginally darker blue, but also highlights on mouse-over. The change
> in shade is so subtle it's hard to see, and the highlight implies the
> button is active when it isn't.
>
Fixed.

>
> - The buttons appear to have a smaller corner radius than those on the
> main browser, or in other dialogues.
>
Fixed.

>
> - Button labels should have an &nbsp; before them to properly space
> the label from the icon (or better yet, this should be done in CSS,
> though that would also need to be done elsewhere).
>
done with css.

>
> - Why do the URLs have a /wizard prefix? I think that should be removed.
>
Removed prefix.

>
> - The available privileges for each object type seem to be defined in
> both grant_wizard.js and allowed_acl.json. Can we just use
> allowed_acl.json?
>
Yes, allowed_acl.json is now used in grant_wizard.js.

>
> Thanks.
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [application/octet-stream] grant_wizard_v5.patch (73.9K, 3-grant_wizard_v5.patch)
  download | inline diff:
diff --git a/web/pgadmin/browser/static/css/wizard.css b/web/pgadmin/browser/static/css/wizard.css
new file mode 100644
index 0000000..ca617b6
--- /dev/null
+++ b/web/pgadmin/browser/static/css/wizard.css
@@ -0,0 +1,148 @@
+
+/** CSS for Wizard **/
+.ajs-content {
+  padding-top: 0px !important;
+}
+
+/* Wizard Header CSS */
+.alertify .ajs-dialog .ajs-close {
+  margin-top: 15px;
+}
+
+.wizard-header {
+  background: #428bca;
+  padding-left: 15px;
+  padding-bottom: 7px;
+  color: #fff;
+  font-size: 18px;
+  height: 76px;
+  line-height: 76px;
+  border-radius: 6px 6px 0 0;
+}
+
+.wizard-header h3 {
+  font-size: 18px;
+  display: inline-block;
+}
+
+.wizard_dlg {
+  float: left;
+  height: 100%;
+  width: 100%;
+}
+
+.grant_wizard_container {
+	position: relative;
+  width: 100%;
+  height: 100%;
+}
+
+.pgadmin-wizard {
+  width: 100%;
+  height: 100%;
+}
+
+.wizard-content {
+  position: relative;
+  padding: 0;
+  height: 64%;
+}
+
+.wizard-right-panel {
+  overflow-y: auto;
+  height: 100%;
+  top: 0;
+  right: 0;
+  position: absolute;
+  bottom: 0;
+}
+
+.wizard-left-panel {
+  position: absolute;
+  top: 0;
+  display: flex;
+  bottom: 0;
+  left: 0;
+  align-items: center;
+  justify-content: center;
+  right: 0;
+}
+
+.wizard-left-panel img {
+  width: 140px;
+}
+
+.wizard-right-panel_content {
+	height: 60%;
+}
+
+.select_db_objects_container {
+  height: 100%;
+}
+
+/* Wizard Footer CSS */
+.footer {
+  position: absolute;
+  background: #fff;
+  border-top: 1px solid #ccc;
+  bottom: 0px;
+  height: 62px;
+  right: 0px;
+  padding-top: 22px;
+  border-radius: 0 0 6px 6px;
+  z-index: 10;
+}
+
+/* Wizard Button CSS */
+.wizard-buttons {
+  float: right;
+}
+
+.wizard-buttons button {
+  float: left;
+  padding: 7px 15.2px;
+  font-size: 14px;
+  margin: 0px 5px 0 0 !important;
+}
+
+.wizard-buttons button.wizard-next i.fa {
+	padding-left: 5px;
+}
+
+.wizard-buttons button.wizard-back i.fa,
+.wizard-buttons button.wizard-cancel i.fa {
+	padding-right: 5px;
+}
+
+.wizard-buttons .wizard-finish {
+  margin-right: 0 !important;
+}
+
+/* Wizard Status bar CSS */
+.wizard-description {
+  padding: 1.7em 0.1em;
+}
+
+/* Error message css */
+.error_msg_div {
+  display: block;
+  position: absolute;
+  bottom: 55px;
+  background: #fff;
+}
+
+.error_msg_div p {
+  background: #fff;
+  color: #b92c28;
+}
+
+/* In wizard select2 dropdown doesn't
+ * popup because z-index of alertify
+ * wizard is greater than the z-index
+ * of select2 dropdown. To make select2
+ * visible, set z-index of select2
+ * higher value than wizard's
+ */
+.select2-container--open {
+  z-index: 10000;
+}
diff --git a/web/pgadmin/browser/static/js/node.ui.js b/web/pgadmin/browser/static/js/node.ui.js
index f127d52..42e7433 100644
--- a/web/pgadmin/browser/static/js/node.ui.js
+++ b/web/pgadmin/browser/static/js/node.ui.js
@@ -279,7 +279,7 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
         return false;
       };
 
-      while(p) {
+      while(p & p.length > 0) {
         top = p.get(0).offsetTop + p.height();
         p = p.parent();
         if (hasScrollbar(p)) {
diff --git a/web/pgadmin/browser/static/js/wizard.js b/web/pgadmin/browser/static/js/wizard.js
new file mode 100644
index 0000000..924ff3a
--- /dev/null
+++ b/web/pgadmin/browser/static/js/wizard.js
@@ -0,0 +1,222 @@
+define(
+    ['underscore', 'backbone', 'pgadmin', 'pgadmin.browser'],
+function(_, Backbone, pgAdmin, pgBrowser) {
+
+  pgBrowser = pgBrowser || pgAdmin.Browser || {};
+
+  /* Wizard individual Page Model */
+  var WizardPage = pgBrowser.WizardPage = Backbone.Model.extend({
+    defaults: {
+      id: undefined, /* Id */
+      page_title: undefined, /* Page Title */
+      view: undefined, /* A Backbone View */
+      html: undefined, /* HTML tags to be rendered */
+      image: undefined, /* Left hand side image */
+      disable_prev: false, /*  Previous Button Flag */
+      disable_next: false, /*  Next Button Flag */
+      disable_cancel: false, /* Cancel Button Flag */
+      show_progress_bar: '',
+      /* Callback for OnLoad */
+      onLoad: function() {
+        return true;
+      },
+      /* Callback for before Next */
+      beforeNext: function() {
+        return true;
+      },
+      onNext: function(){},
+      onBefore: function() {},
+      /* Callback for before Previous */
+      beforePrev: function() {
+        return true;
+      }
+    }
+  });
+
+  var Wizard = pgBrowser.Wizard = Backbone.View.extend({
+    options: {
+      title: 'Wizard', /* Main Wizard Title */
+      image: 'left_panel.png', /* TODO:: We can use default image here */
+      curr_page: 0, /* Current Page to Load */
+      disable_next: false,
+      disable_prev: false,
+      disable_finish: false,
+      disable_cancel: false,
+      height: 400,
+      width: 650,
+      show_left_panel: true
+    },
+    tmpl: _.template(
+       "    <div class='pgadmin-wizard' style='height: <%= this.options.height %>px; width: <%= this.options.width %>px'>"
+       + "      <div class='wizard-header wizard-badge'>"
+       + "        <div class='row'>"
+       + "          <div class='col-sm-9'>"
+       + "              <h3><span id='main-title'><%= this.options.title %></span> - <span id='step-title'><%= page_title %></span></h3>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "      <div class='wizard-content col-sm-12'>"
+       + "        <% if(this.options.show_left_panel) { %>"
+       + "          <div class='col-sm-3 wizard-left-panel'>"
+       + "              <img src='<%= this.options.image %>'></div>"
+       + "        <% } %>"
+       + "        <div class='col-sm-<% if(this.options.show_left_panel){ %>9<% } else { %>12<% } %> wizard-right-panel'>"
+       + "          <% if( typeof show_description !=  'undefined'){ %>"
+       + "            <div class='wizard-description'>"
+       + "              <%= show_description %>"
+       + "            </div>"
+       + "          <% } %>"
+       + "          <div class='wizard-right-panel_content'>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "      <div class='col-sm-12 error_msg_div'>"
+       + "       <p></p>"
+       + "      </div>"
+       + "      <div class='footer col-sm-12'>"
+       + "        <div class='row'>"
+       + "          <div class='col-sm-4 wizard-progress-bar'>"
+       + "             <p><% if(show_progress_bar){ %><%= show_progress_bar %><% } %></p>"
+       + "          </div>"
+       + "          <div class='col-sm-8'>"
+       + "            <div class='wizard-buttons'>"
+       + "              <button class='btn btn-primary wizard-back' <%=this.options.disable_prev ? 'disabled' : ''%>><i class='fa fa-backward'></i>Back</button>"
+       + "              <button class='btn btn-primary wizard-next' <%=this.options.disable_next ? 'disabled' : ''%>>Next<i class='fa fa-forward'></i></button>"
+       + "              <button class='btn btn-danger wizard-cancel' <%=this.options.disable_cancel ? 'disabled' : ''%>><i class='fa fa-lg fa-close'></i>Cancel</button>"
+       + "              <button class='btn btn-primary wizard-finish' <%=this.options.disable_finish ? 'disabled' : ''%>>Finish</button>"
+       + "            </div>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "    </div>"),
+    events: {
+      "click button.wizard-next" : "nextPage",
+      "click button.wizard-back" : "prevPage",
+      "click button.wizard-cancel" : "onCancel",
+      "click button.wizard-finish" : "finishWizard",
+    },
+    initialize: function(options) {
+      this.options = _.extend({}, this.options, options.options);
+      this.currPage = this.collection.at(this.options.curr_page).toJSON();
+    },
+    render: function() {
+      var data = this.currPage;
+
+      /* Check Status of the buttons */
+      this.options.disable_next = (this.options.disable_next ? true : this.evalASFunc(this.currPage.disable_next));
+      this.options.disable_prev = (this.options.disable_prev ? true : this.evalASFunc(this.currPage.disable_prev));
+      this.options.disable_cancel = (this.currPage.canCancel ? true : this.evalASFunc(this.currPage.disable_cancel));
+
+      that = this;
+
+      /* HTML Content */
+      if (data.html) { data.content = data.html; }
+      /* Backbone View */
+      else if (data.view) { data.content = data.view.render().el;}
+
+      $(this.el).html(this.tmpl(data));
+      $(this.el).find(".wizard-right-panel_content").html(data.content);
+
+      /* OnLoad Callback */
+      this.onLoad();
+
+      return this;
+    },
+    nextPage: function() {
+      this.options.curr_page.el = this.$el;
+      if (!this.beforeNext()) { return false; }
+
+      page_id = this.onNext();
+
+      if (page_id ) {
+        this.currPage = this.collection.get(page_id).toJSON();
+        this.options.curr_page = this.collection.indexOf(this.collection.get(page_id));
+      }
+      else if (this.options.curr_page < (this.collection.length-1)) {
+        this.options.curr_page = this.options.curr_page + 1;
+        this.currPage = this.collection.at(this.options.curr_page).toJSON();
+      }
+
+      this.enableDisableNext();
+      this.enableDisablePrev();
+
+      return this.render();
+    },
+    prevPage: function() {
+      if (!this.beforePrev()) { return false; }
+
+      page_id = this.onPrev();
+
+      if (page_id){
+        this.currPage = this.collection.get(page_id).toJSON();
+        this.options.curr_page = this.collection.indexOf(this.collection.get(page_id));
+      }
+      else if (this.options.curr_page > 0) {
+        this.options.curr_page = this.options.curr_page - 1;
+        this.currPage = this.collection.at(this.options.curr_page).toJSON();
+      }
+
+      this.enableDisableNext();
+      this.enableDisablePrev();
+
+      return this.render();
+    },
+    finishWizard: function() {
+      this.onFinish();
+      return true;
+    },
+    enableDisableNext: function(disable) {
+        if (typeof(disable) != 'undefined') {
+          this.options.disable_next = disable;
+        }
+        else if (this.options.curr_page >= (this.collection.length-1)) {
+          this.options.disable_next = true;
+        }
+        else {
+          this.options.disable_next = false;
+        }
+    },
+    enableDisablePrev: function(disable) {
+        if (typeof(disable) != 'undefined') {
+          this.options.disable_prev = disable;
+        }
+        else if (this.options.curr_page <= 0) {
+          this.options.disable_prev = true;
+        }
+        else {
+          this.options.disable_prev = false;
+        }
+    },
+    beforeNext: function(){
+      return this.evalASFunc(this.currPage.beforeNext);
+    },
+    beforePrev: function(){
+      return this.evalASFunc(this.currPage.beforePrev);
+    },
+    onPrev: function(){
+      return this.evalASFunc(this.currPage.onPrev);
+    },
+    onNext: function(){
+      return this.evalASFunc(this.currPage.onNext);
+    },
+    onLoad: function() {
+      return this.evalASFunc(this.currPage.onLoad);
+    },
+    onFinish: function() {
+      return true;
+    },
+    onCancel: function() {
+      this.$el.remove();
+      return true;
+    },
+    evalASFunc: function(func, ctx) {
+      var self = this;
+      ctx = ctx || self.currPage;
+
+      return (_.isFunction(func) ? func.apply(ctx, [self]) : func);
+    }
+  });
+
+  return pgBrowser;
+
+});
diff --git a/web/pgadmin/tools/grant_wizard/__init__.py b/web/pgadmin/tools/grant_wizard/__init__.py
new file mode 100644
index 0000000..c71818e
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/__init__.py
@@ -0,0 +1,386 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Implements Grant Wizard"""
+
+import json
+from flask import render_template, request, current_app
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_response as ajax_response, \
+    make_json_response, internal_server_error
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+# from pgadmin.browser.server_groups.servers.utils import parse_priv_to_db
+from pgadmin.utils import PgAdminModule
+from flask import Response, url_for
+from flask.ext.security import login_required
+from urllib import unquote
+
+# set template path for sql scripts
+template_path = 'grant_wizard/sql'
+MODULE_NAME = 'grant_wizard'
+
+
+class GrantWizardModule(PgAdminModule):
+    """
+    class GrantWizardModule(Object):
+
+        It is a wizard which inherits PgAdminModule
+        class and define methods to load its own
+        javascript file.
+    """
+    def get_own_stylesheets(self):
+        """
+        Returns:
+            list: the stylesheets used by this module.
+        """
+        stylesheets = [
+            url_for('browser.static', filename='css/wizard.css'),
+            url_for('grant_wizard.static', filename='css/grant_wizard.css')
+            ]
+        return stylesheets
+
+    def get_own_javascripts(self):
+        """"
+        Returns:
+            list: js files used by this module
+        """
+        scripts = []
+        scripts.append({
+            'name': 'pgadmin.tools.grant_wizard',
+            'path': url_for('grant_wizard.index') + 'grant_wizard',
+            'when': None
+        })
+        scripts.append({
+            'name': 'pgadmin.browser.wizard',
+            'path': url_for('browser.static', filename='js/wizard'),
+            'when': None
+        })
+        return scripts
+
+# Create blueprint for GrantWizardModule class
+blueprint = GrantWizardModule(
+    MODULE_NAME, __name__, static_url_path='')
+
+
[email protected]("/")
+@login_required
+def index():
+    pass
+
+
[email protected]("/grant_wizard.js")
+@login_required
+def script():
+    """render own javascript"""
+    return Response(response=render_template(
+                "grant_wizard/js/grant_wizard.js", _=gettext),
+                status=200,
+                mimetype="application/javascript")
+
+
[email protected]("/allowed_acl.json")
+@login_required
+def acl_list():
+    """render list of acls"""
+    return Response(response=render_template(
+                "grant_wizard/sql/allowed_acl.json", _=gettext),
+                status=200,
+                mimetype="application/javascript")
+
+
[email protected](
+    '/properties/<int:gid>/<int:sid>/<int:did>'
+    '/<int:node_id>/<node_name>/<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+@login_required
+def properties(gid, sid, did, node_id, node_name, node_type, nspname):
+    """It fetches the properties of object types
+       and render into selection page of wizard
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+    nspname = unquote(nspname)
+
+    res_data = []
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    # Fetch functions against schema
+    if node_type in ['schema']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type=node_type, nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch trigger functions
+    if node_type in ['schema', 'trigger_function']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='trigger_function', nspname=nspname)
+        status, res = conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch Sequences against schema
+    if node_type in ['schema', 'sequence']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='sequence', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+        res_data.extend(res['rows'])
+
+    # Fetch Tables against schema
+    if node_type in ['schema', 'table']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='table', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch Views against schema
+    if node_type in ['schema', 'view']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='view', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    return ajax_response(
+            response=res_data,
+            status=200
+            )
+
+
+def parse_priv_to_db(str_privileges, acls):
+    """
+    Common utility function to parse privileges before sending to database.
+    """
+    db_privileges = {
+        'c': 'CONNECT',
+        'C': 'CREATE',
+        'T': 'TEMPORARY',
+        'a': 'INSERT',
+        'r': 'SELECT',
+        'w': 'UPDATE',
+        'd': 'DELETE',
+        'D': 'TRUNCATE',
+        'x': 'REFERENCES',
+        't': 'TRIGGER',
+        'U': 'USAGE',
+        'X': 'EXECUTE'
+        }
+
+    privileges = []
+
+    for priv in str_privileges:
+        priv_with_grant = {}
+        priv_without_grant = {}
+        priv_function_with_grant = []
+        priv_function_without_grant = []
+        priv_sequence_with_grant = []
+        priv_sequence_without_grant = []
+        priv_table_with_grant = []
+        priv_table_without_grant = []
+
+        # import pdb
+        # pdb.set_trace()
+        for privilege in priv['privileges']:
+
+            if privilege['privilege_type'] not in db_privileges:
+                continue
+
+            # Extract privileges applicable to function
+            if privilege['privilege_type'] in acls['function']['acl']:
+                if privilege['with_grant']:
+                    priv_function_with_grant.append(
+                        db_privileges[privilege['privilege_type']])
+                elif privilege['privilege']:
+                    priv_function_without_grant.append(
+                        db_privileges[privilege['privilege_type']])
+
+            # Extract privileges applicable to sequence
+            if privilege['privilege_type'] in acls['sequence']['acl']:
+                if privilege['with_grant']:
+                    priv_sequence_with_grant.append(
+                        db_privileges[privilege['privilege_type']])
+                elif privilege['privilege']:
+                    priv_sequence_without_grant.append(
+                        db_privileges[privilege['privilege_type']])
+
+            # Extract privileges applicable to view/table
+            if (privilege['privilege_type'] in acls['table']['acl']):
+                if privilege['with_grant']:
+                    priv_table_with_grant.append(
+                        db_privileges[privilege['privilege_type']])
+                elif privilege['privilege']:
+                    priv_table_without_grant.append(
+                        db_privileges[privilege['privilege_type']])
+
+        # If we have all acl then just return all
+        if len(priv_table_with_grant) == len(acls['table']['acl']):
+            priv_table_with_grant = ['ALL']
+
+        if len(priv_table_without_grant) == len(acls['table']['acl']):
+            priv_table_without_grant = ['ALL']
+
+        # Appending and returning all ACL
+        priv_with_grant['function'] = priv_function_with_grant
+        priv_without_grant['function'] = priv_function_without_grant
+
+        priv_with_grant['sequence'] = priv_sequence_with_grant
+        priv_without_grant['sequence'] = priv_sequence_without_grant
+
+        priv_with_grant['table'] = priv_table_with_grant
+        priv_without_grant['table'] = priv_table_without_grant
+
+        privileges.append({
+            'grantee': priv['grantee'],
+            'with_grant': priv_with_grant,
+            'without_grant': priv_without_grant
+            })
+
+    return privileges
+
+
[email protected](
+    '/msql/<int:gid>/<int:sid>/<int:did>/<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+def msql(gid, sid, did, node_type, nspname):
+    """
+    This function will return modified SQL
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+    nspname = unquote(nspname)
+
+    data = {}
+    for k, v in request.args.items():
+        try:
+            data[k] = json.loads(v)
+        except ValueError:
+            data[k] = v
+
+    # Form db connection
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    acls = []
+    try:
+        acls = render_template(
+            "/".join([template_path, 'allowed_acl.json'])
+            )
+        acls = json.loads(acls)
+    except Exception as e:
+        current_app.logger.exception(e)
+
+    try:
+
+        # Parse privileges
+        if 'acl' in data:
+            data['acl'] = parse_priv_to_db(data['acl'], acls)
+
+        # Pass database objects and get SQL for privileges
+        SQL = render_template("/".join(
+            [template_path, 'grant.sql']),
+            data=data, nspname=nspname, conn=conn)
+
+        res = {'data': SQL}
+
+        return ajax_response(
+            response=res,
+            status=200
+            )
+
+    except Exception as e:
+        return make_json_response(
+            status=410,
+            success=0,
+            errormsg=e.message
+        )
+
+
[email protected](
+    '/save/<int:gid>/<int:sid>/<int:did>/'
+    '<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+def save(gid, sid, did, node_type, nspname):
+    """
+    This function will apply the privileges to the selected
+    Database Objects
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+    nspname = unquote(nspname)
+
+    data = request.form if request.form else json.loads(request.data.decode())
+
+    # Form db connection and we use conn to execute sql
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    acls = []
+    try:
+        acls = render_template(
+            "/".join([template_path, 'allowed_acl.json'])
+            )
+        acls = json.loads(acls)
+    except Exception as e:
+        current_app.logger.exception(e)
+
+    try:
+
+        # Parse privileges
+        if 'acl' in data:
+            data['acl'] = parse_priv_to_db(data['acl'], acls)
+
+        # Pass database objects and get SQL for privileges
+        SQL = render_template("/".join(
+            [template_path, 'grant.sql']),
+            data=data, nspname=nspname, conn=conn)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        return make_json_response(
+            success=1,
+            info="Privileges Applied"
+        )
+
+    except Exception as e:
+        return internal_server_error(errormsg=e.message)
diff --git a/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
new file mode 100644
index 0000000..46c9203
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
@@ -0,0 +1,86 @@
+/** Grant Wizard CSS **/
+
+/**
+  CSS to make db object type table
+  fixed so that tbody content may
+  scroll
+ */
+.object_type_table thead tr {
+  position: relative;
+  display: block;
+}
+
+.object_type_table {
+	display: inline-block;
+  height: 100%;
+  border: 0 !important;
+}
+
+.object_type_table tbody {
+  display: block;
+  overflow: scroll;
+  border: 1px solid #ddd;
+  width: 100%;
+  min-height: 100%;
+  max-height: 66%;
+  height: 66%;
+}
+
+.object_type_table tbody tr td {
+	background-position: 4px 4px;
+	border-radius: 0;
+}
+
+.object_type_table tbody tr td:nth-child(1),
+.object_type_table thead tr th:nth-child(1) {
+  width: 28px;
+  min-width: 28px;
+}
+
+.object_type_table tbody tr td:nth-child(2),
+.object_type_table thead tr th:nth-child(2) {
+  width: 185px;
+  min-width: 185px;
+}
+
+.object_type_table tbody tr td:nth-child(3),
+.object_type_table thead tr th:nth-child(3) {
+  width: 110px;
+  min-width: 110px;
+}
+
+.object_type_table thead tr th:nth-child(4) {
+	width: 244px;
+}
+
+.object_type_table tbody tr td:nth-child(4) {
+  width: 244px;
+  min-width: 228px;
+  max-width: 100%;
+}
+
+/** Override Backgrid filter CSS **/
+.backgrid-filter.form-search {
+  float: left;
+  margin: 0px 5px 5px 0;
+}
+
+/** Custom styling for Codemirror field **/
+.wizard-right-panel_content {
+  border: 1px solide #ccc;
+}
+
+.wizard-right-panel_content .CodeMirror {
+  border: 1px solid #ccc;
+  height: 285px !important;
+  min-height: 285px !important;
+}
+
+.wizard-right-panel_content .CodeMirror-linenumber {
+  background: #F7F7F7;
+  border-right: 1px solid #DDDDDD;
+}
+
+.wizard-right-panel_content .CodeMirror-gutters {
+  min-height: 285px !important;
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
new file mode 100644
index 0000000..297ab51
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
@@ -0,0 +1,1083 @@
+define([
+      'jquery', 'underscore', 'underscore.string', 'alertify',
+      'pgadmin.browser', 'backbone', 'backgrid', 'pgadmin.browser.node',
+      'backgrid.select.all', 'backgrid.filter', 'pgadmin.browser.server.privilege',
+      'pgadmin.browser.wizard',
+      ],
+
+  // This defines Grant Wizard dialog
+  function($, _, S, alertify, pgBrowser, Backbone, Backgrid, pgNode) {
+
+    // if module is already initialized, refer to that.
+    if (pgBrowser.GrantWizard) {
+      return pgBrowser.GrantWizard;
+    }
+
+    /**
+      It is sub model for field "Objects". It has fields
+      for database object types such as Schemas, Views and
+      Sequence etc.
+    */
+    var DatabaseObjectModel = pgNode.Model.extend({
+      defaults: {
+        selected: false,
+        icon: 'icon-unknown',
+        name: undefined,
+        name_with_args: undefined,
+        nspname: undefined,
+        proargs: undefined,
+        object_type: undefined,
+        object_id: undefined
+      },
+      idAttribute: 'object_id', // to uniquely identify a model object
+      toJSON: function(obj) {
+        var d = pgNode.Model.prototype.toJSON.apply(this);
+        delete d.icon;
+        return d;
+      },
+      parse: function(res) {
+
+        // Create unique object id
+        res.object_id = res.name_with_args;
+
+        // create name with args if its object is function
+        if(!_.isUndefined(res.object_type) && (res.object_type == 'Function' ||
+          res.object_type == 'Trigger Function'))
+          res.name_with_args = res.name+'('+(typeof(res.proargs) != 'undefined' ? res.proargs : '')+')';
+        else
+          res.name_with_args = res.name;
+
+        return res;
+      },
+
+      validate: function() {
+
+        /*
+        * Triggers error messages for object types "selected"
+        * if it is empty/undefined/null
+        */
+        var err = {},
+          errmsg,
+          node = this.get('objects').toJSON();
+        if (_.isEmpty(node)) {
+          err['selected'] = '{{ _("Please select any database object type") }}';
+          errmsg = errmsg || err['selected'];
+          this.errorModel.set('selected', errmsg);
+          return errmsg;
+        } else {
+          this.errorModel.unset('selected');
+        }
+        return null;
+      }
+    });
+
+    // Define columns for the Db Object Types grid
+    var columns = [{
+      name: "selected",
+
+      /*
+      Override render method of Backgrid.Extension.SelectRowCell
+      class. It has an issue: It doesn't mark rows checked if we move to next
+      page and then go back to previous page. but it must show.
+      so we handle this case by overriding the render method.
+      */
+      cell: Backgrid.Extension.SelectRowCell.extend({
+        render: function() {
+
+          // Use the Backform Control's render function
+          Backgrid.Extension.SelectRowCell.prototype.render.apply(this, arguments);
+
+          var col = this.column.get('name');
+          if (this.model && this.model.has(col)) {
+            if (this.model.get(col)) {
+              this.checkbox().prop("checked", true);
+              this.$el.parent().toggleClass("selected", true);
+              this.model.trigger("backgrid:selected", this.model, true);
+            }
+          }
+          return this;
+        }
+      }),
+
+      headerCell: "select-all",
+
+    },{
+      name: "object_type",
+      label: "Object Type",
+      editable: false,
+      cell: Backgrid.Cell.extend({
+        render: function() {
+
+          // Override render to add icon to Db Object column
+          Backgrid.Cell.prototype.render.apply(this, arguments);
+          this.$el.addClass(this.model.get('icon')).css({"padding-left": "24px"});
+
+          return this;
+        }
+      })
+    },{
+      name: "nspname",
+      label: "Schema",
+      cell: "string",
+      editable: false
+    },{
+      name: "name_with_args",
+      label: "Name",
+      cell: "string",
+      editable: false
+    }];
+
+    // Create an Object GrantWizard of pgBrowser class
+    pgBrowser.GrantWizard  = {
+      init: function() {
+        if (this.initialized)
+          return;
+
+        this.initialized = true;
+
+        // Define list of nodes on which grant wizard context menu option appears
+        var supported_nodes = [
+              'schema', 'coll-function',
+              'coll-sequence', 'coll-table',
+              'coll-view', 'coll-materialized_view'
+            ],
+
+            /**
+              Enable/disable grantwizard menu in tools based
+              on node selected
+              if selected node is present in supported_nodes,
+              menu will be enabled otherwise disabled.
+              Also, hide it for system view in catalogs
+            */
+            menu_enabled = function(itemData, item, data) {
+              var t = pgBrowser.tree, i = item, d = itemData;
+              var parent_item = t.hasParent(i) ? t.parent(i): null,
+                  parent_data = parent_item ? t.itemData(parent_item) : null;
+              if(!_.isUndefined(d) && !_.isNull(d) && !_.isNull(parent_data))
+                return ((_.indexOf(supported_nodes, d._type) !== -1 && parent_data._type != 'catalog') ? true: false);
+              else
+                return false;
+            };
+
+        // Define the nodes on which the menus to be appear
+        var menus = [{
+          name: 'grant_wizard_schema', module: this,
+          applies: ['tools'], callback: 'start_grant_wizard',
+          priority: 10, label: '{{_("Grant Wizard...") }}',
+          icon: 'wcTabIcon', enable: menu_enabled
+        }];
+
+        // Add supported menus into the menus list
+        for (var idx = 0; idx < supported_nodes.length; idx++) {
+          menus.push({
+            name: 'grant_wizard_schema_context_' + supported_nodes[idx],
+            node: supported_nodes[idx], module: this,
+            applies: ['context'], callback: 'start_grant_wizard',
+            priority: 10, label: '{{_("Grant Wizard...") }}',
+            icon: 'wcTabIcon', enable: menu_enabled
+            });
+        }
+        pgAdmin.Browser.add_menus(menus);
+
+        return this;
+      },
+
+      // Callback to draw Wizard Dialog
+      start_grant_wizard: function(action, item) {
+
+        // Declare Wizard dialog
+        if (!alertify.wizardDialog) {
+          alertify.dialog('wizardDialog', function factory() {
+
+            // Generate wizard main container
+            var $container = $("<div class='wizard_dlg'></div>");
+
+            return {
+              main: function(title) {
+                this.set('title', title);
+              },
+              setup:function() {
+                return {
+
+                  // Set options for dialog
+                  options: {
+                    frameless: true,
+                    resizable: false,
+                    autoReset: false,
+                    maximizable: false,
+                    closableByDimmer: false
+                  }
+                };
+              },
+              hooks:{
+                onshow: function() {
+
+                  // Set dimensions for wizard
+                  this.elements.dialog.style.width = '100%';
+                  this.elements.dialog.style.height = '68%';
+
+                  // Add pgadmin_grant_wizard_body class to dialog
+                  $(this.elements.body).addClass('pgadmin_grant_wizard_body');
+                },
+              },
+
+              /**
+                Returns a Paginator Class Object which is again to be rendered
+
+                @class {Backgrid.Extension.Paginator}
+                @param {Backbone.Collection} coll - from which data is fetched
+                @return {Object} paginator
+              */
+              DbPaginator: function(coll){
+                var paginator = this.paginator = new Backgrid.Extension.Paginator({
+                  collection: coll,
+                  windowSize: 8
+                });
+                return paginator;
+              },
+
+              /**
+                Create new Filter which will filter the
+                rendered grid for Select Type Tabular Data
+                @param {Backbone.PageableCollection} coll
+              */
+              DbObjectFilter: function(coll){
+                var clientSideFilter = this.clientSideFilter = new Backgrid.Extension.ClientSideFilter({
+                  collection: coll,
+                  placeholder: _('Search by object type or name'),
+
+                  // The model fields to search for matches
+                  fields: ['object_type', 'name'],
+
+                  // How long to wait after typing has stopped before searching can start
+                  wait: 150
+                });
+                return clientSideFilter;
+              },
+
+              //Enable Disable Next button of Page 2
+              updateButtons: function(modified){
+                if(!modified)
+                  $('.wizard-next').prop('disabled', true);
+                else
+                  $('.wizard-next').prop('disabled', false);
+              },
+
+              /**
+                Callback called when an errorModel is set
+                with invalid value and errormsg is set into
+                status bar element and next button is disabled
+              */
+              onSessionInvalid: function(msg) {
+                $('.error_msg_div p').html(msg).removeClass("hide");
+
+                // Enable disable Next button
+                this.updateButtons(false);
+                return true;
+              },
+
+              /**
+                Callback called when anything is set into model
+                thus hide error msg element and enable next button
+                status bar element and next button is disabled
+              */
+              onSessionValidated: function(sessHasChanged) {
+                $('.error_msg_div p').empty().addClass("hide");
+
+                // Enable disable Next button
+                this.updateButtons(sessHasChanged);
+              },
+
+              /**
+                Remove/Delete objects, attributes
+                in wizard on wizard close or finish
+                to reclaim memory
+              */
+              releaseObjects: function(){
+                var self = this;
+
+                if(!_.isUndefined(self.dbObjectFilter)) {
+                  self.dbObjectFilter.remove();
+                  self.dbObjectFilter = undefined;
+                }
+
+                if(!_.isUndefined(self.clientSideFilter)) {
+                  self.clientSideFilter.remove();
+                  self.clientSideFilter = undefined;
+                }
+
+                // clear object priv array
+                if(!_.isNull(self.obj_priv) &&
+                  !_.isUndefined(self.obj_priv)) {
+                  self.obj_priv = [];
+                  delete self.obj_priv;
+                }
+
+                // Delete Wizard Pages
+                if(!_.isUndefined(self.dbObjectTypePage) &&
+                  !_.isNull(self.dbObjectTypePage)) {
+                  if(!_.isUndefined(self.dbObjectTypePage.get('model')) &&
+                    !_.isNull(self.dbObjectTypePage.get('model'))) {
+                    self.dbObjectTypePage.get('model').clear();
+                    self.dbObjectTypePage.get('view').cleanup();
+                    self.dbObjectTypePage = undefined;
+                  }
+                }
+
+                if(!_.isUndefined(self.privilegePage) &&
+                  !_.isNull(self.privilegePage)) {
+                  if(!_.isUndefined(self.privilegePage.get('model')) &&
+                    !_.isNull(self.privilegePage.get('model'))) {
+                    self.privilegePage.get('model').clear();
+                    self.privilegePage.get('view').cleanup();
+                    self.privilegePage = undefined;
+                  }
+                }
+
+                if(!_.isUndefined(self.reviewSQLPage) &&
+                  !_.isNull(self.reviewSQLPage)) {
+                  if(!_.isUndefined(self.reviewSQLPage.get('model')) &&
+                    !_.isNull(self.reviewSQLPage.get('model'))) {
+                    self.reviewSQLPage.get('model').clear();
+                    self.reviewSQLPage = undefined;
+                  }
+                }
+
+                // clear privModel
+                if(!_.isNull(self.privModel) &&
+                  !_.isUndefined(self.privModel)) {
+                  self.privModel.clear();
+                }
+
+                // Remove collection containing db object data
+                if(!_.isNull(self.coll) &&
+                  !_.isUndefined(self.coll)) {
+                  self.coll.reset();
+                  self.coll = undefined;
+                }
+                // Delete Wizard
+                if(!_.isNull(self.wizard) &&
+                  !_.isUndefined(self.wizard)) {
+                  self.wizard.collection.reset();
+                  self.wizard.curr_page = undefined;
+                  self.wizard.el = undefined;
+                  self.wizard.remove();
+                  self.wizard = undefined;
+                }
+
+              },
+
+              /**
+                Every time a wizard is opened, this function
+                is called everytime. It has Wizard Pages which
+                are rendered by the Wizard Class:
+
+                @class {pgBrowser.WizardPage} dbObjectType1 - This page
+                @extends {Backbone.Model}
+                renders a grid of Database Object Types such as
+                  Schemas, Views and Sequences etc.
+
+                @class {pgBrowser.WizardPage} WizardPage2 - This page
+                @extends {Backbone.Model}
+                adds Privilege Control which provides grant privileges
+                such as "Create, Insert, Delete, Update" so on the
+                database objects selected on Wizard Pages.
+
+                @class {pgBrowser.WizardPage} WizardPage3 - This page
+                displays the generated GRANT SQL query for the Db
+                objects selected with the specific privileges added to it.
+                @extends {Backbone.Model}
+
+                @class {Backbone.Collection} WizardCollection - It is the
+                collection of wizard pages
+
+                @class {pgBrowser.Wizard} wizard - Its task is:
+                - Create a Wizard
+                - Add Buttons, Callbacks to it.
+                - Render WizardPages
+                @extends {Backbone.View}
+
+              */
+              build: function() {
+                this.elements.content.appendChild($container.get(0));
+              },
+
+              //Returns list of Acls defined for nodes
+              get_json_data: function() {
+                var url = "/grant_wizard/allowed_acl.json";
+                return $.ajax({
+                  async: false,
+                  dataType: 'jsonp',
+                  url: url
+                });
+
+              },
+              prepare:function() {
+
+                $container.empty().append("<div class='grant_wizard_container'></div>");
+
+                // Define el for wizard view
+                var el = $('.grant_wizard_container');
+
+                // Fetch privileges specific to nodes
+                var json_data = this.get_json_data(),
+                    privDict = JSON.parse(json_data.responseText);
+
+                // Extract the data from the selected tree node
+                var t = pgBrowser.tree,
+                    i = t.selected(),
+                    d = this.d = i && i.length == 1 ? t.itemData(i) : undefined,
+                    info = this.info = pgBrowser.Node.getTreeNodeHierarchy(i),
+                    icon = d.icon;
+
+                /**
+                  Generate a URL using:
+                  gid, did, sid(server id), node_id(node id),
+                  node_(node name), node_type(node type)
+                  and pass it to collection which will fetch Object Type properties.
+                */
+                var gid = info['server-group']._id,
+                    sid = info.server._id,
+                    did = info.database._id,
+                    node_id = d._id,
+                    nspname = info.schema.label,
+                    node_type = d._type.replace('coll-', '').replace('materialized_', ''),
+                    node_label = d.label;
+
+                    // Collection url to fetch database object types for objects field
+                    var baseUrl = "{{ url_for('grant_wizard.index') }}" + "properties/" +
+                        S('%s/%s/%s/%s/%s/%s/%s/').sprintf(
+                            encodeURI(gid), encodeURI(sid), encodeURI(did),
+                            encodeURI(node_id), encodeURI(node_label),
+                            encodeURI(node_type), encodeURI(nspname)).value();
+
+                    // Model's save url
+                    saveUrl = "{{ url_for('grant_wizard.index') }}" + "save/" +
+                        S('%s/%s/%s/%s/%s/').sprintf(
+                            encodeURI(gid), encodeURI(sid), encodeURI(did),
+                            encodeURI(node_type), encodeURI(nspname)).value(),
+
+                    // generate encoded url based on wizard type
+                    msql_url = this.msql_url = "/grant_wizard/msql/"+
+                      S('%s/%s/%s/%s/%s/').sprintf(
+                          encodeURI(gid), encodeURI(sid), encodeURI(did),
+                          encodeURI(node_type), encodeURI(nspname)).value(),
+
+                    Coll = Backbone.Collection.extend({
+                      model: DatabaseObjectModel,
+                      url: baseUrl
+                    }),
+
+                    // Create instances of collection and filter
+                    coll = this.coll = new Coll(),
+                    dbObjectFilter = this.dbObjectFilter = this.DbObjectFilter(coll);
+
+                /**
+                  privArray holds objects selected which further helps
+                  in creating privileges Model
+                */
+                var self = this;
+                self.privArray = [];
+
+                /**
+                  Override backgrid listener "backgrid:selected" to
+                  Add/Remove model to/from objects collection
+                */
+                coll.on('backgrid:selected', function(model, selected) {
+                  model.set('selected', selected);
+
+                  var object_type = model.get('object_type');
+                  switch (object_type)
+                  {
+                    case 'Function':
+                      object_type = 'function';
+                      break;
+                    case 'Trigger Function':
+                      object_type = 'function';
+                      break;
+                    case 'Table':
+                      object_type = 'table';
+                      break;
+                    case 'Sequence':
+                      object_type = 'sequence';
+                      break;
+                    case 'View':
+                      object_type = 'table';
+                      break;
+                  }
+
+                  /**
+                    if a row (checkbox) is checked, add that model
+                    into collection, when unchecked remove it from
+                    model.
+
+                    Also push/pop object type in/from privArray
+                  */
+                  if(selected) {
+                    if(_.indexOf(self.privArray, object_type) == -1)
+                      self.privArray.push(object_type);
+                    newModel.get('objects').add(model, { silent: true });
+                  }
+                  else {
+                    var idx = self.privArray.indexOf(object_type);
+                    if(idx !=-1)
+                      self.privArray.splice(idx, 1);
+                    newModel.get('objects').remove(model);
+                  }
+
+                  // validate model on checkbox check/uncheck
+                  var msg =  model.validate.call(newModel);
+
+                  /**
+                    If no object type is selected, set error msg
+                    and disable next button, else enable next button
+                  */
+                  if(msg)
+                    self.onSessionInvalid.call(self, msg);
+                  else
+                    self.onSessionValidated.call(self, true);
+                });
+
+                /**
+                  It is the main model with schema defined
+                  Every time a new wizard is opened,
+                  a new model should create.
+                */
+                var GrantWizardModel = pgNode.Model.extend({
+                  defaults: {
+                    objects: undefined,
+                    acl: undefined
+                  },
+                  schema: [
+                    {
+                      id: 'objects', label: '{{ _("Objects") }}', model: DatabaseObjectModel,
+                      type: 'collection', group: 'Objects'
+                    },
+                    {
+                      id: 'acl', label: '{{ _("Privileges") }}',
+                      model: pgAdmin.Browser.Node.PrivilegeRoleModel,
+                      type: 'collection', canAdd: true,
+                      canDelete: true, control: 'unique-col-collection'
+                    }
+                  ],
+                  urlRoot: saveUrl
+                });
+
+                /**
+                  Create instance of GrantWizard Model, provide urlRoot
+                  node_info object, Generate fields objects
+                */
+                var newModel = new GrantWizardModel({}, { node_info: info });
+
+                /**
+                  Fetch data from server and set into grid
+                  and show/hide progress bar
+                */
+                $('.wizard-progress-bar p').show();
+
+                coll.fetch({
+                  success: function(collection, data) {
+                    $('.wizard-progress-bar p').html('');
+                  },
+                  reset: true
+                }, this);
+
+                //////////////////////////////////////////////////////////////////////
+                //                                                                  //
+                //            Wizard Page for Db Object Type                        //
+                //                                                                  //
+                //////////////////////////////////////////////////////////////////////
+
+                /**
+                  Create wizard page. It renders a grid of
+                  Database Object Types such as
+                  Schemas, Views and Sequences etc.
+                  Set default values
+                */
+                var dbObjectTypePage = self.dbObjectTypePage = new pgBrowser.WizardPage({
+                  id: 1,
+                  page_title: _('Object Selection (step 1 of 3)'),
+                  disable_prev: true,
+                  disable_next: true,
+                  show_description: _('Please select objects from the below list.'),
+                  show_progress_bar: _('Please wait while fetching records...'),
+                  model: newModel,
+                  view: new (function() {
+
+                    // Set page Instance
+                    var pageView = this;
+
+                    _.extend(pageView, {
+
+                      // Remove grid if it is before render
+                      cleanup: function() {
+                        if (this.grid) {
+                          this.grid.remove();
+                          delete this.grid;
+                          this.grid = null;
+                        }
+
+                        // Remove grid element if exists
+                        if (this.el) {
+                          $(this.el).remove();
+                          delete this.el;
+                        }
+                      },
+
+                      // Delete grid before render
+                      grid: null,
+
+                      render: function() {
+
+                        // Create a grid container
+                        var gridBody =
+                              $('<div class="select_db_objects_container"></div>');
+
+                        // Remove grid if exits before render
+                        if (this.grid) {
+                          this.cleanup();
+                        }
+
+                        // Initialize a new Grid instance
+                        this.grid = new Backgrid.Grid({
+                          columns: _.clone(columns),
+                          collection: coll,
+                          className: "backgrid table-bordered object_type_table"
+                          });
+
+                        // Render selection Type grid and paginator
+                        gridBody.append( this.grid.render().$el);
+
+                        // Render Search Filter
+                        gridBody.prepend(
+                          self.clientSideFilter.render().el);
+
+                        // Assign gridBody content to page element
+                        this.el = gridBody;
+
+                        /**
+                          Fetch selected models from collection and
+                          make rows checked in grid
+                        */
+                        newModel.get('objects').each(function(m) {
+                          var model = coll.get(m.get('object_id'));
+                          if (model) {
+                            coll.trigger('backgrid:selected', model, true);
+                          }
+                        });
+
+                        // Refresh grid to re render rows.
+                        coll.trigger('backgrid:refresh');
+
+                        return this;
+                      }
+                    });
+                  }),
+
+                  beforeNext: function(obj){
+                    var self = this;
+                    obj.options.disable_next = true;
+
+                    /**
+                      Enable/Disable next button of privilegePage if objects
+                      are present in model
+                    */
+                    if(!_.isNull(newModel.get('acl')) &&
+                      !_.isUndefined(newModel.get('acl'))) {
+                      if(newModel.get('acl').length > 0)
+                        obj.collection.at(1).set('disable_next', false);
+                      }
+
+                    // Clean the view
+                    if (self.view) {
+                      self.view.cleanup();
+                      delete self.view;
+                      self.view = null;
+                    }
+                    return true;
+                  },
+
+                });
+
+                //////////////////////////////////////////////////////////////////////
+                //                                                                  //
+                //            Wizard Page for Privilege Control                     //
+                //                                                                  //
+                //////////////////////////////////////////////////////////////////////
+
+                // Wizard for Privelege control
+                var privilegePage = self.privilegePage = new pgBrowser.WizardPage({
+                  id: 2,
+                  page_title: _('Privileges Selection (step 2 of 3)'),
+                  show_description: _('Please select privileges for the selected objects.'),
+                  disable_next: true,
+                  model: newModel,
+
+                  // Create a view function object
+                  view: new (function() {
+                    var pageView = this;
+                      _.extend(pageView, {
+
+                        // Render Privelege control to generate its html markup
+                        render: function() {
+
+                          var obj_priv = [];
+                          self.privArray = _.uniq(self.privArray);
+                          _.each(self.privArray, function(priv){
+                            self.obj_priv = obj_priv = _.union(obj_priv , privDict[priv].acl);
+                          });
+
+                          /**
+                            Define PrivModel and its instance.
+                            Privileges array is generated based on
+                            the type of nodes selected.
+                           */
+                          var privModel = self.privModel;
+                          var PrivModel = pgNode.Model.extend({
+                            defaults: {
+                              acl: undefined
+                            },
+                            schema: [
+                              {
+                                id: 'acl', label: '{{ _("Privileges") }}',
+                                model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend({
+
+                                  // privileges are selected based on node clicked
+                                  privileges: obj_priv
+                                }), uniqueCol : ['grantee', 'grantor'], editable: true,
+                                type: 'collection', canAdd: true,
+                                canDelete: true, control: 'unique-col-collection'
+                              }
+                            ]
+                          });
+
+                          /**
+                            When privelege control is re-rendered, in order to
+                            render privileges based on object type selected,
+                            delete privileges from privModel which are now not
+                            present in object privileges array(object_priv)
+                           */
+                          var data = {};
+                          if (privModel) {
+                            data = privModel.toJSON();
+                            var rolePrivs = data['acl'];
+
+                            for (var idx in rolePrivs) {
+                              var rolePriv = (rolePrivs[idx])['privileges'],
+                                  removeIdx = [], j;
+
+                              for (j in rolePriv) {
+                                var p = rolePriv[j];
+                                if (_.indexOf(obj_priv, p['privilege_type']) == -1) {
+                                  removeIdx.push(j);
+                                }
+                              }
+
+                              for (j in removeIdx) {
+                                rolePriv.splice(j, 1);
+                              }
+                            }
+                          }
+
+                          // Instantiate privModel
+                          privModel = self.privModel = new PrivModel(data, { node_info: self.info });
+
+                          /*
+                          To track changes into model, start new session
+                          and Add event listener for privileges control
+                          */
+                          self.privModel.startNewSession();
+                          self.privModel.on('pgadmin-session:valid', self.onSessionValidated.bind(self));
+                          self.privModel.on('pgadmin-session:invalid', self.onSessionInvalid.bind(self));
+
+                        /**
+                          Create Field Object which has properties like
+                          node_data, node_info which is required for rendering
+                          Privilege control
+                          */
+                          var fields = Backform.generateViewSchema(
+                              self.info, self.privModel, 'create', self.d._type, self.d
+                              ),
+                              privilegesField = new Backform.Field(fields[0].fields[0]);
+
+                          this.privControl = new (privilegesField.get('control')) ({
+                            field: privilegesField,
+                            model: self.privModel
+                          });
+
+                          return {el: this.privControl.render().$el};
+                        },
+
+                        // Remove the privilege control
+                        cleanup: function() {
+                          if (this.privControl) {
+                            this.privControl.remove();
+                            delete this.privControl;
+                            this.privControl = null;
+                          }
+                        }
+                      });
+                  }),
+
+                  beforePrev: function(wizardObj) {
+
+                    // Remove the privilege control
+                    if (this.view) {
+                      this.view.cleanup();
+                      delete this.view;
+                      this.view = null;
+                    }
+
+                    /**
+                      Enable/Disable next button of DbObjectType page if objects
+                      are present in model
+                     */
+                    var objectsModel = newModel.get('objects');
+
+                    if(!_.isUndefined(objectsModel) && !_.isEmpty(objectsModel) &&
+                        objectsModel.length > 0) {
+                      wizardObj.collection.at(0).set('disable_next', false);
+
+                      // Don't show progress bar
+                      wizardObj.collection.at(0).set('show_progress_bar', '');
+                    }
+
+                    /**
+                      We're re-rendering the controls as they are deleted
+                      before heading to next page
+                      Refresh Backgrid to re-render the elements selected
+                      re-render Filter
+                    */
+                    newModel.trigger("backgrid:refresh", newModel, false);
+                    self.clientSideFilter.render();
+                    return true;
+                  },
+
+                  beforeNext: function() { return true; },
+
+                  onNext: function(obj){
+
+                    // Assign acls of privModel to main model newModel
+                    if (!_.isUndefined(self.privModel)) {
+                      newModel.set({'acl': self.privModel.get('acl')});
+                    }
+
+                    // Remove the privilege control
+                    if (this.view) {
+                      this.view.cleanup();
+                      delete this.view;
+                      this.view = null;
+                    }
+
+                    // Enable finish button
+                    self.wizard.options.disable_finish = false;
+
+                    /**
+                      triggers to get SQL queries data to render
+                      into the reviewSQLPage
+                    */
+                    newModel.trigger('pgadmin-wizard:nextpage:sql', {'node_type': node_type });
+                  }
+                });
+
+
+                //////////////////////////////////////////////////////////////////////
+                //                                                                  //
+                //            Review SQL Query Page                                 //
+                //                                                                  //
+                //////////////////////////////////////////////////////////////////////
+
+                //Create SqlField Object
+                var sqlField = new Backform.Field(
+                    {
+                      id: 'sqltab',
+                      label: _('Sql Tab'),
+
+                      /**
+                        Extend 'SqlTabControl' to define new
+                        function 'onWizardNextPageChange'
+                        which gets triggered on next button
+                        click to fetch generated SQL query
+                        for the selected db objects.
+                      */
+                      control: Backform.SqlTabControl.extend({
+                        initialize: function() {
+
+                          // Initialize parent class
+                          Backform.SqlTabControl.prototype.initialize.apply(this, arguments);
+
+                          this.msql_url = self.msql_url;
+
+                          // define trigger events for prev and next page
+                          this.model.on('pgadmin-wizard:nextpage:sql', this.onWizardNextPageChange, this);
+                          this.model.on('pgadmin-wizard:prevpage:sql', this.onWizardPrevPageChange, this);
+                        },
+
+                        // This method fetches the modified SQL for the wizard
+                        onWizardNextPageChange: function(){
+
+                          var self = this;
+
+                          // Fetches modified SQL
+                          $.ajax({
+                            url: this.msql_url,
+                            type: 'GET',
+                            cache: false,
+                            data: self.model.toJSON(true, 'GET'),
+                            dataType: "json",
+                            contentType: "application/json"
+                          }).done(function(res) {
+                            self.sqlTab.clearHistory();
+                            self.sqlTab.setValue(res.data);
+                          }).fail(function() {
+                            self.model.trigger('pgadmin-view:msql:error');
+                          }).always(function() {
+                            self.model.trigger('pgadmin-view:msql:fetched');
+                          });
+                        },
+
+                        remove: function() {
+
+                          // Stop listen to next/prev page events
+                          this.model.off('pgadmin-wizard:nextpage:sql', this.onWizardNextPageChange, this);
+                          this.model.off('pgadmin-wizard:prevpage:sql', this.onWizardPrevPageChange, this);
+                          Backform.SqlTabControl.__super__.remove.apply(this, arguments);
+                        }
+
+                      })
+                    }),
+
+                  /**
+                    Create sqlField view instance
+                    to render it into wizard page
+                  */
+                  sqlControl = new (sqlField.get('control'))({
+                    field: sqlField,
+                    model: newModel
+                  });
+
+                // Wizard for SQL tab control
+                var reviewSQLPage = self.reviewSQLPage = new pgBrowser.WizardPage({
+                  id: 3,
+                  page_title: _('Final (Review Selection) (step 3 of 3)'),
+                  show_description: _('Following query will be executed on the database'
+                                +' server for the selected objects, and privileges.'
+                                +' Please click on Finish to complete the process.'),
+                  model: newModel,
+                  view: new(function() {
+
+                    // Render SqlTab control to generate its html markup
+                    var sqlTabHtml = sqlControl.render().$el;
+                    this.render = function() {
+                        return { el: sqlTabHtml };
+                    };
+                  }),
+
+                  beforePrev: function(wizardObj) {
+
+                    /**
+                      Enable next button if privilege
+                      model is not empty else disable
+                      next button
+                     */
+                    var aclModel = newModel.get('acl');
+
+                    if(!_.isUndefined(wizardObj.collection) &&
+                      wizardObj.collection.models.length > 0) {
+                      if(!_.isUndefined(aclModel) && !_.isEmpty(aclModel) &&
+                          aclModel.length > 0) {
+                        wizardObj.collection.at(1).set('disable_next', false);
+                      }
+                      else {
+                        wizardObj.collection.at(1).set('disable_next', true);
+                      }
+
+                      return true;
+                    }
+                  },
+                });
+
+
+                // Create Wizard Collection of Wizard Pages
+                var WizardCollection = Backbone.Collection.extend({
+                  model: pgBrowser.WizardPage
+                });
+
+                // It holds all the wizard pages to be rendered
+                this.wizardCollection = new WizardCollection(
+                  [dbObjectTypePage, privilegePage, reviewSQLPage]
+                );
+
+                /**
+                  Create wizard which has following operations:
+                  - renders wizard pages
+                  - defines the first page to render in wizard
+                  - Save the model on finishbutton
+                  - Remove wizard on cancel button
+                  */
+                var wizard = self.wizard = new (pgBrowser.Wizard.extend({
+                  options: {
+                    title: _('Grant Wizard'), /* Main Wizard Title */
+                    width: '',
+                    height: '',
+                    curr_page: 0,
+                    show_left_panel: false,
+                    disable_finish: true
+                  },
+
+                  // Callback for finish button
+                  onFinish: function() {
+                    var m = newModel,
+                        d = m.toJSON('GET');
+
+                    // Save model
+                    if (d && !_.isEmpty(d) && !_.isUndefined(d.objects)) {
+                      m.save({}, {
+                        attrs: d,
+                        validate: false,
+                        cache: false,
+                        success: function(res) {
+
+                          // Release wizard objects
+                          self.releaseObjects();
+                          self.close();
+                        },
+                        error: function(m, jqxhr) {
+                          alertify.pgNotifier(
+                            "error", jqxhr,
+                            S(
+                              "{{ _('Error during saving properties - %%s!') }}"
+                              ).sprintf(jqxhr.statusText).value()
+                            );
+                        }
+                      });
+                    }
+                  },
+
+                  // Callback for cancel button
+                  onCancel: function() {
+
+                    // Release wizard objects
+                    self.releaseObjects();
+                    self.close();
+                  }
+                })) ({
+                  collection: this.wizardCollection,
+                  el: el,
+                  model: newModel
+                });
+
+                // Render wizard
+                wizard.render();
+              }
+            };
+          });
+        }
+
+        // Call Grant Wizard Dialog
+        alertify.wizardDialog(true);
+      }
+    };
+
+    return pgBrowser.GrantWizard;
+  });
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/allowed_acl.json b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/allowed_acl.json
new file mode 100644
index 0000000..2d21f14
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/allowed_acl.json
@@ -0,0 +1,30 @@
+{# List of allowed privileges of Schema, table sequence, function and view #}
+{#
+    Format for allowed privileges are:
+    "node_name": {
+        "type": "name",
+        "acl": [...]
+    }
+#}
+{
+    "schema": {
+        "type": "SCHEMA",
+        "acl": ["a", "r", "w", "d", "D", "x", "t", "U", "X"]
+    },
+    "table": {
+        "type": "TABLE",
+        "acl": ["a", "w", "d", "D", "x", "t"]
+    },
+    "view": {
+        "type": "VIEW",
+        "acl": ["a", "w", "d", "D", "x", "t"]
+    },
+    "sequence": {
+        "type": "SEQUENCE",
+        "acl": ["w", "U"]
+    },
+    "function": {
+        "type": "FUNCTION",
+        "acl": ["X"]
+    }
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/grant.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/grant.sql
new file mode 100644
index 0000000..ae59b53
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/grant.sql
@@ -0,0 +1,19 @@
+{#=====Grant Permissions on Database Objects Selected====#}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% import 'macros/functions/privilege.macros' as PRIVILEGE_FUNCTION %}
+{% if data.acl -%}
+{% for obj in data.objects -%}
+{% for priv in data.acl -%}
+{%- set obj_type = obj.object_type|upper -%}
+{# ===== if object_type is Function then apply function marcros ===== #}
+{% if (obj_type == 'FUNCTION' or obj_type == 'TRIGGER FUNCTION') and (priv.with_grant['function']|length > 0 or priv.without_grant['function']|length > 0) -%}
+{{ PRIVILEGE_FUNCTION.SET(conn, 'FUNCTION', priv.grantee, obj.name, priv.without_grant['function'], priv.with_grant['function'], nspname, obj.proargs)}}
+{% elif (obj_type == 'SEQUENCE') and (priv.with_grant['table']|length > 0 or priv.without_grant['table']|length > 0) -%}
+{{ PRIVILEGE.SET(conn, obj_type.upper(), priv.grantee, obj.name, priv.without_grant['sequence'], priv.with_grant['sequence'], nspname ) }}
+{% elif (obj_type == 'VIEW' or obj_type == 'TABLE') and (priv.with_grant['table']|length > 0 or priv.without_grant['table']|length > 0) -%} {# ===== Views are also table ===== #}
+{%- set obj_type = 'TABLE' -%}
+{{ PRIVILEGE.SET(conn, obj_type.upper(), priv.grantee, obj.name, priv.without_grant['table'], priv.with_grant['table'], nspname ) }}
+{% endif -%}
+{% endfor -%}
+{% endfor -%}
+{% endif -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/properties.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/properties.sql
new file mode 100644
index 0000000..e92888c
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/properties.sql
@@ -0,0 +1,66 @@
+{#===================Fetch list of Database object types(Functions, Sequence, Tables or View)===================#}
+{% if type == 'schema' or type == 'trigger_function' and node_id and node_name %}
+{% set func_type = 'Trigger Function' if type == 'trigger_function' else 'Function' %}
+SELECT
+    pg_get_function_identity_arguments(pr.oid) AS proargs,
+    {# pr.proname || '(' || pg_get_function_identity_arguments(pr.oid) || ')' AS name,#}
+    pr.proname AS name,
+    '{{ func_type }}' AS object_type,
+    '{{ nspname }}' AS nspname,
+    '{{ "icon-function" if type != "trigger_function" else "icon-trigger_function" }}' AS icon
+FROM pg_proc pr
+    JOIN pg_type typ ON typ.oid=prorettype
+    JOIN pg_namespace typns ON typns.oid=typ.typnamespace
+    JOIN pg_language lng ON lng.oid=prolang
+    LEFT OUTER JOIN pg_description des ON (des.objoid=pr.oid AND des.classoid='pg_proc'::regclass)
+WHERE proisagg = FALSE AND pronamespace = {{ node_id }}::oid
+    AND typname {{ 'NOT' if type != 'trigger_function' else '' }} IN ('trigger', 'event_trigger')
+ORDER BY proname
+{% endif %}
+
+{% if type == 'sequence' and node_id and node_name %}
+SELECT
+    cl.relname AS name,
+    'Sequence' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-sequence' AS icon
+FROM pg_class cl
+    LEFT OUTER JOIN pg_description des ON (des.objoid=cl.oid AND des.classoid='pg_class'::regclass)
+WHERE relkind = 'S' AND relnamespace  = {{ node_id }}::oid
+ORDER BY cl.relname
+{% endif %}
+
+{% if type == 'table' and node_id and node_name %}
+SELECT rel.relname AS name,
+    'Table' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-table' AS icon
+FROM pg_class rel
+    LEFT OUTER JOIN pg_tablespace spc ON spc.oid=rel.reltablespace
+    LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass)
+    LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'
+    LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid
+    LEFT JOIN pg_type typ ON rel.reloftype=typ.oid
+WHERE rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ node_id }}::oid
+ORDER BY rel.relname
+{% endif %}
+
+{% if type == 'view' and node_id and node_name %}
+SELECT
+    c.relname AS name,
+    'View' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-view' AS icon
+FROM pg_class c
+    LEFT OUTER JOIN pg_tablespace spc ON spc.oid=c.reltablespace
+    LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass)
+    LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid
+WHERE ((c.relhasrules AND (EXISTS (
+    SELECT r.rulename
+    FROM pg_rewrite r
+    WHERE ((r.ev_class = c.oid)
+        AND (bpchar(r.ev_type) = '1'::bpchar)) ))
+       ) OR (c.relkind = 'v'::char))
+AND c.relnamespace = {{ node_id }}::oid
+ORDER BY c.relname
+{% endif %}


^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-03-30 11:44  Surinder Kumar <[email protected]>
  parent: Surinder Kumar <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Surinder Kumar @ 2016-03-30 11:44 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: pgadmin-hackers

Hi,

Please find updated patch.

This patch has following changes:
1. Improved code commenting.
2. Properly handling memory leak issues in js code.


On Mon, Mar 28, 2016 at 2:09 PM, Surinder Kumar <
[email protected]> wrote:

> Hi Dave,
>
> Please find updated patch with suggested changes.
>
> On Thu, Mar 10, 2016 at 5:26 PM, Dave Page <[email protected]> wrote:
>
>> On Thu, Mar 10, 2016 at 9:50 AM, Surinder Kumar
>> <[email protected]> wrote:
>> > Please apply Khusboo's patch for "Privileges macros under Schema" before
>> > using grant wizard patch.
>>
>> Thanks, that works. Some (hopefully final) feedback:
>>
>> - Can we add a side-image? Not sure what yet - just a placeholder for
>> now until I come up with something.
>>
> Already a placeholder for left side image.
>
>>
>> - Why is the closed button in an odd position (see File -> Test Alert
>> for comparison)
>>
> Fixed
>
>>
>> - Why are we using a scrolling list AND pagination? I think a
>> scrolling list alone should be fine.
>>
> Pagination is removed.
>
>>
>> - The grid sizing is wrong. See how the scrollbar on the right in the
>> screenshot is off the edge of the dialogue, and there's a horizontal
>> scrollbar?
>>
> Fixed.
>
>>
>> - We shouldn't truncate object names as that can be ambiguous. The
>> column should extend as necessary, and there should be a horizontal
>> scrollbar on the grid itself (not at the bottom of the dialogue
>> content).
>>
> I have fixed it and scrollbar is now on the grid itself.
>
>>
>> - Function names should include the parameters, as they are part of
>> the identifier. Without, it can be ambigous - e.g. do_stuff(int) vs.
>> do_stuff(text).
>>
> Fixed.
>
>>
>> - If I select only functions (for example), the Privileges panel
>> should only list privileges available for functions. If I select
>> multiple object types, it should show the available options for only
>> those object types.
>>
> Implemented this feature.
>
>>
>> - If I select functions and tables, and then choose (for example),
>> usage and truncate, it will attempt to set usage on tables and
>> truncate on functions. It should only attempt to set privileges on the
>> objects for which they are appropriate.
>>
> Done
>
>>
>> - On the last page, the Next button is disabled. It is turned a
>> marginally darker blue, but also highlights on mouse-over. The change
>> in shade is so subtle it's hard to see, and the highlight implies the
>> button is active when it isn't.
>>
> Fixed.
>
>>
>> - The buttons appear to have a smaller corner radius than those on the
>> main browser, or in other dialogues.
>>
> Fixed.
>
>>
>> - Button labels should have an &nbsp; before them to properly space
>> the label from the icon (or better yet, this should be done in CSS,
>> though that would also need to be done elsewhere).
>>
> done with css.
>
>>
>> - Why do the URLs have a /wizard prefix? I think that should be removed.
>>
> Removed prefix.
>
>>
>> - The available privileges for each object type seem to be defined in
>> both grant_wizard.js and allowed_acl.json. Can we just use
>> allowed_acl.json?
>>
> Yes, allowed_acl.json is now used in grant_wizard.js.
>
>>
>> Thanks.
>>
>> --
>> Dave Page
>> Blog: http://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EnterpriseDB UK: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>>
>
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [application/octet-stream] grant_wizard_v6.patch (74.4K, 3-grant_wizard_v6.patch)
  download | inline diff:
diff --git a/web/pgadmin/browser/static/css/wizard.css b/web/pgadmin/browser/static/css/wizard.css
new file mode 100644
index 0000000..ca617b6
--- /dev/null
+++ b/web/pgadmin/browser/static/css/wizard.css
@@ -0,0 +1,148 @@
+
+/** CSS for Wizard **/
+.ajs-content {
+  padding-top: 0px !important;
+}
+
+/* Wizard Header CSS */
+.alertify .ajs-dialog .ajs-close {
+  margin-top: 15px;
+}
+
+.wizard-header {
+  background: #428bca;
+  padding-left: 15px;
+  padding-bottom: 7px;
+  color: #fff;
+  font-size: 18px;
+  height: 76px;
+  line-height: 76px;
+  border-radius: 6px 6px 0 0;
+}
+
+.wizard-header h3 {
+  font-size: 18px;
+  display: inline-block;
+}
+
+.wizard_dlg {
+  float: left;
+  height: 100%;
+  width: 100%;
+}
+
+.grant_wizard_container {
+	position: relative;
+  width: 100%;
+  height: 100%;
+}
+
+.pgadmin-wizard {
+  width: 100%;
+  height: 100%;
+}
+
+.wizard-content {
+  position: relative;
+  padding: 0;
+  height: 64%;
+}
+
+.wizard-right-panel {
+  overflow-y: auto;
+  height: 100%;
+  top: 0;
+  right: 0;
+  position: absolute;
+  bottom: 0;
+}
+
+.wizard-left-panel {
+  position: absolute;
+  top: 0;
+  display: flex;
+  bottom: 0;
+  left: 0;
+  align-items: center;
+  justify-content: center;
+  right: 0;
+}
+
+.wizard-left-panel img {
+  width: 140px;
+}
+
+.wizard-right-panel_content {
+	height: 60%;
+}
+
+.select_db_objects_container {
+  height: 100%;
+}
+
+/* Wizard Footer CSS */
+.footer {
+  position: absolute;
+  background: #fff;
+  border-top: 1px solid #ccc;
+  bottom: 0px;
+  height: 62px;
+  right: 0px;
+  padding-top: 22px;
+  border-radius: 0 0 6px 6px;
+  z-index: 10;
+}
+
+/* Wizard Button CSS */
+.wizard-buttons {
+  float: right;
+}
+
+.wizard-buttons button {
+  float: left;
+  padding: 7px 15.2px;
+  font-size: 14px;
+  margin: 0px 5px 0 0 !important;
+}
+
+.wizard-buttons button.wizard-next i.fa {
+	padding-left: 5px;
+}
+
+.wizard-buttons button.wizard-back i.fa,
+.wizard-buttons button.wizard-cancel i.fa {
+	padding-right: 5px;
+}
+
+.wizard-buttons .wizard-finish {
+  margin-right: 0 !important;
+}
+
+/* Wizard Status bar CSS */
+.wizard-description {
+  padding: 1.7em 0.1em;
+}
+
+/* Error message css */
+.error_msg_div {
+  display: block;
+  position: absolute;
+  bottom: 55px;
+  background: #fff;
+}
+
+.error_msg_div p {
+  background: #fff;
+  color: #b92c28;
+}
+
+/* In wizard select2 dropdown doesn't
+ * popup because z-index of alertify
+ * wizard is greater than the z-index
+ * of select2 dropdown. To make select2
+ * visible, set z-index of select2
+ * higher value than wizard's
+ */
+.select2-container--open {
+  z-index: 10000;
+}
diff --git a/web/pgadmin/browser/static/js/node.ui.js b/web/pgadmin/browser/static/js/node.ui.js
index f127d52..a800593 100644
--- a/web/pgadmin/browser/static/js/node.ui.js
+++ b/web/pgadmin/browser/static/js/node.ui.js
@@ -279,7 +279,7 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
         return false;
       };
 
-      while(p) {
+      while(p && p.length > 0) {
         top = p.get(0).offsetTop + p.height();
         p = p.parent();
         if (hasScrollbar(p)) {
diff --git a/web/pgadmin/browser/static/js/wizard.js b/web/pgadmin/browser/static/js/wizard.js
new file mode 100644
index 0000000..f42eab7
--- /dev/null
+++ b/web/pgadmin/browser/static/js/wizard.js
@@ -0,0 +1,226 @@
+define(
+    ['underscore', 'backbone', 'pgadmin', 'pgadmin.browser'],
+function(_, Backbone, pgAdmin, pgBrowser) {
+
+  pgBrowser = pgBrowser || pgAdmin.Browser || {};
+
+  /* Wizard individual Page Model */
+  var WizardPage = pgBrowser.WizardPage = Backbone.Model.extend({
+    defaults: {
+      id: undefined, /* Id */
+      page_title: undefined, /* Page Title */
+      view: undefined, /* A Backbone View */
+      html: undefined, /* HTML tags to be rendered */
+      image: undefined, /* Left hand side image */
+      disable_prev: false, /*  Previous Button Flag */
+      disable_next: false, /*  Next Button Flag */
+      disable_cancel: false, /* Cancel Button Flag */
+      show_progress_bar: '',
+      /* Callback for OnLoad */
+      onLoad: function() {
+        return true;
+      },
+      /* Callback for before Next */
+      beforeNext: function() {
+        return true;
+      },
+      onNext: function(){},
+      onBefore: function() {},
+      /* Callback for before Previous */
+      beforePrev: function() {
+        return true;
+      }
+    }
+  });
+
+  var Wizard = pgBrowser.Wizard = Backbone.View.extend({
+    options: {
+      title: 'Wizard', /* Main Wizard Title */
+      image: 'left_panel.png', /* TODO:: We can use default image here */
+      curr_page: 0, /* Current Page to Load */
+      disable_next: false,
+      disable_prev: false,
+      disable_finish: false,
+      disable_cancel: false,
+      height: 400,
+      width: 650,
+      show_left_panel: true
+    },
+    tmpl: _.template(
+       "    <div class='pgadmin-wizard' style='height: <%= this.options.height %>px; width: <%= this.options.width %>px'>"
+       + "      <div class='wizard-header wizard-badge'>"
+       + "        <div class='row'>"
+       + "          <div class='col-sm-9'>"
+       + "              <h3><span id='main-title'><%= this.options.title %></span> - <span id='step-title'><%= page_title %></span></h3>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "      <div class='wizard-content col-sm-12'>"
+       + "        <% if(this.options.show_left_panel) { %>"
+       + "          <div class='col-sm-3 wizard-left-panel'>"
+       + "              <img src='<%= this.options.image %>'></div>"
+       + "        <% } %>"
+       + "        <div class='col-sm-<% if(this.options.show_left_panel){ %>9<% } else { %>12<% } %> wizard-right-panel'>"
+       + "          <% if( typeof show_description !=  'undefined'){ %>"
+       + "            <div class='wizard-description'>"
+       + "              <%= show_description %>"
+       + "            </div>"
+       + "          <% } %>"
+       + "          <div class='wizard-right-panel_content'>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "      <div class='col-sm-12 error_msg_div'>"
+       + "       <p></p>"
+       + "      </div>"
+       + "      <div class='footer col-sm-12'>"
+       + "        <div class='row'>"
+       + "          <div class='col-sm-4 wizard-progress-bar'>"
+       + "             <p><% if(show_progress_bar){ %><%= show_progress_bar %><% } %></p>"
+       + "          </div>"
+       + "          <div class='col-sm-8'>"
+       + "            <div class='wizard-buttons'>"
+       + "              <button class='btn btn-primary wizard-back' <%=this.options.disable_prev ? 'disabled' : ''%>><i class='fa fa-backward'></i>Back</button>"
+       + "              <button class='btn btn-primary wizard-next' <%=this.options.disable_next ? 'disabled' : ''%>>Next<i class='fa fa-forward'></i></button>"
+       + "              <button class='btn btn-danger wizard-cancel' <%=this.options.disable_cancel ? 'disabled' : ''%>><i class='fa fa-lg fa-close'></i>Cancel</button>"
+       + "              <button class='btn btn-primary wizard-finish' <%=this.options.disable_finish ? 'disabled' : ''%>>Finish</button>"
+       + "            </div>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "    </div>"),
+    events: {
+      "click button.wizard-next" : "nextPage",
+      "click button.wizard-back" : "prevPage",
+      "click button.wizard-cancel" : "onCancel",
+      "click button.wizard-finish" : "finishWizard",
+    },
+    initialize: function(options) {
+      this.options = _.extend({}, this.options, options.options);
+      this.currPage = this.collection.at(this.options.curr_page).toJSON();
+    },
+    render: function() {
+      var data = this.currPage;
+
+      /* Check Status of the buttons */
+      this.options.disable_next = (this.options.disable_next ? true : this.evalASFunc(this.currPage.disable_next));
+      this.options.disable_prev = (this.options.disable_prev ? true : this.evalASFunc(this.currPage.disable_prev));
+      this.options.disable_cancel = (this.currPage.canCancel ? true : this.evalASFunc(this.currPage.disable_cancel));
+
+      that = this;
+
+      /* HTML Content */
+      if (data.html) { data.content = data.html; }
+      /* Backbone View */
+      else if (data.view) { data.content = data.view.render().el;}
+
+      $(this.el).html(this.tmpl(data));
+      $(this.el).find(".wizard-right-panel_content").html(data.content);
+
+      /* OnLoad Callback */
+      this.onLoad();
+
+      return this;
+    },
+    nextPage: function() {
+      this.options.curr_page.el = this.$el;
+      if (!this.beforeNext()) { return false; }
+
+      page_id = this.onNext();
+
+      if (page_id ) {
+        this.currPage = this.collection.get(page_id).toJSON();
+        this.options.curr_page = this.collection.indexOf(this.collection.get(page_id));
+      }
+      else if (this.options.curr_page < (this.collection.length-1)) {
+        this.options.curr_page = this.options.curr_page + 1;
+        this.currPage = this.collection.at(this.options.curr_page).toJSON();
+      }
+
+      this.enableDisableNext();
+      this.enableDisablePrev();
+
+      return this.render();
+    },
+    prevPage: function() {
+      if (!this.beforePrev()) { return false; }
+
+      page_id = this.onPrev();
+
+      if (page_id){
+        this.currPage = this.collection.get(page_id).toJSON();
+        this.options.curr_page = this.collection.indexOf(this.collection.get(page_id));
+      }
+      else if (this.options.curr_page > 0) {
+        this.options.curr_page = this.options.curr_page - 1;
+        this.currPage = this.collection.at(this.options.curr_page).toJSON();
+      }
+
+      this.enableDisableNext();
+      this.enableDisablePrev();
+
+      return this.render();
+    },
+    finishWizard: function() {
+      this.onFinish();
+      this.remove(); // Remove view from DOM
+      this.unbind(); // Unbind all local event bindings
+      delete this.$el; // Delete the jQuery wrapped object variable
+      delete this.el; // Delete the variable reference to this node
+      return true;
+    },
+    enableDisableNext: function(disable) {
+        if (typeof(disable) != 'undefined') {
+          this.options.disable_next = disable;
+        }
+        else if (this.options.curr_page >= (this.collection.length-1)) {
+          this.options.disable_next = true;
+        }
+        else {
+          this.options.disable_next = false;
+        }
+    },
+    enableDisablePrev: function(disable) {
+        if (typeof(disable) != 'undefined') {
+          this.options.disable_prev = disable;
+        }
+        else if (this.options.curr_page <= 0) {
+          this.options.disable_prev = true;
+        }
+        else {
+          this.options.disable_prev = false;
+        }
+    },
+    beforeNext: function(){
+      return this.evalASFunc(this.currPage.beforeNext);
+    },
+    beforePrev: function(){
+      return this.evalASFunc(this.currPage.beforePrev);
+    },
+    onPrev: function(){
+      return this.evalASFunc(this.currPage.onPrev);
+    },
+    onNext: function(){
+      return this.evalASFunc(this.currPage.onNext);
+    },
+    onLoad: function() {
+      return this.evalASFunc(this.currPage.onLoad);
+    },
+    onFinish: function() {
+      return true;
+    },
+    onCancel: function() {
+      this.$el.remove();
+      return true;
+    },
+    evalASFunc: function(func, ctx) {
+      var self = this;
+      ctx = ctx || self.currPage;
+
+      return (_.isFunction(func) ? func.apply(ctx, [self]) : func);
+    }
+  });
+
+  return pgBrowser;
+
+});
diff --git a/web/pgadmin/tools/grant_wizard/__init__.py b/web/pgadmin/tools/grant_wizard/__init__.py
new file mode 100644
index 0000000..c71818e
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/__init__.py
@@ -0,0 +1,386 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Implements Grant Wizard"""
+
+import json
+from flask import render_template, request, current_app
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_response as ajax_response, \
+    make_json_response, internal_server_error
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+# from pgadmin.browser.server_groups.servers.utils import parse_priv_to_db
+from pgadmin.utils import PgAdminModule
+from flask import Response, url_for
+from flask.ext.security import login_required
+from urllib import unquote
+
+# set template path for sql scripts
+template_path = 'grant_wizard/sql'
+MODULE_NAME = 'grant_wizard'
+
+
+class GrantWizardModule(PgAdminModule):
+    """
+    class GrantWizardModule(Object):
+
+        It is a wizard which inherits PgAdminModule
+        class and define methods to load its own
+        javascript file.
+    """
+    def get_own_stylesheets(self):
+        """
+        Returns:
+            list: the stylesheets used by this module.
+        """
+        stylesheets = [
+            url_for('browser.static', filename='css/wizard.css'),
+            url_for('grant_wizard.static', filename='css/grant_wizard.css')
+            ]
+        return stylesheets
+
+    def get_own_javascripts(self):
+        """"
+        Returns:
+            list: js files used by this module
+        """
+        scripts = []
+        scripts.append({
+            'name': 'pgadmin.tools.grant_wizard',
+            'path': url_for('grant_wizard.index') + 'grant_wizard',
+            'when': None
+        })
+        scripts.append({
+            'name': 'pgadmin.browser.wizard',
+            'path': url_for('browser.static', filename='js/wizard'),
+            'when': None
+        })
+        return scripts
+
+# Create blueprint for GrantWizardModule class
+blueprint = GrantWizardModule(
+    MODULE_NAME, __name__, static_url_path='')
+
+
[email protected]("/")
+@login_required
+def index():
+    pass
+
+
[email protected]("/grant_wizard.js")
+@login_required
+def script():
+    """render own javascript"""
+    return Response(response=render_template(
+                "grant_wizard/js/grant_wizard.js", _=gettext),
+                status=200,
+                mimetype="application/javascript")
+
+
[email protected]("/allowed_acl.json")
+@login_required
+def acl_list():
+    """render list of acls"""
+    return Response(response=render_template(
+                "grant_wizard/sql/allowed_acl.json", _=gettext),
+                status=200,
+                mimetype="application/javascript")
+
+
[email protected](
+    '/properties/<int:gid>/<int:sid>/<int:did>'
+    '/<int:node_id>/<node_name>/<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+@login_required
+def properties(gid, sid, did, node_id, node_name, node_type, nspname):
+    """It fetches the properties of object types
+       and render into selection page of wizard
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+    nspname = unquote(nspname)
+
+    res_data = []
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    # Fetch functions against schema
+    if node_type in ['schema']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type=node_type, nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch trigger functions
+    if node_type in ['schema', 'trigger_function']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='trigger_function', nspname=nspname)
+        status, res = conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch Sequences against schema
+    if node_type in ['schema', 'sequence']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='sequence', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+        res_data.extend(res['rows'])
+
+    # Fetch Tables against schema
+    if node_type in ['schema', 'table']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='table', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch Views against schema
+    if node_type in ['schema', 'view']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='view', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    return ajax_response(
+            response=res_data,
+            status=200
+            )
+
+
+def parse_priv_to_db(str_privileges, acls):
+    """
+    Common utility function to parse privileges before sending to database.
+    """
+    db_privileges = {
+        'c': 'CONNECT',
+        'C': 'CREATE',
+        'T': 'TEMPORARY',
+        'a': 'INSERT',
+        'r': 'SELECT',
+        'w': 'UPDATE',
+        'd': 'DELETE',
+        'D': 'TRUNCATE',
+        'x': 'REFERENCES',
+        't': 'TRIGGER',
+        'U': 'USAGE',
+        'X': 'EXECUTE'
+        }
+
+    privileges = []
+
+    for priv in str_privileges:
+        priv_with_grant = {}
+        priv_without_grant = {}
+        priv_function_with_grant = []
+        priv_function_without_grant = []
+        priv_sequence_with_grant = []
+        priv_sequence_without_grant = []
+        priv_table_with_grant = []
+        priv_table_without_grant = []
+
+        # import pdb
+        # pdb.set_trace()
+        for privilege in priv['privileges']:
+
+            if privilege['privilege_type'] not in db_privileges:
+                continue
+
+            # Extract privileges applicable to function
+            if privilege['privilege_type'] in acls['function']['acl']:
+                if privilege['with_grant']:
+                    priv_function_with_grant.append(
+                        db_privileges[privilege['privilege_type']])
+                elif privilege['privilege']:
+                    priv_function_without_grant.append(
+                        db_privileges[privilege['privilege_type']])
+
+            # Extract privileges applicable to sequence
+            if privilege['privilege_type'] in acls['sequence']['acl']:
+                if privilege['with_grant']:
+                    priv_sequence_with_grant.append(
+                        db_privileges[privilege['privilege_type']])
+                elif privilege['privilege']:
+                    priv_sequence_without_grant.append(
+                        db_privileges[privilege['privilege_type']])
+
+            # Extract privileges applicable to view/table
+            if (privilege['privilege_type'] in acls['table']['acl']):
+                if privilege['with_grant']:
+                    priv_table_with_grant.append(
+                        db_privileges[privilege['privilege_type']])
+                elif privilege['privilege']:
+                    priv_table_without_grant.append(
+                        db_privileges[privilege['privilege_type']])
+
+        # If we have all acl then just return all
+        if len(priv_table_with_grant) == len(acls['table']['acl']):
+            priv_table_with_grant = ['ALL']
+
+        if len(priv_table_without_grant) == len(acls['table']['acl']):
+            priv_table_without_grant = ['ALL']
+
+        # Appending and returning all ACL
+        priv_with_grant['function'] = priv_function_with_grant
+        priv_without_grant['function'] = priv_function_without_grant
+
+        priv_with_grant['sequence'] = priv_sequence_with_grant
+        priv_without_grant['sequence'] = priv_sequence_without_grant
+
+        priv_with_grant['table'] = priv_table_with_grant
+        priv_without_grant['table'] = priv_table_without_grant
+
+        privileges.append({
+            'grantee': priv['grantee'],
+            'with_grant': priv_with_grant,
+            'without_grant': priv_without_grant
+            })
+
+    return privileges
+
+
[email protected](
+    '/msql/<int:gid>/<int:sid>/<int:did>/<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+def msql(gid, sid, did, node_type, nspname):
+    """
+    This function will return modified SQL
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+    nspname = unquote(nspname)
+
+    data = {}
+    for k, v in request.args.items():
+        try:
+            data[k] = json.loads(v)
+        except ValueError:
+            data[k] = v
+
+    # Form db connection
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    acls = []
+    try:
+        acls = render_template(
+            "/".join([template_path, 'allowed_acl.json'])
+            )
+        acls = json.loads(acls)
+    except Exception as e:
+        current_app.logger.exception(e)
+
+    try:
+
+        # Parse privileges
+        if 'acl' in data:
+            data['acl'] = parse_priv_to_db(data['acl'], acls)
+
+        # Pass database objects and get SQL for privileges
+        SQL = render_template("/".join(
+            [template_path, 'grant.sql']),
+            data=data, nspname=nspname, conn=conn)
+
+        res = {'data': SQL}
+
+        return ajax_response(
+            response=res,
+            status=200
+            )
+
+    except Exception as e:
+        return make_json_response(
+            status=410,
+            success=0,
+            errormsg=e.message
+        )
+
+
[email protected](
+    '/save/<int:gid>/<int:sid>/<int:did>/'
+    '<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+def save(gid, sid, did, node_type, nspname):
+    """
+    This function will apply the privileges to the selected
+    Database Objects
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+    nspname = unquote(nspname)
+
+    data = request.form if request.form else json.loads(request.data.decode())
+
+    # Form db connection and we use conn to execute sql
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    acls = []
+    try:
+        acls = render_template(
+            "/".join([template_path, 'allowed_acl.json'])
+            )
+        acls = json.loads(acls)
+    except Exception as e:
+        current_app.logger.exception(e)
+
+    try:
+
+        # Parse privileges
+        if 'acl' in data:
+            data['acl'] = parse_priv_to_db(data['acl'], acls)
+
+        # Pass database objects and get SQL for privileges
+        SQL = render_template("/".join(
+            [template_path, 'grant.sql']),
+            data=data, nspname=nspname, conn=conn)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        return make_json_response(
+            success=1,
+            info="Privileges Applied"
+        )
+
+    except Exception as e:
+        return internal_server_error(errormsg=e.message)
diff --git a/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
new file mode 100644
index 0000000..46c9203
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
@@ -0,0 +1,86 @@
+/** Grant Wizard CSS **/
+
+/**
+  CSS to make db object type table
+  fixed so that tbody content may
+  scroll
+ */
+.object_type_table thead tr {
+  position: relative;
+  display: block;
+}
+
+.object_type_table {
+	display: inline-block;
+  height: 100%;
+  border: 0 !important;
+}
+
+.object_type_table tbody {
+  display: block;
+  overflow: scroll;
+  border: 1px solid #ddd;
+  width: 100%;
+  min-height: 100%;
+  max-height: 66%;
+  height: 66%;
+}
+
+.object_type_table tbody tr td {
+	background-position: 4px 4px;
+	border-radius: 0;
+}
+
+.object_type_table tbody tr td:nth-child(1),
+.object_type_table thead tr th:nth-child(1) {
+  width: 28px;
+  min-width: 28px;
+}
+
+.object_type_table tbody tr td:nth-child(2),
+.object_type_table thead tr th:nth-child(2) {
+  width: 185px;
+  min-width: 185px;
+}
+
+.object_type_table tbody tr td:nth-child(3),
+.object_type_table thead tr th:nth-child(3) {
+  width: 110px;
+  min-width: 110px;
+}
+
+.object_type_table thead tr th:nth-child(4) {
+	width: 244px;
+}
+
+.object_type_table tbody tr td:nth-child(4) {
+  width: 244px;
+  min-width: 228px;
+  max-width: 100%;
+}
+
+/** Override Backgrid filter CSS **/
+.backgrid-filter.form-search {
+  float: left;
+  margin: 0px 5px 5px 0;
+}
+
+/** Custom styling for Codemirror field **/
+.wizard-right-panel_content {
+  border: 1px solide #ccc;
+}
+
+.wizard-right-panel_content .CodeMirror {
+  border: 1px solid #ccc;
+  height: 285px !important;
+  min-height: 285px !important;
+}
+
+.wizard-right-panel_content .CodeMirror-linenumber {
+  background: #F7F7F7;
+  border-right: 1px solid #DDDDDD;
+}
+
+.wizard-right-panel_content .CodeMirror-gutters {
+  min-height: 285px !important;
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
new file mode 100644
index 0000000..54805b1
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
@@ -0,0 +1,1091 @@
+define([
+      'jquery', 'underscore', 'underscore.string', 'alertify',
+      'pgadmin.browser', 'backbone', 'backgrid', 'pgadmin.browser.node',
+      'backgrid.select.all', 'backgrid.filter', 'pgadmin.browser.server.privilege',
+      'pgadmin.browser.wizard',
+      ],
+
+  // This defines Grant Wizard dialog
+  function($, _, S, alertify, pgBrowser, Backbone, Backgrid, pgNode) {
+
+    // if module is already initialized, refer to that.
+    if (pgBrowser.GrantWizard) {
+      return pgBrowser.GrantWizard;
+    }
+
+    /**
+      It is sub model for field "Objects". It has fields
+      for database object types such as Schemas, Views and
+      Sequence etc.
+    */
+    var DatabaseObjectModel = pgNode.Model.extend({
+      defaults: {
+        selected: false,
+        icon: 'icon-unknown',
+        name: undefined,
+        name_with_args: undefined,
+        nspname: undefined,
+        proargs: undefined,
+        object_type: undefined,
+        object_id: undefined
+      },
+      idAttribute: 'object_id', // to uniquely identify a model object
+      toJSON: function(obj) {
+        var d = pgNode.Model.prototype.toJSON.apply(this);
+        delete d.icon;
+        return d;
+      },
+      parse: function(res) {
+
+        // Create unique object id
+        res.object_id = res.name_with_args;
+
+        // create name with args if its object is function
+        if(!_.isUndefined(res.object_type) && (res.object_type == 'Function' ||
+          res.object_type == 'Trigger Function'))
+          res.name_with_args = res.name+'('+(typeof(res.proargs) != 'undefined' ? res.proargs : '')+')';
+        else
+          res.name_with_args = res.name;
+
+        return res;
+      },
+
+      validate: function() {
+
+        /*
+        * Triggers error messages for object types "selected"
+        * if it is empty/undefined/null
+        */
+        var err = {},
+          errmsg,
+          node = this.get('objects').toJSON();
+        if (_.isEmpty(node)) {
+          err['selected'] = '{{ _("Please select any database object type") }}';
+          errmsg = errmsg || err['selected'];
+          this.errorModel.set('selected', errmsg);
+          return errmsg;
+        } else {
+          this.errorModel.unset('selected');
+        }
+        return null;
+      }
+    });
+
+    // Define columns for the Db Object Types grid
+    var columns = [{
+      name: "selected",
+
+      /*
+      Override render method of Backgrid.Extension.SelectRowCell
+      class. It has an issue: It doesn't mark rows checked if we move to next
+      page and then go back to previous page. but it must show.
+      so we handle this case by overriding the render method.
+      */
+      cell: Backgrid.Extension.SelectRowCell.extend({
+        render: function() {
+
+          // Use the Backform Control's render function
+          Backgrid.Extension.SelectRowCell.prototype.render.apply(this, arguments);
+
+          var col = this.column.get('name');
+          if (this.model && this.model.has(col)) {
+            if (this.model.get(col)) {
+              this.checkbox().prop("checked", true);
+              this.$el.parent().toggleClass("selected", true);
+              this.model.trigger("backgrid:selected", this.model, true);
+            }
+          }
+          return this;
+        }
+      }),
+
+      headerCell: "select-all",
+
+    },{
+      name: "object_type",
+      label: "Object Type",
+      editable: false,
+      cell: Backgrid.Cell.extend({
+        render: function() {
+
+          // Override render to add icon to Db Object column
+          Backgrid.Cell.prototype.render.apply(this, arguments);
+          this.$el.addClass(this.model.get('icon')).css({"padding-left": "24px"});
+
+          return this;
+        }
+      })
+    },{
+      name: "nspname",
+      label: "Schema",
+      cell: "string",
+      editable: false
+    },{
+      name: "name_with_args",
+      label: "Name",
+      cell: "string",
+      editable: false
+    }];
+
+    // Create an Object GrantWizard of pgBrowser class
+    pgBrowser.GrantWizard  = {
+      init: function() {
+        if (this.initialized)
+          return;
+
+        this.initialized = true;
+
+        // Define list of nodes on which grant wizard context menu option appears
+        var supported_nodes = [
+              'schema', 'coll-function',
+              'coll-sequence', 'coll-table',
+              'coll-view', 'coll-materialized_view'
+            ],
+
+            /**
+              Enable/disable grantwizard menu in tools based
+              on node selected
+              if selected node is present in supported_nodes,
+              menu will be enabled otherwise disabled.
+              Also, hide it for system view in catalogs
+            */
+            menu_enabled = function(itemData, item, data) {
+              var t = pgBrowser.tree, i = item, d = itemData;
+              var parent_item = t.hasParent(i) ? t.parent(i): null,
+                  parent_data = parent_item ? t.itemData(parent_item) : null;
+              if(!_.isUndefined(d) && !_.isNull(d) && !_.isNull(parent_data))
+                return ((_.indexOf(supported_nodes, d._type) !== -1 && parent_data._type != 'catalog') ? true: false);
+              else
+                return false;
+            };
+
+        // Define the nodes on which the menus to be appear
+        var menus = [{
+          name: 'grant_wizard_schema', module: this,
+          applies: ['tools'], callback: 'start_grant_wizard',
+          priority: 10, label: '{{_("Grant Wizard...") }}',
+          icon: 'wcTabIcon', enable: menu_enabled
+        }];
+
+        // Add supported menus into the menus list
+        for (var idx = 0; idx < supported_nodes.length; idx++) {
+          menus.push({
+            name: 'grant_wizard_schema_context_' + supported_nodes[idx],
+            node: supported_nodes[idx], module: this,
+            applies: ['context'], callback: 'start_grant_wizard',
+            priority: 10, label: '{{_("Grant Wizard...") }}',
+            icon: 'wcTabIcon', enable: menu_enabled
+            });
+        }
+        pgAdmin.Browser.add_menus(menus);
+
+        return this;
+      },
+
+      // Callback to draw Wizard Dialog
+      start_grant_wizard: function(action, item) {
+
+        // Declare Wizard dialog
+        if (!alertify.wizardDialog) {
+          alertify.dialog('wizardDialog', function factory() {
+
+            // Generate wizard main container
+            var $container = $("<div class='wizard_dlg'></div>");
+
+            return {
+              main: function(title) {
+                this.set('title', title);
+              },
+              setup:function() {
+                return {
+
+                  // Set options for dialog
+                  options: {
+                    frameless: true,
+                    resizable: false,
+                    autoReset: false,
+                    maximizable: false,
+                    closableByDimmer: false
+                  }
+                };
+              },
+              hooks:{
+                onshow: function() {
+
+                  // Set dimensions for wizard
+                  this.elements.dialog.style.width = '100%';
+                  this.elements.dialog.style.height = '68%';
+
+                  // Add pgadmin_grant_wizard_body class to dialog
+                  $(this.elements.body).addClass('pgadmin_grant_wizard_body');
+                },
+              },
+
+              /**
+                Returns a Paginator Class Object which is again to be rendered
+
+                @class {Backgrid.Extension.Paginator}
+                @param {Backbone.Collection} coll - from which data is fetched
+                @return {Object} paginator
+              */
+              DbPaginator: function(coll){
+                var paginator = this.paginator = new Backgrid.Extension.Paginator({
+                  collection: coll,
+                  windowSize: 8
+                });
+                return paginator;
+              },
+
+              /**
+                Create new Filter which will filter the
+                rendered grid for Select Type Tabular Data
+                @param {Backbone.PageableCollection} coll
+              */
+              DbObjectFilter: function(coll){
+                var clientSideFilter = this.clientSideFilter = new Backgrid.Extension.ClientSideFilter({
+                  collection: coll,
+                  placeholder: _('Search by object type or name'),
+
+                  // The model fields to search for matches
+                  fields: ['object_type', 'name'],
+
+                  // How long to wait after typing has stopped before searching can start
+                  wait: 150
+                });
+                return clientSideFilter;
+              },
+
+              //Enable Disable Next button of PrivilegePage
+              updateButtons: function(modified){
+                if(!modified)
+                  $('.wizard-next').prop('disabled', true);
+                else
+                  $('.wizard-next').prop('disabled', false);
+              },
+
+              /**
+                Callback called when an errorModel is set
+                with invalid value and errormsg is set into
+                status bar element and next button is disabled
+              */
+              onSessionInvalid: function(msg) {
+                $('.error_msg_div p').html(msg).removeClass("hide");
+
+                // Enable disable Next button
+                this.updateButtons(false);
+                return true;
+              },
+
+              /**
+                Callback called when anything is set into model
+                thus hide error msg element and enable next button
+                status bar element and next button is disabled
+              */
+              onSessionValidated: function(sessHasChanged) {
+                $('.error_msg_div p').empty().addClass("hide");
+
+                // Enable disable Next button
+                this.updateButtons(sessHasChanged);
+              },
+
+              /**
+                Remove/Delete objects, attributes
+                in wizard on wizard close or finish
+                to reclaim memory
+              */
+              releaseObjects: function(){
+                var self = this;
+
+                if(!_.isUndefined(self.dbObjectFilter)) {
+                  self.dbObjectFilter.remove();
+                  self.dbObjectFilter = undefined;
+                }
+
+                if(!_.isUndefined(self.clientSideFilter)) {
+                  self.clientSideFilter.remove();
+                  self.clientSideFilter = undefined;
+                }
+
+                // clear object priv array
+                if(!_.isNull(self.obj_priv) &&
+                  !_.isUndefined(self.obj_priv)) {
+                  self.obj_priv = [];
+                  delete self.obj_priv;
+                }
+
+                // Delete Wizard Pages, clear model and cleanup view
+                if(!_.isUndefined(self.dbObjectTypePage) &&
+                  !_.isNull(self.dbObjectTypePage)) {
+                  if(!_.isUndefined(self.dbObjectTypePage.get('model')) &&
+                    !_.isNull(self.dbObjectTypePage.get('model'))) {
+                    self.dbObjectTypePage.get('model').clear();
+                    self.dbObjectTypePage.get('view').cleanup();
+                    self.dbObjectTypePage = undefined;
+                  }
+                }
+
+                if(!_.isUndefined(self.privilegePage) &&
+                  !_.isNull(self.privilegePage)) {
+                  if(!_.isUndefined(self.privilegePage.get('model')) &&
+                    !_.isNull(self.privilegePage.get('model'))) {
+                    self.privilegePage.get('model').clear();
+                    self.privilegePage.get('view').cleanup();
+                    self.privilegePage = undefined;
+                  }
+                }
+
+                if(!_.isUndefined(self.reviewSQLPage) &&
+                  !_.isNull(self.reviewSQLPage)) {
+                  if(!_.isUndefined(self.reviewSQLPage.get('model')) &&
+                    !_.isNull(self.reviewSQLPage.get('model'))) {
+                    self.reviewSQLPage.get('model').clear();
+                    self.reviewSQLPage = undefined;
+                  }
+                }
+
+                // Remove Sql control
+                if (!_.isUndefined(self.sqlControl)) {
+                  self.sqlControl.remove();
+                }
+
+                // Clear privModel
+                if(!_.isNull(self.privModel) &&
+                  !_.isUndefined(self.privModel)) {
+                  self.privModel.clear();
+                }
+
+                // Remove collection containing db object data
+                if(!_.isNull(self.coll) &&
+                  !_.isUndefined(self.coll)) {
+                  self.coll.reset();
+                  self.coll = undefined;
+                }
+                // Delete Wizard
+                if(!_.isNull(self.wizard) &&
+                  !_.isUndefined(self.wizard)) {
+                  self.wizard.collection.reset();
+                  self.wizard.curr_page = undefined;
+                }
+
+              },
+
+              /**
+                Every time a wizard is opened, this function
+                is called everytime. It has Wizard Pages which
+                are rendered by the Wizard Class:
+
+                @class {pgBrowser.WizardPage} dbObjectType1 - This page
+                @extends {Backbone.Model}
+                renders a grid of Database Object Types such as
+                  Schemas, Views and Sequences etc.
+
+                @class {pgBrowser.WizardPage} WizardPage2 - This page
+                @extends {Backbone.Model}
+                adds Privilege Control which provides grant privileges
+                such as "Create, Insert, Delete, Update" so on the
+                database objects selected on Wizard Pages.
+
+                @class {pgBrowser.WizardPage} WizardPage3 - This page
+                displays the generated GRANT SQL query for the Db
+                objects selected with the specific privileges added to it.
+                @extends {Backbone.Model}
+
+                @class {Backbone.Collection} WizardCollection - It is the
+                collection of wizard pages
+
+                @class {pgBrowser.Wizard} wizard - Its task is:
+                - Create a Wizard
+                - Add Buttons, Callbacks to it.
+                - Render WizardPages
+                @extends {Backbone.View}
+
+              */
+              build: function() {
+                this.elements.content.appendChild($container.get(0));
+              },
+
+              //Returns list of Acls defined for nodes
+              get_json_data: function() {
+                var url = "/grant_wizard/allowed_acl.json";
+                return $.ajax({
+                  async: false,
+                  dataType: 'jsonp',
+                  url: url
+                });
+
+              },
+              prepare:function() {
+
+                $container.empty().append("<div class='grant_wizard_container'></div>");
+
+                // Define el for wizard view
+                var el = $('.grant_wizard_container');
+
+                // Fetch privileges specific to nodes
+                var json_data = this.get_json_data(),
+                    privDict = JSON.parse(json_data.responseText);
+
+                // Extract the data from the selected tree node
+                var t = pgBrowser.tree,
+                    i = t.selected(),
+                    d = this.d = i && i.length == 1 ? t.itemData(i) : undefined,
+                    info = this.info = pgBrowser.Node.getTreeNodeHierarchy(i),
+                    icon = d.icon;
+
+                /**
+                  Generate a URL using:
+                  gid, did, sid(server id), node_id(node id),
+                  node_(node name), node_type(node type)
+                  and pass it to collection which will fetch Object Type properties.
+                */
+                var gid = info['server-group']._id,
+                    sid = info.server._id,
+                    did = info.database._id,
+                    node_id = d._id,
+                    nspname = info.schema.label,
+
+                    /**
+                      get node name only. used in mapping with object types defined
+                      in allowed_acl.json
+                     */
+                    node_type = d._type.replace('coll-', '').replace('materialized_', ''),
+                    node_label = d.label;
+
+                    // Collection url to fetch database object types for objects field
+                    var baseUrl = "{{ url_for('grant_wizard.index') }}" + "properties/" +
+                        S('%s/%s/%s/%s/%s/%s/%s/').sprintf(
+                            encodeURI(gid), encodeURI(sid), encodeURI(did),
+                            encodeURI(node_id), encodeURI(node_label),
+                            encodeURI(node_type), encodeURI(nspname)).value();
+
+                    // Model's save url
+                    saveUrl = "{{ url_for('grant_wizard.index') }}" + "save/" +
+                        S('%s/%s/%s/%s/%s/').sprintf(
+                            encodeURI(gid), encodeURI(sid), encodeURI(did),
+                            encodeURI(node_type), encodeURI(nspname)).value(),
+
+                    // generate encoded url based on wizard type
+                    msql_url = this.msql_url = "/grant_wizard/msql/"+
+                      S('%s/%s/%s/%s/%s/').sprintf(
+                          encodeURI(gid), encodeURI(sid), encodeURI(did),
+                          encodeURI(node_type), encodeURI(nspname)).value(),
+
+                    Coll = Backbone.Collection.extend({
+                      model: DatabaseObjectModel,
+                      url: baseUrl
+                    }),
+
+                    // Create instances of collection and filter
+                    coll = this.coll = new Coll(),
+                    dbObjectFilter = this.dbObjectFilter = this.DbObjectFilter(coll);
+
+                /**
+                  privArray holds objects selected which further helps
+                  in creating privileges Model
+                */
+                var self = this;
+                self.privArray = [];
+
+                /**
+                  Override backgrid listener "backgrid:selected" to
+                  Add/Remove model to/from objects collection
+                */
+                coll.on('backgrid:selected', function(model, selected) {
+                  model.set('selected', selected);
+
+                  var object_type = model.get('object_type');
+                  switch (object_type)
+                  {
+                    case 'Function':
+                      object_type = 'function';
+                      break;
+                    case 'Trigger Function':
+                      object_type = 'function';
+                      break;
+                    case 'Table':
+                      object_type = 'table';
+                      break;
+                    case 'Sequence':
+                      object_type = 'sequence';
+                      break;
+                    case 'View':
+                      object_type = 'table';
+                      break;
+                  }
+
+                  /**
+                    if a row (checkbox) is checked, add that model
+                    into collection, when unchecked remove it from
+                    model.
+
+                    Also push/pop object type in/from privArray
+                  */
+                  if(selected) {
+                    if(_.indexOf(self.privArray, object_type) == -1)
+                      self.privArray.push(object_type);
+                    newModel.get('objects').add(model, { silent: true });
+                  }
+                  else {
+                    var idx = self.privArray.indexOf(object_type);
+                    if(idx !=-1)
+                      self.privArray.splice(idx, 1);
+                    newModel.get('objects').remove(model);
+                  }
+
+                  // validate model on checkbox check/uncheck
+                  var msg =  model.validate.call(newModel);
+
+                  /**
+                    If no object type is selected, set error msg
+                    and disable next button, else enable next button
+                  */
+                  if(msg)
+                    self.onSessionInvalid.call(self, msg);
+                  else
+                    self.onSessionValidated.call(self, true);
+                });
+
+                /**
+                  It is the main model with schema defined
+                  Every time a new wizard is opened,
+                  a new model should create.
+                */
+                var GrantWizardModel = pgNode.Model.extend({
+                  defaults: {
+                    objects: undefined,
+                    acl: undefined
+                  },
+                  schema: [
+                    {
+                      id: 'objects', label: '{{ _("Objects") }}', model: DatabaseObjectModel,
+                      type: 'collection', group: 'Objects'
+                    },
+                    {
+                      id: 'acl', label: '{{ _("Privileges") }}',
+                      model: pgAdmin.Browser.Node.PrivilegeRoleModel,
+                      type: 'collection', canAdd: true,
+                      canDelete: true, control: 'unique-col-collection'
+                    }
+                  ],
+                  urlRoot: saveUrl
+                });
+
+                /**
+                  Create instance of GrantWizard Model, provide urlRoot
+                  node_info object, Generate fields objects
+                */
+                var newModel = new GrantWizardModel({}, { node_info: info });
+
+                /**
+                  Fetch data from server and set into grid
+                  and show/hide progress bar
+                */
+                $('.wizard-progress-bar p').show();
+
+                coll.fetch({
+                  success: function(collection, data) {
+                    $('.wizard-progress-bar p').html('');
+                  },
+                  reset: true
+                }, this);
+
+                //////////////////////////////////////////////////////////////////////
+                //                                                                  //
+                //            Wizard Page for Db Object Type                        //
+                //                                                                  //
+                //////////////////////////////////////////////////////////////////////
+
+                /**
+                  Create wizard page. It renders a grid of
+                  Database Object Types such as
+                  Schemas, Views and Sequences etc.
+                  Set default values
+                */
+                var dbObjectTypePage = self.dbObjectTypePage = new pgBrowser.WizardPage({
+                  id: 1,
+                  page_title: _('Object Selection (step 1 of 3)'),
+                  disable_prev: true,
+                  disable_next: true,
+                  show_description: _('Please select objects from the below list.'),
+                  show_progress_bar: _('Please wait while fetching records...'),
+                  model: newModel,
+                  view: new (function() {
+
+                    // Set page Instance
+                    var pageView = this;
+
+                    _.extend(pageView, {
+
+                      // Remove grid if it is before render
+                      cleanup: function() {
+                        if (this.grid) {
+                          this.grid.remove();
+                          delete this.grid;
+                          this.grid = null;
+                        }
+
+                        // Remove grid element if exists
+                        if (this.el) {
+                          $(this.el).remove();
+                          delete this.el;
+                        }
+                      },
+
+                      // Delete grid before render
+                      grid: null,
+
+                      render: function() {
+
+                        // Create a grid container
+                        var gridBody =
+                              $('<div class="select_db_objects_container"></div>');
+
+                        // Remove grid if exits before render
+                        if (this.grid) {
+                          this.cleanup();
+                        }
+
+                        // Initialize a new Grid instance
+                        this.grid = new Backgrid.Grid({
+                          columns: _.clone(columns),
+                          collection: coll,
+                          className: "backgrid table-bordered object_type_table"
+                          });
+
+                        // Render selection Type grid and paginator
+                        gridBody.append( this.grid.render().$el);
+
+                        // Render Search Filter
+                        gridBody.prepend(
+                          self.clientSideFilter.render().el);
+
+                        // Assign gridBody content to page element
+                        this.el = gridBody;
+
+                        /**
+                          Fetch selected models from collection and
+                          make rows checked in grid
+                        */
+                        newModel.get('objects').each(function(m) {
+                          var model = coll.get(m.get('object_id'));
+                          if (model) {
+                            coll.trigger('backgrid:selected', model, true);
+                          }
+                        });
+
+                        // Refresh grid to re render rows.
+                        coll.trigger('backgrid:refresh');
+
+                        return this;
+                      }
+                    });
+                  }),
+
+                  beforeNext: function(obj){
+                    var self = this;
+                    obj.options.disable_next = true;
+
+                    /**
+                      Enable/Disable next button of privilegePage if objects
+                      are present in model
+                    */
+                    if(!_.isNull(newModel.get('acl')) &&
+                      !_.isUndefined(newModel.get('acl'))) {
+                      if(newModel.get('acl').length > 0)
+                        obj.collection.at(1).set('disable_next', false);
+                      }
+
+                    // Clean the view
+                    if (self.view) {
+                      self.view.cleanup();
+                      delete self.view;
+                      self.view = null;
+                    }
+                    return true;
+                  },
+
+                });
+
+                //////////////////////////////////////////////////////////////////////
+                //                                                                  //
+                //            Wizard Page for Privilege Control                     //
+                //                                                                  //
+                //////////////////////////////////////////////////////////////////////
+
+                // Wizard for Privelege control
+                var privilegePage = self.privilegePage = new pgBrowser.WizardPage({
+                  id: 2,
+                  page_title: _('Privileges Selection (step 2 of 3)'),
+                  show_description: _('Please select privileges for the selected objects.'),
+                  disable_next: true,
+                  model: newModel,
+
+                  // Create a view function object
+                  view: new (function() {
+                    var pageView = this;
+                      _.extend(pageView, {
+
+                        // Render Privelege control to generate its html markup
+                        render: function() {
+
+                          var obj_priv = [];
+                          self.privArray = _.uniq(self.privArray);
+                          _.each(self.privArray, function(priv){
+                            self.obj_priv = obj_priv = _.union(obj_priv , privDict[priv].acl);
+                          });
+
+                          /**
+                            Define PrivModel and its instance.
+                            Privileges array is generated based on
+                            the type of nodes selected.
+                           */
+                          var privModel = self.privModel;
+                          var PrivModel = pgNode.Model.extend({
+                            defaults: {
+                              acl: undefined
+                            },
+                            schema: [
+                              {
+                                id: 'acl', label: '{{ _("Privileges") }}',
+                                model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend({
+
+                                  // privileges are selected based on node clicked
+                                  privileges: obj_priv
+                                }), uniqueCol : ['grantee', 'grantor'], editable: true,
+                                type: 'collection', canAdd: true,
+                                canDelete: true, control: 'unique-col-collection'
+                              }
+                            ]
+                          });
+
+                          /**
+                            When privelege control is re-rendered, in order to
+                            render privileges based on object type selected,
+                            delete privileges from privModel which are now not
+                            present in object privileges array(object_priv)
+                           */
+                          var data = {};
+                          if (privModel) {
+                            data = privModel.toJSON();
+                            var rolePrivs = data['acl'];
+
+                            for (var idx in rolePrivs) {
+                              var rolePriv = (rolePrivs[idx])['privileges'],
+                                  removeIdx = [], j;
+
+                              for (j in rolePriv) {
+                                var p = rolePriv[j];
+                                if (_.indexOf(obj_priv, p['privilege_type']) == -1) {
+                                  removeIdx.push(j);
+                                }
+                              }
+
+                              for (j in removeIdx) {
+                                rolePriv.splice(j, 1);
+                              }
+                            }
+                          }
+
+                          // Instantiate privModel
+                          privModel = self.privModel = new PrivModel(data, { node_info: self.info });
+
+                          /*
+                          To track changes into model, start new session
+                          and Add event listener for privileges control
+                          */
+                          self.privModel.startNewSession();
+                          self.privModel.on('pgadmin-session:valid', self.onSessionValidated.bind(self));
+                          self.privModel.on('pgadmin-session:invalid', self.onSessionInvalid.bind(self));
+
+                        /**
+                          Create Field Object which has properties like
+                          node_data, node_info which is required for rendering
+                          Privilege control
+                          */
+                          var fields = Backform.generateViewSchema(
+                              self.info, self.privModel, 'create', self.d._type, self.d
+                              );
+                          var privilegesField = new Backform.Field(fields[0].fields[0]);
+
+                          this.privControl = new (privilegesField.get('control')) ({
+                            field: privilegesField,
+                            model: self.privModel
+                          });
+
+                          return {el: this.privControl.render().$el};
+                        },
+
+                        // Remove the privilege control
+                        cleanup: function() {
+                          if (this.privControl) {
+                            this.privControl.remove();
+                            delete this.privControl;
+                            this.privControl = null;
+                          }
+                        }
+                      });
+                  }),
+
+                  beforePrev: function(wizardObj) {
+
+                    // Remove the privilege control
+                    if (this.view) {
+                      this.view.cleanup();
+                      delete this.view;
+                      this.view = null;
+                    }
+
+                    /**
+                      Enable/Disable next button of DbObjectType page if objects
+                      are present in model
+                     */
+                    var objectsModel = newModel.get('objects');
+
+                    if(!_.isUndefined(objectsModel) && !_.isEmpty(objectsModel) &&
+                        objectsModel.length > 0) {
+                      wizardObj.collection.at(0).set('disable_next', false);
+
+                      // Don't show progress bar
+                      wizardObj.collection.at(0).set('show_progress_bar', '');
+                    }
+
+                    /**
+                      We're re-rendering the controls as they are deleted
+                      before heading to next page
+                      Refresh Backgrid to re-render the elements selected
+                      re-render Filter
+                    */
+                    newModel.trigger("backgrid:refresh", newModel, false);
+                    self.clientSideFilter.render();
+                    return true;
+                  },
+
+                  beforeNext: function() { return true; },
+
+                  onNext: function(obj){
+
+                    // Assign acls of privModel to main model newModel
+                    if (!_.isUndefined(self.privModel)) {
+                      newModel.set({'acl': self.privModel.get('acl')});
+                    }
+
+                    // Remove the privilege control
+                    if (this.view) {
+                      this.view.cleanup();
+                      delete this.view;
+                      this.view = null;
+                    }
+
+                    // Enable finish button
+                    self.wizard.options.disable_finish = false;
+
+                    /**
+                      triggers to get SQL queries data to render
+                      into the reviewSQLPage
+                    */
+                    newModel.trigger('pgadmin-wizard:nextpage:sql', {'node_type': node_type });
+                  }
+                });
+
+
+                //////////////////////////////////////////////////////////////////////
+                //                                                                  //
+                //            Review SQL Query Page                                 //
+                //                                                                  //
+                //////////////////////////////////////////////////////////////////////
+
+                //Create SqlField Object
+                var sqlField = new Backform.Field(
+                    {
+                      id: 'sqltab',
+                      label: _('Sql Tab'),
+
+                      /**
+                        Extend 'SqlTabControl' to define new
+                        function 'onWizardNextPageChange'
+                        which gets triggered on next button
+                        click to fetch generated SQL query
+                        for the selected db objects.
+                      */
+                      control: Backform.SqlTabControl.extend({
+                        initialize: function() {
+
+                          // Initialize parent class
+                          Backform.SqlTabControl.prototype.initialize.apply(this, arguments);
+
+                          this.msql_url = self.msql_url;
+
+                          // define trigger events for prev and next page
+                          this.model.on('pgadmin-wizard:nextpage:sql', this.onWizardNextPageChange, this);
+                          this.model.on('pgadmin-wizard:prevpage:sql', this.onWizardPrevPageChange, this);
+                        },
+
+                        // This method fetches the modified SQL for the wizard
+                        onWizardNextPageChange: function(){
+
+                          var self = this;
+
+                          // Fetches modified SQL
+                          $.ajax({
+                            url: this.msql_url,
+                            type: 'GET',
+                            cache: false,
+                            data: self.model.toJSON(true, 'GET'),
+                            dataType: "json",
+                            contentType: "application/json"
+                          }).done(function(res) {
+                            self.sqlTab.clearHistory();
+                            self.sqlTab.setValue(res.data);
+                          }).fail(function() {
+                            self.model.trigger('pgadmin-view:msql:error');
+                          }).always(function() {
+                            self.model.trigger('pgadmin-view:msql:fetched');
+                          });
+                        },
+
+                        remove: function() {
+
+                          // Clear html dom elements of CodeMirror sql tab
+                          self.sqlControl.unbind(); // Unbind all local event bindings
+                          var cmElem = self.sqlControl.sqlTab.getWrapperElement();
+                          cmElem.remove(); 
+                          self.sqlControl.sqlTab = undefined;
+                        }
+
+                      })
+                    }),
+
+                  /**
+                    Create sqlField view instance
+                    to render it into wizard page
+                  */
+                  sqlControl = self.sqlControl = new (sqlField.get('control'))({
+                    field: sqlField,
+                    model: newModel
+                  });
+
+                // Wizard for SQL tab control
+                var reviewSQLPage = self.reviewSQLPage = new pgBrowser.WizardPage({
+                  id: 3,
+                  page_title: _('Final (Review Selection) (step 3 of 3)'),
+                  show_description: _('Following query will be executed on the database ' +
+                                'server for the selected objects, and privileges. ' +
+                                'Please click on Finish to complete the process.'),
+                  model: newModel,
+                  view: new(function() {
+
+                    // Render SqlTab control to generate its html markup
+                    var sqlTabHtml = sqlControl.render().$el;
+                    this.render = function() {
+                        return { el: sqlTabHtml };
+                    };
+                  }),
+
+                  beforePrev: function(wizardObj) {
+
+                    /**
+                      Enable next button if privilege
+                      model is not empty else disable
+                      next button
+                     */
+                    var aclModel = newModel.get('acl');
+
+                    if(!_.isUndefined(wizardObj.collection) &&
+                      wizardObj.collection.models.length > 0) {
+                      if(!_.isUndefined(aclModel) && !_.isEmpty(aclModel) &&
+                          aclModel.length > 0) {
+                        wizardObj.collection.at(1).set('disable_next', false);
+                      }
+                      else {
+                        wizardObj.collection.at(1).set('disable_next', true);
+                      }
+
+                      return true;
+                    }
+                  },
+                });
+
+
+                // Create Wizard Collection of Wizard Pages
+                var WizardCollection = Backbone.Collection.extend({
+                  model: pgBrowser.WizardPage
+                });
+
+                // It holds all the wizard pages to be rendered
+                this.wizardCollection = new WizardCollection(
+                  [dbObjectTypePage, privilegePage, reviewSQLPage]
+                );
+
+                /**
+                  Create wizard which has following operations:
+                  - renders wizard pages
+                  - defines the first page to render in wizard
+                  - Save the model on finishbutton
+                  - Remove wizard on cancel button
+                  */
+                self.wizard = new (pgBrowser.Wizard.extend({
+                  options: {
+                    title: _('Grant Wizard'), /* Main Wizard Title */
+                    width: '',
+                    height: '',
+                    curr_page: 0,
+                    show_left_panel: false,
+                    disable_finish: true
+                  },
+
+                  // Callback for finish button
+                  onFinish: function() {
+                    var m = newModel,
+                        d = m.toJSON('GET');
+
+                    // Save model
+                    if (d && !_.isEmpty(d) && !_.isUndefined(d.objects)) {
+                      m.save({}, {
+                        attrs: d,
+                        validate: false,
+                        cache: false,
+                        success: function(res) {
+
+                          // Release wizard objects
+                          self.releaseObjects();
+                          self.close();
+                        },
+                        error: function(m, jqxhr) {
+                          alertify.pgNotifier(
+                            "error", jqxhr,
+                            S(
+                              "{{ _('Error during saving properties - %%s!') }}"
+                              ).sprintf(jqxhr.statusText).value()
+                            );
+                        }
+                      });
+                    }
+                  },
+
+                  // Callback for cancel button
+                  onCancel: function() {
+
+                    // Release wizard objects
+                    self.releaseObjects();
+                    self.close();
+                  }
+                })) ({
+                  collection: this.wizardCollection,
+                  el: el,
+                  model: newModel
+                });
+
+                // Render wizard
+                self.wizard.render();
+              }
+            };
+          });
+        }
+
+        // Call Grant Wizard Dialog
+        alertify.wizardDialog(true);
+      }
+    };
+
+    return pgBrowser.GrantWizard;
+  });
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/allowed_acl.json b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/allowed_acl.json
new file mode 100644
index 0000000..2d21f14
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/allowed_acl.json
@@ -0,0 +1,30 @@
+{# List of allowed privileges of Schema, table sequence, function and view #}
+{#
+    Format for allowed privileges are:
+    "node_name": {
+        "type": "name",
+        "acl": [...]
+    }
+#}
+{
+    "schema": {
+        "type": "SCHEMA",
+        "acl": ["a", "r", "w", "d", "D", "x", "t", "U", "X"]
+    },
+    "table": {
+        "type": "TABLE",
+        "acl": ["a", "w", "d", "D", "x", "t"]
+    },
+    "view": {
+        "type": "VIEW",
+        "acl": ["a", "w", "d", "D", "x", "t"]
+    },
+    "sequence": {
+        "type": "SEQUENCE",
+        "acl": ["w", "U"]
+    },
+    "function": {
+        "type": "FUNCTION",
+        "acl": ["X"]
+    }
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/grant.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/grant.sql
new file mode 100644
index 0000000..ae59b53
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/grant.sql
@@ -0,0 +1,19 @@
+{#=====Grant Permissions on Database Objects Selected====#}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% import 'macros/functions/privilege.macros' as PRIVILEGE_FUNCTION %}
+{% if data.acl -%}
+{% for obj in data.objects -%}
+{% for priv in data.acl -%}
+{%- set obj_type = obj.object_type|upper -%}
+{# ===== if object_type is Function then apply function marcros ===== #}
+{% if (obj_type == 'FUNCTION' or obj_type == 'TRIGGER FUNCTION') and (priv.with_grant['function']|length > 0 or priv.without_grant['function']|length > 0) -%}
+{{ PRIVILEGE_FUNCTION.SET(conn, 'FUNCTION', priv.grantee, obj.name, priv.without_grant['function'], priv.with_grant['function'], nspname, obj.proargs)}}
+{% elif (obj_type == 'SEQUENCE') and (priv.with_grant['table']|length > 0 or priv.without_grant['table']|length > 0) -%}
+{{ PRIVILEGE.SET(conn, obj_type.upper(), priv.grantee, obj.name, priv.without_grant['sequence'], priv.with_grant['sequence'], nspname ) }}
+{% elif (obj_type == 'VIEW' or obj_type == 'TABLE') and (priv.with_grant['table']|length > 0 or priv.without_grant['table']|length > 0) -%} {# ===== Views are also table ===== #}
+{%- set obj_type = 'TABLE' -%}
+{{ PRIVILEGE.SET(conn, obj_type.upper(), priv.grantee, obj.name, priv.without_grant['table'], priv.with_grant['table'], nspname ) }}
+{% endif -%}
+{% endfor -%}
+{% endfor -%}
+{% endif -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/properties.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/properties.sql
new file mode 100644
index 0000000..e92888c
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/properties.sql
@@ -0,0 +1,66 @@
+{#===================Fetch list of Database object types(Functions, Sequence, Tables or View)===================#}
+{% if type == 'schema' or type == 'trigger_function' and node_id and node_name %}
+{% set func_type = 'Trigger Function' if type == 'trigger_function' else 'Function' %}
+SELECT
+    pg_get_function_identity_arguments(pr.oid) AS proargs,
+    {# pr.proname || '(' || pg_get_function_identity_arguments(pr.oid) || ')' AS name,#}
+    pr.proname AS name,
+    '{{ func_type }}' AS object_type,
+    '{{ nspname }}' AS nspname,
+    '{{ "icon-function" if type != "trigger_function" else "icon-trigger_function" }}' AS icon
+FROM pg_proc pr
+    JOIN pg_type typ ON typ.oid=prorettype
+    JOIN pg_namespace typns ON typns.oid=typ.typnamespace
+    JOIN pg_language lng ON lng.oid=prolang
+    LEFT OUTER JOIN pg_description des ON (des.objoid=pr.oid AND des.classoid='pg_proc'::regclass)
+WHERE proisagg = FALSE AND pronamespace = {{ node_id }}::oid
+    AND typname {{ 'NOT' if type != 'trigger_function' else '' }} IN ('trigger', 'event_trigger')
+ORDER BY proname
+{% endif %}
+
+{% if type == 'sequence' and node_id and node_name %}
+SELECT
+    cl.relname AS name,
+    'Sequence' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-sequence' AS icon
+FROM pg_class cl
+    LEFT OUTER JOIN pg_description des ON (des.objoid=cl.oid AND des.classoid='pg_class'::regclass)
+WHERE relkind = 'S' AND relnamespace  = {{ node_id }}::oid
+ORDER BY cl.relname
+{% endif %}
+
+{% if type == 'table' and node_id and node_name %}
+SELECT rel.relname AS name,
+    'Table' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-table' AS icon
+FROM pg_class rel
+    LEFT OUTER JOIN pg_tablespace spc ON spc.oid=rel.reltablespace
+    LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass)
+    LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'
+    LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid
+    LEFT JOIN pg_type typ ON rel.reloftype=typ.oid
+WHERE rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ node_id }}::oid
+ORDER BY rel.relname
+{% endif %}
+
+{% if type == 'view' and node_id and node_name %}
+SELECT
+    c.relname AS name,
+    'View' AS object_type,
+    '{{ nspname }}' AS nspname,
+    'icon-view' AS icon
+FROM pg_class c
+    LEFT OUTER JOIN pg_tablespace spc ON spc.oid=c.reltablespace
+    LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass)
+    LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid
+WHERE ((c.relhasrules AND (EXISTS (
+    SELECT r.rulename
+    FROM pg_rewrite r
+    WHERE ((r.ev_class = c.oid)
+        AND (bpchar(r.ev_type) = '1'::bpchar)) ))
+       ) OR (c.relkind = 'v'::char))
+AND c.relnamespace = {{ node_id }}::oid
+ORDER BY c.relname
+{% endif %}


^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-04-05 05:36  Ashesh Vashi <[email protected]>
  parent: Surinder Kumar <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Ashesh Vashi @ 2016-04-05 05:36 UTC (permalink / raw)
  To: Surinder Kumar <[email protected]>; +Cc: Dave Page <[email protected]>; pgadmin-hackers

On Wed, Mar 30, 2016 at 5:14 PM, Surinder Kumar <
[email protected]> wrote:

> Hi,
>
> Please find updated patch.
>
> This patch has following changes:
> 1. Improved code commenting.
> 2. Properly handling memory leak issues in js code.
>
Hi Surinder,

As discussed offline, here are the list of some of the review comments:

* CSS should be relative to its parent element. Please make sure - whenever
you make
  some changes in CSS, it should not affect the existing CSS unless
discussed.

* Change class name for 'error_msg_div' as it is common name. Please name a
class
  with prefixed as the module name.

* Add comments for the blow line changed in node.ui.js file. Always add
logical
  explanation for a change as a comment for any changes.
*while(p && p.length > 0) {*

* Please make sure, we wrap the code around 80 characters for better
readability.
  Line length should not be greater than 80 characters.

* Put the allowed ACLs logic with server version support. We need to be
flexible
  enough to accommodate possible future change in ACLs.

* Avoid using name as reference in each of the given. It will make the
search faster
  in the database and less prone to character conversion issue.
  i.e.
  Use schema/namespace OID instead of nspname, object OID instead of their
name.

* Use separate templates for each type of objects.

* Use the existing functionalities as much as possible instead of
introducing new
  one. That will make the code/results consistent across the application.
  i.e.
  Use existing 'parse_priv_to_db' method, instead of creating new one.

* Please remove unnecessary suffixed white-spaces.

--

Thanks & Regards,

Ashesh Vashi
EnterpriseDB INDIA: Enterprise PostgreSQL Company
<http://www.enterprisedb.com/;


*http://www.linkedin.com/in/asheshvashi*
<http://www.linkedin.com/in/asheshvashi;


>
>
> On Mon, Mar 28, 2016 at 2:09 PM, Surinder Kumar <
> [email protected]> wrote:
>
>> Hi Dave,
>>
>> Please find updated patch with suggested changes.
>>
>> On Thu, Mar 10, 2016 at 5:26 PM, Dave Page <[email protected]> wrote:
>>
>>> On Thu, Mar 10, 2016 at 9:50 AM, Surinder Kumar
>>> <[email protected]> wrote:
>>> > Please apply Khusboo's patch for "Privileges macros under Schema"
>>> before
>>> > using grant wizard patch.
>>>
>>> Thanks, that works. Some (hopefully final) feedback:
>>>
>>> - Can we add a side-image? Not sure what yet - just a placeholder for
>>> now until I come up with something.
>>>
>> Already a placeholder for left side image.
>>
>>>
>>> - Why is the closed button in an odd position (see File -> Test Alert
>>> for comparison)
>>>
>> Fixed
>>
>>>
>>> - Why are we using a scrolling list AND pagination? I think a
>>> scrolling list alone should be fine.
>>>
>> Pagination is removed.
>>
>>>
>>> - The grid sizing is wrong. See how the scrollbar on the right in the
>>> screenshot is off the edge of the dialogue, and there's a horizontal
>>> scrollbar?
>>>
>> Fixed.
>>
>>>
>>> - We shouldn't truncate object names as that can be ambiguous. The
>>> column should extend as necessary, and there should be a horizontal
>>> scrollbar on the grid itself (not at the bottom of the dialogue
>>> content).
>>>
>> I have fixed it and scrollbar is now on the grid itself.
>>
>>>
>>> - Function names should include the parameters, as they are part of
>>> the identifier. Without, it can be ambigous - e.g. do_stuff(int) vs.
>>> do_stuff(text).
>>>
>> Fixed.
>>
>>>
>>> - If I select only functions (for example), the Privileges panel
>>> should only list privileges available for functions. If I select
>>> multiple object types, it should show the available options for only
>>> those object types.
>>>
>> Implemented this feature.
>>
>>>
>>> - If I select functions and tables, and then choose (for example),
>>> usage and truncate, it will attempt to set usage on tables and
>>> truncate on functions. It should only attempt to set privileges on the
>>> objects for which they are appropriate.
>>>
>> Done
>>
>>>
>>> - On the last page, the Next button is disabled. It is turned a
>>> marginally darker blue, but also highlights on mouse-over. The change
>>> in shade is so subtle it's hard to see, and the highlight implies the
>>> button is active when it isn't.
>>>
>> Fixed.
>>
>>>
>>> - The buttons appear to have a smaller corner radius than those on the
>>> main browser, or in other dialogues.
>>>
>> Fixed.
>>
>>>
>>> - Button labels should have an &nbsp; before them to properly space
>>> the label from the icon (or better yet, this should be done in CSS,
>>> though that would also need to be done elsewhere).
>>>
>> done with css.
>>
>>>
>>> - Why do the URLs have a /wizard prefix? I think that should be removed.
>>>
>> Removed prefix.
>>
>>>
>>> - The available privileges for each object type seem to be defined in
>>> both grant_wizard.js and allowed_acl.json. Can we just use
>>> allowed_acl.json?
>>>
>> Yes, allowed_acl.json is now used in grant_wizard.js.
>>
>>>
>>> Thanks.
>>>
>>> --
>>> Dave Page
>>> Blog: http://pgsnake.blogspot.com
>>> Twitter: @pgsnake
>>>
>>> EnterpriseDB UK: http://www.enterprisedb.com
>>> The Enterprise PostgreSQL Company
>>>
>>
>>
>
>
> --
> Sent via pgadmin-hackers mailing list ([email protected])
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgadmin-hackers
>
>


^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-04-06 11:37  Surinder Kumar <[email protected]>
  parent: Ashesh Vashi <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Surinder Kumar @ 2016-04-06 11:37 UTC (permalink / raw)
  To: Ashesh Vashi <[email protected]>; +Cc: Dave Page <[email protected]>; pgadmin-hackers

Hi

PFA updated patch with resolved review comments.

On Tue, Apr 5, 2016 at 11:06 AM, Ashesh Vashi <[email protected]
> wrote:

> On Wed, Mar 30, 2016 at 5:14 PM, Surinder Kumar <
> [email protected]> wrote:
>
>> Hi,
>>
>> Please find updated patch.
>>
>> This patch has following changes:
>> 1. Improved code commenting.
>> 2. Properly handling memory leak issues in js code.
>>
> Hi Surinder,
>
> As discussed offline, here are the list of some of the review comments:
>
> * CSS should be relative to its parent element. Please make sure -
> whenever you make
>   some changes in CSS, it should not affect the existing CSS unless
> discussed.
>
Done

>
> * Change class name for 'error_msg_div' as it is common name. Please name
> a class
>   with prefixed as the module name.
>
Done

>
> * Add comments for the blow line changed in node.ui.js file. Always add
> logical
>   explanation for a change as a comment for any changes.
> *while(p && p.length > 0) {*
>
Done

>
> * Please make sure, we wrap the code around 80 characters for better
> readability.
>   Line length should not be greater than 80 characters.
>
Done

>
> * Put the allowed ACLs logic with server version support. We need to be
> flexible
>   enough to accommodate possible future change in ACLs.
>
Done

>
> * Avoid using name as reference in each of the given. It will make the
> search faster
>   in the database and less prone to character conversion issue.
>   i.e.
>   Use schema/namespace OID instead of nspname, object OID instead of their
> name.
>
Done

>
> * Use separate templates for each type of objects.
>
Done

>
> * Use the existing functionalities as much as possible instead of
> introducing new
>   one. That will make the code/results consistent across the application.
>   i.e.
>   Use existing 'parse_priv_to_db' method, instead of creating new one.
>
Done

>
> * Please remove unnecessary suffixed white-spaces.
>
Done

>
> --
>
> Thanks & Regards,
>
> Ashesh Vashi
> EnterpriseDB INDIA: Enterprise PostgreSQL Company
> <http://www.enterprisedb.com/;
>
>
> *http://www.linkedin.com/in/asheshvashi*
> <http://www.linkedin.com/in/asheshvashi;
>
>
>>
>>
>> On Mon, Mar 28, 2016 at 2:09 PM, Surinder Kumar <
>> [email protected]> wrote:
>>
>>> Hi Dave,
>>>
>>> Please find updated patch with suggested changes.
>>>
>>> On Thu, Mar 10, 2016 at 5:26 PM, Dave Page <[email protected]> wrote:
>>>
>>>> On Thu, Mar 10, 2016 at 9:50 AM, Surinder Kumar
>>>> <[email protected]> wrote:
>>>> > Please apply Khusboo's patch for "Privileges macros under Schema"
>>>> before
>>>> > using grant wizard patch.
>>>>
>>>> Thanks, that works. Some (hopefully final) feedback:
>>>>
>>>> - Can we add a side-image? Not sure what yet - just a placeholder for
>>>> now until I come up with something.
>>>>
>>> Already a placeholder for left side image.
>>>
>>>>
>>>> - Why is the closed button in an odd position (see File -> Test Alert
>>>> for comparison)
>>>>
>>> Fixed
>>>
>>>>
>>>> - Why are we using a scrolling list AND pagination? I think a
>>>> scrolling list alone should be fine.
>>>>
>>> Pagination is removed.
>>>
>>>>
>>>> - The grid sizing is wrong. See how the scrollbar on the right in the
>>>> screenshot is off the edge of the dialogue, and there's a horizontal
>>>> scrollbar?
>>>>
>>> Fixed.
>>>
>>>>
>>>> - We shouldn't truncate object names as that can be ambiguous. The
>>>> column should extend as necessary, and there should be a horizontal
>>>> scrollbar on the grid itself (not at the bottom of the dialogue
>>>> content).
>>>>
>>> I have fixed it and scrollbar is now on the grid itself.
>>>
>>>>
>>>> - Function names should include the parameters, as they are part of
>>>> the identifier. Without, it can be ambigous - e.g. do_stuff(int) vs.
>>>> do_stuff(text).
>>>>
>>> Fixed.
>>>
>>>>
>>>> - If I select only functions (for example), the Privileges panel
>>>> should only list privileges available for functions. If I select
>>>> multiple object types, it should show the available options for only
>>>> those object types.
>>>>
>>> Implemented this feature.
>>>
>>>>
>>>> - If I select functions and tables, and then choose (for example),
>>>> usage and truncate, it will attempt to set usage on tables and
>>>> truncate on functions. It should only attempt to set privileges on the
>>>> objects for which they are appropriate.
>>>>
>>> Done
>>>
>>>>
>>>> - On the last page, the Next button is disabled. It is turned a
>>>> marginally darker blue, but also highlights on mouse-over. The change
>>>> in shade is so subtle it's hard to see, and the highlight implies the
>>>> button is active when it isn't.
>>>>
>>> Fixed.
>>>
>>>>
>>>> - The buttons appear to have a smaller corner radius than those on the
>>>> main browser, or in other dialogues.
>>>>
>>> Fixed.
>>>
>>>>
>>>> - Button labels should have an &nbsp; before them to properly space
>>>> the label from the icon (or better yet, this should be done in CSS,
>>>> though that would also need to be done elsewhere).
>>>>
>>> done with css.
>>>
>>>>
>>>> - Why do the URLs have a /wizard prefix? I think that should be removed.
>>>>
>>> Removed prefix.
>>>
>>>>
>>>> - The available privileges for each object type seem to be defined in
>>>> both grant_wizard.js and allowed_acl.json. Can we just use
>>>> allowed_acl.json?
>>>>
>>> Yes, allowed_acl.json is now used in grant_wizard.js.
>>>
>>>>
>>>> Thanks.
>>>>
>>>> --
>>>> Dave Page
>>>> Blog: http://pgsnake.blogspot.com
>>>> Twitter: @pgsnake
>>>>
>>>> EnterpriseDB UK: http://www.enterprisedb.com
>>>> The Enterprise PostgreSQL Company
>>>>
>>>
>>>
>>
>>
>> --
>> Sent via pgadmin-hackers mailing list ([email protected])
>> To make changes to your subscription:
>> http://www.postgresql.org/mailpref/pgadmin-hackers
>>
>>
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [application/octet-stream] grant_wizard_v7.patch (84.3K, 3-grant_wizard_v7.patch)
  download | inline diff:
diff --git a/web/pgadmin/browser/static/css/wizard.css b/web/pgadmin/browser/static/css/wizard.css
new file mode 100644
index 0000000..3f2dcaa
--- /dev/null
+++ b/web/pgadmin/browser/static/css/wizard.css
@@ -0,0 +1,150 @@
+
+/** CSS for Wizard **/
+.pgadmin_grant_wizard_body .ajs-content {
+  padding-top: 0px !important;
+}
+
+/* Wizard Header CSS */
+/*.alertify .ajs-dialog .ajs-close {
+  margin-top: 15px;
+}
+*/
+
+.wizard-header {
+  background: #428bca;
+  padding-left: 15px;
+  padding-bottom: 7px;
+  color: #fff;
+  font-size: 18px;
+  height: 76px;
+  line-height: 76px;
+  border-radius: 6px 6px 0 0;
+}
+
+.wizard-header h3 {
+  font-size: 18px;
+  display: inline-block;
+}
+
+.wizard_dlg {
+  float: left;
+  height: 100%;
+  width: 100%;
+}
+
+.grant_wizard_container {
+	position: relative;
+  overflow: hidden;
+  width: 100%;
+  height: 100%;
+}
+
+.grant_wizard_container .pgadmin-wizard {
+  width: 100%;
+  height: 100%;
+}
+
+.grant_wizard_container .wizard-content {
+  position: relative;
+  padding: 0;
+  height: 64%;
+}
+
+.grant_wizard_container .wizard-right-panel {
+  overflow-y: auto;
+  height: 100%;
+  top: 0;
+  right: 0;
+  position: absolute;
+  bottom: 0;
+}
+
+.grant_wizard_container .wizard-left-panel {
+  position: absolute;
+  top: 0;
+  display: flex;
+  bottom: 0;
+  left: 0;
+  align-items: center;
+  justify-content: center;
+  right: 0;
+}
+
+.grant_wizard_container .wizard-left-panel img {
+  width: 140px;
+}
+
+.grant_wizard_container .wizard-right-panel_content {
+	height: 60%;
+}
+
+.grant_wizard_container {
+  height: 100%;
+}
+
+/* Wizard Footer CSS */
+.grant_wizard_container .footer {
+  position: absolute;
+  background: #fff;
+  border-top: 1px solid #ccc;
+  bottom: 0px;
+  height: 62px;
+  right: 0px;
+  padding-top: 22px;
+  border-radius: 0 0 6px 6px;
+  z-index: 10;
+}
+
+/* Wizard Button CSS */
+.grant_wizard_container .wizard-buttons {
+  float: right;
+}
+
+.grant_wizard_container .wizard-buttons button {
+  float: left;
+  padding: 7px 15.2px;
+  font-size: 14px;
+  margin: 0px 5px 0 0 !important;
+}
+
+.grant_wizard_container .wizard-buttons button.wizard-next i.fa {
+	padding-left: 5px;
+}
+
+.grant_wizard_container .wizard-buttons button.wizard-back i.fa,
+.grant_wizard_container .wizard-buttons button.wizard-cancel i.fa {
+	padding-right: 5px;
+}
+
+.grant_wizard_container .wizard-buttons .wizard-finish {
+  margin-right: 0 !important;
+}
+
+/* Wizard Status bar CSS */
+.grant_wizard_container .wizard-description {
+  padding: 1.7em 0.1em;
+}
+
+/* Error message css */
+.grant_wizard_container .error_msg_div {
+  display: block;
+  position: absolute;
+  bottom: 55px;
+  background: #fff;
+}
+
+.grant_wizard_container .error_msg_div p {
+  background: #fff;
+  color: #b92c28;
+}
+
+/* In wizard select2 dropdown doesn't
+ * popup because z-index of alertify
+ * wizard is greater than the z-index
+ * of select2 dropdown. To make select2
+ * visible, set z-index of select2
+ * higher value than wizard's
+ */
+.select2-container--open {
+  z-index: 10000;
+}
diff --git a/web/pgadmin/browser/static/js/node.ui.js b/web/pgadmin/browser/static/js/node.ui.js
index 4d69e1f..de3f577 100644
--- a/web/pgadmin/browser/static/js/node.ui.js
+++ b/web/pgadmin/browser/static/js/node.ui.js
@@ -279,7 +279,8 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
         return false;
       };
 
-      while(p) {
+      // check if p is not empty
+      while(p && p.length > 0) {
         top = p.get(0).offsetTop + p.height();
         p = p.parent();
         if (hasScrollbar(p)) {
diff --git a/web/pgadmin/browser/static/js/wizard.js b/web/pgadmin/browser/static/js/wizard.js
new file mode 100644
index 0000000..383a312
--- /dev/null
+++ b/web/pgadmin/browser/static/js/wizard.js
@@ -0,0 +1,233 @@
+define(
+    ['underscore', 'backbone', 'pgadmin', 'pgadmin.browser'],
+function(_, Backbone, pgAdmin, pgBrowser) {
+
+  pgBrowser = pgBrowser || pgAdmin.Browser || {};
+
+  /* Wizard individual Page Model */
+  var WizardPage = pgBrowser.WizardPage = Backbone.Model.extend({
+    defaults: {
+      id: undefined, /* Id */
+      page_title: undefined, /* Page Title */
+      view: undefined, /* A Backbone View */
+      html: undefined, /* HTML tags to be rendered */
+      image: undefined, /* Left hand side image */
+      disable_prev: false, /*  Previous Button Flag */
+      disable_next: false, /*  Next Button Flag */
+      disable_cancel: false, /* Cancel Button Flag */
+      show_progress_bar: '',
+      /* Callback for OnLoad */
+      onLoad: function() {
+        return true;
+      },
+      /* Callback for before Next */
+      beforeNext: function() {
+        return true;
+      },
+      onNext: function(){},
+      onBefore: function() {},
+      /* Callback for before Previous */
+      beforePrev: function() {
+        return true;
+      }
+    }
+  });
+
+  var Wizard = pgBrowser.Wizard = Backbone.View.extend({
+    options: {
+      title: 'Wizard', /* Main Wizard Title */
+      image: 'left_panel.png', /* TODO:: We can use default image here */
+      curr_page: 0, /* Current Page to Load */
+      disable_next: false,
+      disable_prev: false,
+      disable_finish: false,
+      disable_cancel: false,
+      height: 400,
+      width: 650,
+      show_left_panel: true
+    },
+    tmpl: _.template(
+       "    <div class='pgadmin-wizard' style='height: <%= this.options.height %>px;"
+       + "    width: <%= this.options.width %>px'>"
+       + "      <div class='wizard-header wizard-badge'>"
+       + "        <div class='row'>"
+       + "          <div class='col-sm-9'>"
+       + "              <h3><span id='main-title'><%= this.options.title %></span> -"
+       + "              <span id='step-title'><%= page_title %></span></h3>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "      <div class='wizard-content col-sm-12'>"
+       + "        <% if(this.options.show_left_panel) { %>"
+       + "          <div class='col-sm-3 wizard-left-panel'>"
+       + "              <img src='<%= this.options.image %>'></div>"
+       + "        <% } %>"
+       + "        <div class='col-sm-<% if(this.options.show_left_panel){ %>9<% }"
+       + "          else { %>12<% } %> wizard-right-panel'>"
+       + "          <% if( typeof show_description !=  'undefined'){ %>"
+       + "            <div class='wizard-description'>"
+       + "              <%= show_description %>"
+       + "            </div>"
+       + "          <% } %>"
+       + "          <div class='wizard-right-panel_content'>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "      <div class='col-sm-12 error_msg_div'>"
+       + "       <p></p>"
+       + "      </div>"
+       + "      <div class='footer col-sm-12'>"
+       + "        <div class='row'>"
+       + "          <div class='col-sm-4 wizard-progress-bar'>"
+       + "             <p><% if(show_progress_bar){ %><%= show_progress_bar %><% } %></p>"
+       + "          </div>"
+       + "          <div class='col-sm-8'>"
+       + "            <div class='wizard-buttons'>"
+       + "              <button class='btn btn-primary wizard-back' <%=this.options.disable_prev ? 'disabled' : ''%>>"
+       + "                <i class='fa fa-backward'></i>Back</button>"
+       + "              <button class='btn btn-primary wizard-next' <%=this.options.disable_next ? 'disabled' : ''%>>Next"
+       + "                <i class='fa fa-forward'></i></button>"
+       + "              <button class='btn btn-danger wizard-cancel' <%=this.options.disable_cancel ? 'disabled' : ''%>>"
+       + "                <i class='fa fa-lg fa-close'></i>Cancel</button>"
+       + "              <button class='btn btn-primary wizard-finish' <%=this.options.disable_finish ? 'disabled' : ''%>>"
+       + "                Finish</button>"
+       + "            </div>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "    </div>"),
+    events: {
+      "click button.wizard-next" : "nextPage",
+      "click button.wizard-back" : "prevPage",
+      "click button.wizard-cancel" : "onCancel",
+      "click button.wizard-finish" : "finishWizard",
+    },
+    initialize: function(options) {
+      this.options = _.extend({}, this.options, options.options);
+      this.currPage = this.collection.at(this.options.curr_page).toJSON();
+    },
+    render: function() {
+      var data = this.currPage;
+
+      /* Check Status of the buttons */
+      this.options.disable_next = (this.options.disable_next ? true : this.evalASFunc(this.currPage.disable_next));
+      this.options.disable_prev = (this.options.disable_prev ? true : this.evalASFunc(this.currPage.disable_prev));
+      this.options.disable_cancel = (this.currPage.canCancel ? true : this.evalASFunc(this.currPage.disable_cancel));
+
+      that = this;
+
+      /* HTML Content */
+      if (data.html) { data.content = data.html; }
+      /* Backbone View */
+      else if (data.view) { data.content = data.view.render().el;}
+
+      $(this.el).html(this.tmpl(data));
+      $(this.el).find(".wizard-right-panel_content").html(data.content);
+
+      /* OnLoad Callback */
+      this.onLoad();
+
+      return this;
+    },
+    nextPage: function() {
+      this.options.curr_page.el = this.$el;
+      if (!this.beforeNext()) { return false; }
+
+      page_id = this.onNext();
+
+      if (page_id ) {
+        this.currPage = this.collection.get(page_id).toJSON();
+        this.options.curr_page = this.collection.indexOf(this.collection.get(page_id));
+      }
+      else if (this.options.curr_page < (this.collection.length-1)) {
+        this.options.curr_page = this.options.curr_page + 1;
+        this.currPage = this.collection.at(this.options.curr_page).toJSON();
+      }
+
+      this.enableDisableNext();
+      this.enableDisablePrev();
+
+      return this.render();
+    },
+    prevPage: function() {
+      if (!this.beforePrev()) { return false; }
+
+      page_id = this.onPrev();
+
+      if (page_id){
+        this.currPage = this.collection.get(page_id).toJSON();
+        this.options.curr_page = this.collection.indexOf(this.collection.get(page_id));
+      }
+      else if (this.options.curr_page > 0) {
+        this.options.curr_page = this.options.curr_page - 1;
+        this.currPage = this.collection.at(this.options.curr_page).toJSON();
+      }
+
+      this.enableDisableNext();
+      this.enableDisablePrev();
+
+      return this.render();
+    },
+    finishWizard: function() {
+      this.onFinish();
+      this.remove(); // Remove view from DOM
+      this.unbind(); // Unbind all local event bindings
+      delete this.$el; // Delete the jQuery wrapped object variable
+      delete this.el; // Delete the variable reference to this node
+      return true;
+    },
+    enableDisableNext: function(disable) {
+        if (typeof(disable) != 'undefined') {
+          this.options.disable_next = disable;
+        }
+        else if (this.options.curr_page >= (this.collection.length-1)) {
+          this.options.disable_next = true;
+        }
+        else {
+          this.options.disable_next = false;
+        }
+    },
+    enableDisablePrev: function(disable) {
+        if (typeof(disable) != 'undefined') {
+          this.options.disable_prev = disable;
+        }
+        else if (this.options.curr_page <= 0) {
+          this.options.disable_prev = true;
+        }
+        else {
+          this.options.disable_prev = false;
+        }
+    },
+    beforeNext: function(){
+      return this.evalASFunc(this.currPage.beforeNext);
+    },
+    beforePrev: function(){
+      return this.evalASFunc(this.currPage.beforePrev);
+    },
+    onPrev: function(){
+      return this.evalASFunc(this.currPage.onPrev);
+    },
+    onNext: function(){
+      return this.evalASFunc(this.currPage.onNext);
+    },
+    onLoad: function() {
+      return this.evalASFunc(this.currPage.onLoad);
+    },
+    onFinish: function() {
+      return true;
+    },
+    onCancel: function() {
+      this.$el.remove();
+      return true;
+    },
+    evalASFunc: function(func, ctx) {
+      var self = this;
+      ctx = ctx || self.currPage;
+
+      return (_.isFunction(func) ? func.apply(ctx, [self]) : func);
+    }
+  });
+
+  return pgBrowser;
+
+});
diff --git a/web/pgadmin/tools/grant_wizard/__init__.py b/web/pgadmin/tools/grant_wizard/__init__.py
new file mode 100644
index 0000000..c71818e
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/__init__.py
@@ -0,0 +1,386 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Implements Grant Wizard"""
+
+import json
+from flask import render_template, request, current_app
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_response as ajax_response, \
+    make_json_response, internal_server_error
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+# from pgadmin.browser.server_groups.servers.utils import parse_priv_to_db
+from pgadmin.utils import PgAdminModule
+from flask import Response, url_for
+from flask.ext.security import login_required
+from urllib import unquote
+
+# set template path for sql scripts
+template_path = 'grant_wizard/sql'
+MODULE_NAME = 'grant_wizard'
+
+
+class GrantWizardModule(PgAdminModule):
+    """
+    class GrantWizardModule(Object):
+
+        It is a wizard which inherits PgAdminModule
+        class and define methods to load its own
+        javascript file.
+    """
+    def get_own_stylesheets(self):
+        """
+        Returns:
+            list: the stylesheets used by this module.
+        """
+        stylesheets = [
+            url_for('browser.static', filename='css/wizard.css'),
+            url_for('grant_wizard.static', filename='css/grant_wizard.css')
+            ]
+        return stylesheets
+
+    def get_own_javascripts(self):
+        """"
+        Returns:
+            list: js files used by this module
+        """
+        scripts = []
+        scripts.append({
+            'name': 'pgadmin.tools.grant_wizard',
+            'path': url_for('grant_wizard.index') + 'grant_wizard',
+            'when': None
+        })
+        scripts.append({
+            'name': 'pgadmin.browser.wizard',
+            'path': url_for('browser.static', filename='js/wizard'),
+            'when': None
+        })
+        return scripts
+
+# Create blueprint for GrantWizardModule class
+blueprint = GrantWizardModule(
+    MODULE_NAME, __name__, static_url_path='')
+
+
[email protected]("/")
+@login_required
+def index():
+    pass
+
+
[email protected]("/grant_wizard.js")
+@login_required
+def script():
+    """render own javascript"""
+    return Response(response=render_template(
+                "grant_wizard/js/grant_wizard.js", _=gettext),
+                status=200,
+                mimetype="application/javascript")
+
+
[email protected]("/allowed_acl.json")
+@login_required
+def acl_list():
+    """render list of acls"""
+    return Response(response=render_template(
+                "grant_wizard/sql/allowed_acl.json", _=gettext),
+                status=200,
+                mimetype="application/javascript")
+
+
[email protected](
+    '/properties/<int:gid>/<int:sid>/<int:did>'
+    '/<int:node_id>/<node_name>/<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+@login_required
+def properties(gid, sid, did, node_id, node_name, node_type, nspname):
+    """It fetches the properties of object types
+       and render into selection page of wizard
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+    nspname = unquote(nspname)
+
+    res_data = []
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    # Fetch functions against schema
+    if node_type in ['schema']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type=node_type, nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch trigger functions
+    if node_type in ['schema', 'trigger_function']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='trigger_function', nspname=nspname)
+        status, res = conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch Sequences against schema
+    if node_type in ['schema', 'sequence']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='sequence', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+        res_data.extend(res['rows'])
+
+    # Fetch Tables against schema
+    if node_type in ['schema', 'table']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='table', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch Views against schema
+    if node_type in ['schema', 'view']:
+        SQL = render_template("/".join(
+            [template_path, 'properties.sql']),
+            node_id=node_id, node_name=node_name,
+            type='view', nspname=nspname)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    return ajax_response(
+            response=res_data,
+            status=200
+            )
+
+
+def parse_priv_to_db(str_privileges, acls):
+    """
+    Common utility function to parse privileges before sending to database.
+    """
+    db_privileges = {
+        'c': 'CONNECT',
+        'C': 'CREATE',
+        'T': 'TEMPORARY',
+        'a': 'INSERT',
+        'r': 'SELECT',
+        'w': 'UPDATE',
+        'd': 'DELETE',
+        'D': 'TRUNCATE',
+        'x': 'REFERENCES',
+        't': 'TRIGGER',
+        'U': 'USAGE',
+        'X': 'EXECUTE'
+        }
+
+    privileges = []
+
+    for priv in str_privileges:
+        priv_with_grant = {}
+        priv_without_grant = {}
+        priv_function_with_grant = []
+        priv_function_without_grant = []
+        priv_sequence_with_grant = []
+        priv_sequence_without_grant = []
+        priv_table_with_grant = []
+        priv_table_without_grant = []
+
+        # import pdb
+        # pdb.set_trace()
+        for privilege in priv['privileges']:
+
+            if privilege['privilege_type'] not in db_privileges:
+                continue
+
+            # Extract privileges applicable to function
+            if privilege['privilege_type'] in acls['function']['acl']:
+                if privilege['with_grant']:
+                    priv_function_with_grant.append(
+                        db_privileges[privilege['privilege_type']])
+                elif privilege['privilege']:
+                    priv_function_without_grant.append(
+                        db_privileges[privilege['privilege_type']])
+
+            # Extract privileges applicable to sequence
+            if privilege['privilege_type'] in acls['sequence']['acl']:
+                if privilege['with_grant']:
+                    priv_sequence_with_grant.append(
+                        db_privileges[privilege['privilege_type']])
+                elif privilege['privilege']:
+                    priv_sequence_without_grant.append(
+                        db_privileges[privilege['privilege_type']])
+
+            # Extract privileges applicable to view/table
+            if (privilege['privilege_type'] in acls['table']['acl']):
+                if privilege['with_grant']:
+                    priv_table_with_grant.append(
+                        db_privileges[privilege['privilege_type']])
+                elif privilege['privilege']:
+                    priv_table_without_grant.append(
+                        db_privileges[privilege['privilege_type']])
+
+        # If we have all acl then just return all
+        if len(priv_table_with_grant) == len(acls['table']['acl']):
+            priv_table_with_grant = ['ALL']
+
+        if len(priv_table_without_grant) == len(acls['table']['acl']):
+            priv_table_without_grant = ['ALL']
+
+        # Appending and returning all ACL
+        priv_with_grant['function'] = priv_function_with_grant
+        priv_without_grant['function'] = priv_function_without_grant
+
+        priv_with_grant['sequence'] = priv_sequence_with_grant
+        priv_without_grant['sequence'] = priv_sequence_without_grant
+
+        priv_with_grant['table'] = priv_table_with_grant
+        priv_without_grant['table'] = priv_table_without_grant
+
+        privileges.append({
+            'grantee': priv['grantee'],
+            'with_grant': priv_with_grant,
+            'without_grant': priv_without_grant
+            })
+
+    return privileges
+
+
[email protected](
+    '/msql/<int:gid>/<int:sid>/<int:did>/<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+def msql(gid, sid, did, node_type, nspname):
+    """
+    This function will return modified SQL
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+    nspname = unquote(nspname)
+
+    data = {}
+    for k, v in request.args.items():
+        try:
+            data[k] = json.loads(v)
+        except ValueError:
+            data[k] = v
+
+    # Form db connection
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    acls = []
+    try:
+        acls = render_template(
+            "/".join([template_path, 'allowed_acl.json'])
+            )
+        acls = json.loads(acls)
+    except Exception as e:
+        current_app.logger.exception(e)
+
+    try:
+
+        # Parse privileges
+        if 'acl' in data:
+            data['acl'] = parse_priv_to_db(data['acl'], acls)
+
+        # Pass database objects and get SQL for privileges
+        SQL = render_template("/".join(
+            [template_path, 'grant.sql']),
+            data=data, nspname=nspname, conn=conn)
+
+        res = {'data': SQL}
+
+        return ajax_response(
+            response=res,
+            status=200
+            )
+
+    except Exception as e:
+        return make_json_response(
+            status=410,
+            success=0,
+            errormsg=e.message
+        )
+
+
[email protected](
+    '/save/<int:gid>/<int:sid>/<int:did>/'
+    '<node_type>/<nspname>/',
+    methods=('GET', 'POST'))
+def save(gid, sid, did, node_type, nspname):
+    """
+    This function will apply the privileges to the selected
+    Database Objects
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+    nspname = unquote(nspname)
+
+    data = request.form if request.form else json.loads(request.data.decode())
+
+    # Form db connection and we use conn to execute sql
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    acls = []
+    try:
+        acls = render_template(
+            "/".join([template_path, 'allowed_acl.json'])
+            )
+        acls = json.loads(acls)
+    except Exception as e:
+        current_app.logger.exception(e)
+
+    try:
+
+        # Parse privileges
+        if 'acl' in data:
+            data['acl'] = parse_priv_to_db(data['acl'], acls)
+
+        # Pass database objects and get SQL for privileges
+        SQL = render_template("/".join(
+            [template_path, 'grant.sql']),
+            data=data, nspname=nspname, conn=conn)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        return make_json_response(
+            success=1,
+            info="Privileges Applied"
+        )
+
+    except Exception as e:
+        return internal_server_error(errormsg=e.message)
diff --git a/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
new file mode 100644
index 0000000..46c9203
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
@@ -0,0 +1,86 @@
+/** Grant Wizard CSS **/
+
+/**
+  CSS to make db object type table
+  fixed so that tbody content may
+  scroll
+ */
+.object_type_table thead tr {
+  position: relative;
+  display: block;
+}
+
+.object_type_table {
+	display: inline-block;
+  height: 100%;
+  border: 0 !important;
+}
+
+.object_type_table tbody {
+  display: block;
+  overflow: scroll;
+  border: 1px solid #ddd;
+  width: 100%;
+  min-height: 100%;
+  max-height: 66%;
+  height: 66%;
+}
+
+.object_type_table tbody tr td {
+	background-position: 4px 4px;
+	border-radius: 0;
+}
+
+.object_type_table tbody tr td:nth-child(1),
+.object_type_table thead tr th:nth-child(1) {
+  width: 28px;
+  min-width: 28px;
+}
+
+.object_type_table tbody tr td:nth-child(2),
+.object_type_table thead tr th:nth-child(2) {
+  width: 185px;
+  min-width: 185px;
+}
+
+.object_type_table tbody tr td:nth-child(3),
+.object_type_table thead tr th:nth-child(3) {
+  width: 110px;
+  min-width: 110px;
+}
+
+.object_type_table thead tr th:nth-child(4) {
+	width: 244px;
+}
+
+.object_type_table tbody tr td:nth-child(4) {
+  width: 244px;
+  min-width: 228px;
+  max-width: 100%;
+}
+
+/** Override Backgrid filter CSS **/
+.backgrid-filter.form-search {
+  float: left;
+  margin: 0px 5px 5px 0;
+}
+
+/** Custom styling for Codemirror field **/
+.wizard-right-panel_content {
+  border: 1px solide #ccc;
+}
+
+.wizard-right-panel_content .CodeMirror {
+  border: 1px solid #ccc;
+  height: 285px !important;
+  min-height: 285px !important;
+}
+
+.wizard-right-panel_content .CodeMirror-linenumber {
+  background: #F7F7F7;
+  border-right: 1px solid #DDDDDD;
+}
+
+.wizard-right-panel_content .CodeMirror-gutters {
+  min-height: 285px !important;
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
new file mode 100644
index 0000000..8f4a39f
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
@@ -0,0 +1,1101 @@
+define([
+      'jquery', 'underscore', 'underscore.string', 'alertify',
+      'pgadmin.browser', 'backbone', 'backgrid', 'pgadmin.browser.node',
+      'backgrid.select.all', 'backgrid.filter', 'pgadmin.browser.server.privilege',
+      'pgadmin.browser.wizard',
+      ],
+
+  // This defines Grant Wizard dialog
+  function($, _, S, alertify, pgBrowser, Backbone, Backgrid, pgNode) {
+
+    // if module is already initialized, refer to that.
+    if (pgBrowser.GrantWizard) {
+      return pgBrowser.GrantWizard;
+    }
+
+    /**
+      It is sub model for field "Objects". It has fields
+      for database object types such as Schemas, Views and
+      Sequence etc.
+    */
+    var DatabaseObjectModel = pgNode.Model.extend({
+      defaults: {
+        selected: false,
+        icon: 'icon-unknown',
+        name: undefined,
+        name_with_args: undefined,
+        nspname: undefined,
+        proargs: undefined,
+        object_type: undefined,
+        object_id: undefined
+      },
+      idAttribute: 'object_id', // to uniquely identify a model object
+      toJSON: function(obj) {
+        var d = pgNode.Model.prototype.toJSON.apply(this);
+        delete d.icon;
+        return d;
+      },
+      parse: function(res) {
+
+        // Create unique object id
+        res.object_id = res.name_with_args;
+
+        // create name with args if its object is function
+        if(!_.isUndefined(res.object_type) && (res.object_type == 'Function' ||
+          res.object_type == 'Trigger Function'))
+          res.name_with_args = res.name+'('+(typeof(res.proargs) != 'undefined' ? res.proargs : '')+')';
+        else
+          res.name_with_args = res.name;
+
+        return res;
+      },
+
+      validate: function() {
+
+        /*
+        * Triggers error messages for object types "selected"
+        * if it is empty/undefined/null
+        */
+        var err = {},
+          errmsg,
+          node = this.get('objects').toJSON();
+        if (_.isEmpty(node)) {
+          err['selected'] = '{{ _("Please select any database object type") }}';
+          errmsg = errmsg || err['selected'];
+          this.errorModel.set('selected', errmsg);
+          return errmsg;
+        } else {
+          this.errorModel.unset('selected');
+        }
+        return null;
+      }
+    });
+
+    // Define columns for the Db Object Types grid
+    var columns = [{
+      name: "selected",
+
+      /*
+      Override render method of Backgrid.Extension.SelectRowCell
+      class. It has an issue: It doesn't mark rows checked if we move to next
+      page and then go back to previous page. but it must show.
+      so we handle this case by overriding the render method.
+      */
+      cell: Backgrid.Extension.SelectRowCell.extend({
+        render: function() {
+
+          // Use the Backform Control's render function
+          Backgrid.Extension.SelectRowCell.prototype.render.apply(this, arguments);
+
+          var col = this.column.get('name');
+          if (this.model && this.model.has(col)) {
+            if (this.model.get(col)) {
+              this.checkbox().prop("checked", true);
+              this.$el.parent().toggleClass("selected", true);
+              this.model.trigger("backgrid:selected", this.model, true);
+            }
+          }
+          return this;
+        }
+      }),
+
+      headerCell: "select-all",
+
+    },{
+      name: "object_type",
+      label: "Object Type",
+      editable: false,
+      cell: Backgrid.Cell.extend({
+        render: function() {
+
+          // Override render to add icon to Db Object column
+          Backgrid.Cell.prototype.render.apply(this, arguments);
+          this.$el.addClass(this.model.get('icon')).css({"padding-left": "24px"});
+
+          return this;
+        }
+      })
+    },{
+      name: "nspname",
+      label: "Schema",
+      cell: "string",
+      editable: false
+    },{
+      name: "name_with_args",
+      label: "Name",
+      cell: "string",
+      editable: false
+    }];
+
+    // Create an Object GrantWizard of pgBrowser class
+    pgBrowser.GrantWizard  = {
+      init: function() {
+        if (this.initialized)
+          return;
+
+        this.initialized = true;
+
+        // Define list of nodes on which grant wizard context menu option appears
+        var supported_nodes = [
+              'schema', 'coll-function',
+              'coll-sequence', 'coll-table',
+              'coll-view', 'coll-materialized_view'
+            ],
+
+            /**
+              Enable/disable grantwizard menu in tools based
+              on node selected
+              if selected node is present in supported_nodes,
+              menu will be enabled otherwise disabled.
+              Also, hide it for system view in catalogs
+            */
+            menu_enabled = function(itemData, item, data) {
+              var t = pgBrowser.tree, i = item, d = itemData;
+              var parent_item = t.hasParent(i) ? t.parent(i): null,
+                  parent_data = parent_item ? t.itemData(parent_item) : null;
+              if(!_.isUndefined(d) && !_.isNull(d) && !_.isNull(parent_data))
+                return ((_.indexOf(supported_nodes, d._type) !== -1 && parent_data._type != 'catalog') ? true: false);
+              else
+                return false;
+            };
+
+        // Define the nodes on which the menus to be appear
+        var menus = [{
+          name: 'grant_wizard_schema', module: this,
+          applies: ['tools'], callback: 'start_grant_wizard',
+          priority: 10, label: '{{_("Grant Wizard...") }}',
+          icon: 'wcTabIcon', enable: menu_enabled
+        }];
+
+        // Add supported menus into the menus list
+        for (var idx = 0; idx < supported_nodes.length; idx++) {
+          menus.push({
+            name: 'grant_wizard_schema_context_' + supported_nodes[idx],
+            node: supported_nodes[idx], module: this,
+            applies: ['context'], callback: 'start_grant_wizard',
+            priority: 10, label: '{{_("Grant Wizard...") }}',
+            icon: 'wcTabIcon', enable: menu_enabled
+            });
+        }
+        pgAdmin.Browser.add_menus(menus);
+
+        return this;
+      },
+
+      // Callback to draw Wizard Dialog
+      start_grant_wizard: function(action, item) {
+
+        // Declare Wizard dialog
+        if (!alertify.wizardDialog) {
+          alertify.dialog('wizardDialog', function factory() {
+
+            // Generate wizard main container
+            var $container = $("<div class='wizard_dlg'></div>");
+
+            return {
+              main: function(title) {
+                this.set('title', title);
+              },
+              setup:function() {
+                return {
+
+                  // Set options for dialog
+                  options: {
+                    frameless: true,
+                    resizable: true,
+                    autoReset: false,
+                    maximizable: false,
+                    closableByDimmer: false
+                  }
+                };
+              },
+              hooks:{
+                onshow: function() {
+
+                  // Set dimensions for wizard
+                  this.elements.dialog.style.width = '100%';
+                  this.elements.dialog.style.height = '68%';
+
+                  // Add pgadmin_grant_wizard_body class to dialog
+                  $(this.elements.body).addClass('pgadmin_grant_wizard_body');
+                },
+              },
+
+              /**
+                Returns a Paginator Class Object which is again to be rendered
+
+                @class {Backgrid.Extension.Paginator}
+                @param {Backbone.Collection} coll - from which data is fetched
+                @return {Object} paginator
+              */
+              DbPaginator: function(coll){
+                var paginator = this.paginator = new Backgrid.Extension.Paginator({
+                  collection: coll,
+                  windowSize: 8
+                });
+                return paginator;
+              },
+
+              /**
+                Create new Filter which will filter the
+                rendered grid for Select Type Tabular Data
+                @param {Backbone.PageableCollection} coll
+              */
+              DbObjectFilter: function(coll){
+                var clientSideFilter = this.clientSideFilter = new Backgrid.Extension.ClientSideFilter({
+                  collection: coll,
+                  placeholder: _('Search by object type or name'),
+
+                  // The model fields to search for matches
+                  fields: ['object_type', 'name'],
+
+                  // How long to wait after typing has stopped before searching can start
+                  wait: 150
+                });
+                return clientSideFilter;
+              },
+
+              //Enable Disable Next button of PrivilegePage
+              updateButtons: function(modified){
+                if(!modified)
+                  $('.wizard-next').prop('disabled', true);
+                else
+                  $('.wizard-next').prop('disabled', false);
+              },
+
+              /**
+                Callback called when an errorModel is set
+                with invalid value and errormsg is set into
+                status bar element and next button is disabled
+              */
+              onSessionInvalid: function(msg) {
+                $('.error_msg_div p').html(msg).removeClass("hide");
+
+                // Enable disable Next button
+                this.updateButtons(false);
+                return true;
+              },
+
+              /**
+                Callback called when anything is set into model
+                thus hide error msg element and enable next button
+                status bar element and next button is disabled
+              */
+              onSessionValidated: function(sessHasChanged) {
+                $('.error_msg_div p').empty().addClass("hide");
+
+                // Enable disable Next button
+                this.updateButtons(sessHasChanged);
+              },
+
+              /**
+                Remove/Delete objects, attributes
+                in wizard on wizard close or finish
+                to reclaim memory
+              */
+              releaseObjects: function(){
+                var self = this;
+
+                if(!_.isUndefined(self.dbObjectFilter)) {
+                  self.dbObjectFilter.remove();
+                  self.dbObjectFilter = undefined;
+                }
+
+                if(!_.isUndefined(self.clientSideFilter)) {
+                  self.clientSideFilter.remove();
+                  self.clientSideFilter = undefined;
+                }
+
+                // clear object priv array
+                if(!_.isNull(self.obj_priv) &&
+                  !_.isUndefined(self.obj_priv)) {
+                  self.obj_priv = [];
+                  delete self.obj_priv;
+                }
+
+                // Delete Wizard Pages, clear model and cleanup view
+                if(!_.isUndefined(self.dbObjectTypePage) &&
+                  !_.isNull(self.dbObjectTypePage)) {
+                  if(!_.isUndefined(self.dbObjectTypePage.get('model')) &&
+                    !_.isNull(self.dbObjectTypePage.get('model'))) {
+                    self.dbObjectTypePage.get('model').clear();
+                    self.dbObjectTypePage.get('view').cleanup();
+                    self.dbObjectTypePage = undefined;
+                  }
+                }
+
+                if(!_.isUndefined(self.privilegePage) &&
+                  !_.isNull(self.privilegePage)) {
+                  if(!_.isUndefined(self.privilegePage.get('model')) &&
+                    !_.isNull(self.privilegePage.get('model'))) {
+                    self.privilegePage.get('model').clear();
+                    self.privilegePage.get('view').cleanup();
+                    self.privilegePage = undefined;
+                  }
+                }
+
+                if(!_.isUndefined(self.reviewSQLPage) &&
+                  !_.isNull(self.reviewSQLPage)) {
+                  if(!_.isUndefined(self.reviewSQLPage.get('model')) &&
+                    !_.isNull(self.reviewSQLPage.get('model'))) {
+                    self.reviewSQLPage.get('model').clear();
+                    self.reviewSQLPage = undefined;
+                  }
+                }
+
+                // Remove Sql control
+                if (!_.isUndefined(self.sqlControl)) {
+                  self.sqlControl.remove();
+                }
+
+                // Clear privModel
+                if(!_.isNull(self.privModel) &&
+                  !_.isUndefined(self.privModel)) {
+                  self.privModel.clear();
+                }
+
+                // Remove collection containing db object data
+                if(!_.isNull(self.coll) &&
+                  !_.isUndefined(self.coll)) {
+                  self.coll.reset();
+                  self.coll = undefined;
+                }
+                // Delete Wizard
+                if(!_.isNull(self.wizard) &&
+                  !_.isUndefined(self.wizard)) {
+                  self.wizard.collection.reset();
+                  self.wizard.curr_page = undefined;
+                }
+
+              },
+
+              /**
+                Every time a wizard is opened, this function
+                is called everytime. It has Wizard Pages which
+                are rendered by the Wizard Class:
+
+                @class {pgBrowser.WizardPage} dbObjectType1 - This page
+                @extends {Backbone.Model}
+                renders a grid of Database Object Types such as
+                  Schemas, Views and Sequences etc.
+
+                @class {pgBrowser.WizardPage} WizardPage2 - This page
+                @extends {Backbone.Model}
+                adds Privilege Control which provides grant privileges
+                such as "Create, Insert, Delete, Update" so on the
+                database objects selected on Wizard Pages.
+
+                @class {pgBrowser.WizardPage} WizardPage3 - This page
+                displays the generated GRANT SQL query for the Db
+                objects selected with the specific privileges added to it.
+                @extends {Backbone.Model}
+
+                @class {Backbone.Collection} WizardCollection - It is the
+                collection of wizard pages
+
+                @class {pgBrowser.Wizard} wizard - Its task is:
+                - Create a Wizard
+                - Add Buttons, Callbacks to it.
+                - Render WizardPages
+                @extends {Backbone.View}
+
+              */
+              build: function() {
+                this.elements.content.appendChild($container.get(0));
+              },
+
+              //Returns list of Acls defined for nodes
+              get_json_data: function(gid, sid, did) {
+                var url = "{{ url_for('grant_wizard.index') }}" + "acl/" +
+                    S('%s/%s/%s/').sprintf(
+                        encodeURI(gid), encodeURI(sid), encodeURI(did)).value();
+                // var url = "/grant_wizard/acl.json";
+                return $.ajax({
+                  async: false,
+                  url: url,
+                  dataType: 'jsonp'
+                });
+
+              },
+              prepare:function() {
+
+                $container.empty().append("<div class='grant_wizard_container'></div>");
+
+                // Define el for wizard view
+                var el = $('.grant_wizard_container');
+
+                // Extract the data from the selected tree node
+                var t = pgBrowser.tree,
+                    i = t.selected(),
+                    d = this.d = i && i.length == 1 ? t.itemData(i) : undefined,
+                    info = this.info = pgBrowser.Node.getTreeNodeHierarchy(i),
+                    icon = d.icon;
+
+                /**
+                  Generate a URL using:
+                  gid, did, sid(server id), node_id(node id),
+                  node_(node name), node_type(node type)
+                  and pass it to collection which will fetch Object Type properties.
+                */
+                var gid = info['server-group']._id,
+                    sid = info.server._id,
+                    did = info.database._id,
+                    node_id = d._id,
+                    nspname = info.schema.label,
+
+                    /**
+                      get node name only. used in mapping with object types defined
+                      in allowed_acl.json
+                     */
+                    node_type = d._type.replace('coll-', '').replace('materialized_', ''),
+                    node_label = d.label;
+
+                // Fetch privileges specific to nodes
+                var json_data = this.get_json_data(gid, sid, did);
+                var privDict = JSON.parse(json_data.responseText);
+
+                // Collection url to fetch database object types for objects field
+                var baseUrl = "{{ url_for('grant_wizard.index') }}" + "properties/" +
+                    S('%s/%s/%s/%s/%s/').sprintf(
+                        encodeURI(gid), encodeURI(sid), encodeURI(did),
+                        encodeURI(node_id), encodeURI(node_type)).value();
+
+                    // Model's save url
+                    saveUrl = "{{ url_for('grant_wizard.index') }}" + "save/" +
+                        S('%s/%s/%s/').sprintf(
+                            encodeURI(gid), encodeURI(sid),
+                            encodeURI(did)).value(),
+
+                    // generate encoded url based on wizard type
+                    msql_url = this.msql_url = "/grant_wizard/msql/"+
+                      S('%s/%s/%s/').sprintf(
+                          encodeURI(gid), encodeURI(sid),
+                          encodeURI(did)).value(),
+
+                    Coll = Backbone.Collection.extend({
+                      model: DatabaseObjectModel.extend({
+                        defaults: _.extend({}, DatabaseObjectModel.prototype.defaults,
+                              {nspname: nspname}
+                        )
+                      }),
+                      url: baseUrl
+                    }),
+
+                    // Create instances of collection and filter
+                    coll = this.coll = new Coll(),
+                    dbObjectFilter = this.dbObjectFilter = this.DbObjectFilter(coll);
+
+                /**
+                  privArray holds objects selected which further helps
+                  in creating privileges Model
+                */
+                var self = this;
+                self.privArray = [];
+
+                /**
+                  Override backgrid listener "backgrid:selected" to
+                  Add/Remove model to/from objects collection
+                */
+                coll.on('backgrid:selected', function(model, selected) {
+                  model.set('selected', selected);
+
+                  var object_type = model.get('object_type');
+                  switch (object_type)
+                  {
+                    case 'Function':
+                      object_type = 'function';
+                      break;
+                    case 'Trigger Function':
+                      object_type = 'function';
+                      break;
+                    case 'Table':
+                      object_type = 'table';
+                      break;
+                    case 'Sequence':
+                      object_type = 'sequence';
+                      break;
+                    case 'View':
+                      object_type = 'table';
+                      break;
+                  }
+
+                  /**
+                    if a row (checkbox) is checked, add that model
+                    into collection, when unchecked remove it from
+                    model.
+
+                    Also push/pop object type in/from privArray
+                  */
+                  if(selected) {
+                    if(_.indexOf(self.privArray, object_type) == -1)
+                      self.privArray.push(object_type);
+                    newModel.get('objects').add(model, { silent: true });
+                  }
+                  else {
+                    var idx = self.privArray.indexOf(object_type);
+                    if(idx !=-1)
+                      self.privArray.splice(idx, 1);
+                    newModel.get('objects').remove(model);
+                  }
+
+                  // validate model on checkbox check/uncheck
+                  var msg =  model.validate.call(newModel);
+
+                  /**
+                    If no object type is selected, set error msg
+                    and disable next button, else enable next button
+                  */
+                  if(msg)
+                    self.onSessionInvalid.call(self, msg);
+                  else
+                    self.onSessionValidated.call(self, true);
+                });
+
+                /**
+                  It is the main model with schema defined
+                  Every time a new wizard is opened,
+                  a new model should create.
+                */
+                var GrantWizardModel = pgNode.Model.extend({
+                  defaults: {
+                    objects: undefined,
+                    acl: undefined
+                  },
+                  schema: [
+                    {
+                      id: 'objects', label: '{{ _("Objects") }}', model: DatabaseObjectModel,
+                      type: 'collection', group: 'Objects'
+                    },
+                    {
+                      id: 'acl', label: '{{ _("Privileges") }}',
+                      model: pgAdmin.Browser.Node.PrivilegeRoleModel,
+                      type: 'collection', canAdd: true,
+                      canDelete: true, control: 'unique-col-collection'
+                    }
+                  ],
+                  urlRoot: saveUrl
+                });
+
+                /**
+                  Create instance of GrantWizard Model, provide urlRoot
+                  node_info object, Generate fields objects
+                */
+                var newModel = new GrantWizardModel({}, { node_info: info });
+
+                /**
+                  Fetch data from server and set into grid
+                  and show/hide progress bar
+                */
+                $('.wizard-progress-bar p').show();
+
+                coll.fetch({
+                  success: function(collection, data) {
+                    $('.wizard-progress-bar p').html('');
+                  },
+                  reset: true
+                }, this);
+
+                //////////////////////////////////////////////////////////////////////
+                //                                                                  //
+                //            Wizard Page for Db Object Type                        //
+                //                                                                  //
+                //////////////////////////////////////////////////////////////////////
+
+                /**
+                  Create wizard page. It renders a grid of
+                  Database Object Types such as
+                  Schemas, Views and Sequences etc.
+                  Set default values
+                */
+                var dbObjectTypePage = self.dbObjectTypePage = new pgBrowser.WizardPage({
+                  id: 1,
+                  page_title: _('Object Selection (step 1 of 3)'),
+                  disable_prev: true,
+                  disable_next: true,
+                  show_description: _('Please select objects from the below list.'),
+                  show_progress_bar: _('Please wait while fetching records...'),
+                  model: newModel,
+                  view: new (function() {
+
+                    // Set page Instance
+                    var pageView = this;
+
+                    _.extend(pageView, {
+
+                      // Remove grid if it is before render
+                      cleanup: function() {
+                        if (this.grid) {
+                          this.grid.remove();
+                          delete this.grid;
+                          this.grid = null;
+                        }
+
+                        // Remove grid element if exists
+                        if (this.el) {
+                          $(this.el).remove();
+                          delete this.el;
+                        }
+                      },
+
+                      // Delete grid before render
+                      grid: null,
+
+                      render: function() {
+
+                        // Create a grid container
+                        var gridBody =
+                              $('<div class="db_objects_container"></div>');
+
+                        // Remove grid if exits before render
+                        if (this.grid) {
+                          this.cleanup();
+                        }
+
+                        // Initialize a new Grid instance
+                        this.grid = new Backgrid.Grid({
+                          columns: _.clone(columns),
+                          collection: coll,
+                          className: "backgrid table-bordered object_type_table"
+                          });
+
+                        // Render selection Type grid and paginator
+                        gridBody.append( this.grid.render().$el);
+
+                        // Render Search Filter
+                        gridBody.prepend(
+                          self.clientSideFilter.render().el);
+
+                        // Assign gridBody content to page element
+                        this.el = gridBody;
+
+                        /**
+                          Fetch selected models from collection and
+                          make rows checked in grid
+                        */
+                        newModel.get('objects').each(function(m) {
+                          var model = coll.get(m.get('object_id'));
+                          if (model) {
+                            coll.trigger('backgrid:selected', model, true);
+                          }
+                        });
+
+                        // Refresh grid to re render rows.
+                        coll.trigger('backgrid:refresh');
+
+                        return this;
+                      }
+                    });
+                  }),
+
+                  beforeNext: function(obj){
+                    var self = this;
+                    obj.options.disable_next = true;
+
+                    /**
+                      Enable/Disable next button of privilegePage if objects
+                      are present in model
+                    */
+                    if(!_.isNull(newModel.get('acl')) &&
+                      !_.isUndefined(newModel.get('acl'))) {
+                      if(newModel.get('acl').length > 0)
+                        obj.collection.at(1).set('disable_next', false);
+                      }
+
+                    // Clean the view
+                    if (self.view) {
+                      self.view.cleanup();
+                      delete self.view;
+                      self.view = null;
+                    }
+                    return true;
+                  },
+
+                });
+
+                //////////////////////////////////////////////////////////////////////
+                //                                                                  //
+                //            Wizard Page for Privilege Control                     //
+                //                                                                  //
+                //////////////////////////////////////////////////////////////////////
+
+                // Wizard for Privelege control
+                var privilegePage = self.privilegePage = new pgBrowser.WizardPage({
+                  id: 2,
+                  page_title: _('Privileges Selection (step 2 of 3)'),
+                  show_description: _('Please select privileges for the selected objects.'),
+                  disable_next: true,
+                  model: newModel,
+
+                  // Create a view function object
+                  view: new (function() {
+                    var pageView = this;
+                      _.extend(pageView, {
+
+                        // Render Privelege control to generate its html markup
+                        render: function() {
+
+                          var obj_priv = [];
+                          self.privArray = _.uniq(self.privArray);
+                          _.each(self.privArray, function(priv){
+                            self.obj_priv = obj_priv = _.union(obj_priv , privDict[priv].acl);
+                          });
+
+                          /**
+                            Define PrivModel and its instance.
+                            Privileges array is generated based on
+                            the type of nodes selected.
+                           */
+                          var privModel = self.privModel;
+                          var PrivModel = pgNode.Model.extend({
+                            defaults: {
+                              acl: undefined
+                            },
+                            schema: [
+                              {
+                                id: 'acl', label: '{{ _("Privileges") }}',
+                                model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend({
+
+                                  // privileges are selected based on node clicked
+                                  privileges: obj_priv
+                                }), uniqueCol : ['grantee', 'grantor'], editable: true,
+                                type: 'collection', canAdd: true,
+                                canDelete: true, control: 'unique-col-collection'
+                              }
+                            ]
+                          });
+
+                          /**
+                            When privelege control is re-rendered, in order to
+                            render privileges based on object type selected,
+                            delete privileges from privModel which are now not
+                            present in object privileges array(object_priv)
+                           */
+                          var data = {};
+                          if (privModel) {
+                            data = privModel.toJSON();
+                            var rolePrivs = data['acl'];
+
+                            for (var idx in rolePrivs) {
+                              var rolePriv = (rolePrivs[idx])['privileges'],
+                                  removeIdx = [], j;
+
+                              for (j in rolePriv) {
+                                var p = rolePriv[j];
+                                if (_.indexOf(obj_priv, p['privilege_type']) == -1) {
+                                  removeIdx.push(j);
+                                }
+                              }
+
+                              for (j in removeIdx) {
+                                rolePriv.splice(j, 1);
+                              }
+                            }
+                          }
+
+                          // Instantiate privModel
+                          privModel = self.privModel = new PrivModel(data, { node_info: self.info });
+
+                          /*
+                          To track changes into model, start new session
+                          and Add event listener for privileges control
+                          */
+                          self.privModel.startNewSession();
+                          self.privModel.on('pgadmin-session:valid', self.onSessionValidated.bind(self));
+                          self.privModel.on('pgadmin-session:invalid', self.onSessionInvalid.bind(self));
+
+                        /**
+                          Create Field Object which has properties like
+                          node_data, node_info which is required for rendering
+                          Privilege control
+                          */
+                          var fields = Backform.generateViewSchema(
+                              self.info, self.privModel, 'create', self.d._type, self.d
+                              );
+                          var privilegesField = new Backform.Field(fields[0].fields[0]);
+
+                          this.privControl = new (privilegesField.get('control')) ({
+                            field: privilegesField,
+                            model: self.privModel
+                          });
+
+                          return {el: this.privControl.render().$el};
+                        },
+
+                        // Remove the privilege control
+                        cleanup: function() {
+                          if (this.privControl) {
+                            this.privControl.remove();
+                            delete this.privControl;
+                            this.privControl = null;
+                          }
+                        }
+                      });
+                  }),
+
+                  beforePrev: function(wizardObj) {
+
+                    // Remove the privilege control
+                    if (this.view) {
+                      this.view.cleanup();
+                      delete this.view;
+                      this.view = null;
+                    }
+
+                    /**
+                      Enable/Disable next button of DbObjectType page if objects
+                      are present in model
+                     */
+                    var objectsModel = newModel.get('objects');
+
+                    if(!_.isUndefined(objectsModel) && !_.isEmpty(objectsModel) &&
+                        objectsModel.length > 0) {
+                      wizardObj.collection.at(0).set('disable_next', false);
+
+                      // Don't show progress bar
+                      wizardObj.collection.at(0).set('show_progress_bar', '');
+                    }
+
+                    /**
+                      We're re-rendering the controls as they are deleted
+                      before heading to next page
+                      Refresh Backgrid to re-render the elements selected
+                      re-render Filter
+                    */
+                    newModel.trigger("backgrid:refresh", newModel, false);
+                    self.clientSideFilter.render();
+                    return true;
+                  },
+
+                  beforeNext: function() { return true; },
+
+                  onNext: function(obj){
+
+                    // Assign acls of privModel to main model newModel
+                    if (!_.isUndefined(self.privModel)) {
+                      newModel.set({'acl': self.privModel.get('acl')});
+                    }
+
+                    // Remove the privilege control
+                    if (this.view) {
+                      this.view.cleanup();
+                      delete this.view;
+                      this.view = null;
+                    }
+
+                    // Enable finish button
+                    self.wizard.options.disable_finish = false;
+
+                    /**
+                      triggers to get SQL queries data to render
+                      into the reviewSQLPage
+                    */
+                    newModel.trigger('pgadmin-wizard:nextpage:sql', {'node_type': node_type });
+                  }
+                });
+
+
+                //////////////////////////////////////////////////////////////////////
+                //                                                                  //
+                //            Review SQL Query Page                                 //
+                //                                                                  //
+                //////////////////////////////////////////////////////////////////////
+
+                //Create SqlField Object
+                var sqlField = new Backform.Field(
+                    {
+                      id: 'sqltab',
+                      label: _('Sql Tab'),
+
+                      /**
+                        Extend 'SqlTabControl' to define new
+                        function 'onWizardNextPageChange'
+                        which gets triggered on next button
+                        click to fetch generated SQL query
+                        for the selected db objects.
+                      */
+                      control: Backform.SqlTabControl.extend({
+                        initialize: function() {
+
+                          // Initialize parent class
+                          Backform.SqlTabControl.prototype.initialize.apply(this, arguments);
+
+                          this.msql_url = self.msql_url;
+
+                          // define trigger events for prev and next page
+                          this.model.on('pgadmin-wizard:nextpage:sql', this.onWizardNextPageChange, this);
+                          this.model.on('pgadmin-wizard:prevpage:sql', this.onWizardPrevPageChange, this);
+                        },
+
+                        // This method fetches the modified SQL for the wizard
+                        onWizardNextPageChange: function(){
+
+                          var self = this;
+
+                          // Fetches modified SQL
+                          $.ajax({
+                            url: this.msql_url,
+                            type: 'GET',
+                            cache: false,
+                            data: self.model.toJSON(true, 'GET'),
+                            dataType: "json",
+                            contentType: "application/json"
+                          }).done(function(res) {
+                            self.sqlTab.clearHistory();
+                            self.sqlTab.setValue(res.data);
+                          }).fail(function() {
+                            self.model.trigger('pgadmin-view:msql:error');
+                          }).always(function() {
+                            self.model.trigger('pgadmin-view:msql:fetched');
+                          });
+                        },
+
+                        remove: function() {
+
+                          // Clear html dom elements of CodeMirror sql tab
+                          self.sqlControl.unbind(); // Unbind all local event bindings
+                          var cmElem = self.sqlControl.sqlTab.getWrapperElement();
+                          cmElem.remove();
+                          self.sqlControl.sqlTab = undefined;
+                        }
+
+                      })
+                    }),
+
+                  /**
+                    Create sqlField view instance
+                    to render it into wizard page
+                  */
+                  sqlControl = self.sqlControl = new (sqlField.get('control'))({
+                    field: sqlField,
+                    model: newModel
+                  });
+
+                // Wizard for SQL tab control
+                var reviewSQLPage = self.reviewSQLPage = new pgBrowser.WizardPage({
+                  id: 3,
+                  page_title: _('Final (Review Selection) (step 3 of 3)'),
+                  show_description: _('Following query will be executed on the database ' +
+                                'server for the selected objects, and privileges. ' +
+                                'Please click on Finish to complete the process.'),
+                  model: newModel,
+                  view: new(function() {
+
+                    // Render SqlTab control to generate its html markup
+                    var sqlTabHtml = sqlControl.render().$el;
+                    this.render = function() {
+                        return { el: sqlTabHtml };
+                    };
+                  }),
+
+                  beforePrev: function(wizardObj) {
+
+                    /**
+                      Enable next button if privilege
+                      model is not empty else disable
+                      next button
+                     */
+                    var aclModel = newModel.get('acl');
+
+                    if(!_.isUndefined(wizardObj.collection) &&
+                      wizardObj.collection.models.length > 0) {
+                      if(!_.isUndefined(aclModel) && !_.isEmpty(aclModel) &&
+                          aclModel.length > 0) {
+                        wizardObj.collection.at(1).set('disable_next', false);
+                      }
+                      else {
+                        wizardObj.collection.at(1).set('disable_next', true);
+                      }
+
+                      return true;
+                    }
+                  },
+                });
+
+
+                // Create Wizard Collection of Wizard Pages
+                var WizardCollection = Backbone.Collection.extend({
+                  model: pgBrowser.WizardPage
+                });
+
+                // It holds all the wizard pages to be rendered
+                this.wizardCollection = new WizardCollection(
+                  [dbObjectTypePage, privilegePage, reviewSQLPage]
+                );
+
+                /**
+                  Create wizard which has following operations:
+                  - renders wizard pages
+                  - defines the first page to render in wizard
+                  - Save the model on finishbutton
+                  - Remove wizard on cancel button
+                  */
+                self.wizard = new (pgBrowser.Wizard.extend({
+                  options: {
+                    title: _('Grant Wizard'), /* Main Wizard Title */
+                    width: '',
+                    height: '',
+                    curr_page: 0,
+                    show_left_panel: false,
+                    disable_finish: true
+                  },
+
+                  // Callback for finish button
+                  onFinish: function() {
+                    var m = newModel,
+                        d = m.toJSON('GET');
+
+                    // Save model
+                    if (d && !_.isEmpty(d) && !_.isUndefined(d.objects)) {
+                      m.save({}, {
+                        attrs: d,
+                        validate: false,
+                        cache: false,
+                        success: function(res) {
+
+                          // Release wizard objects
+                          self.releaseObjects();
+                          self.close();
+                        },
+                        error: function(m, jqxhr) {
+                          alertify.pgNotifier(
+                            "error", jqxhr,
+                            S(
+                              "{{ _('Error during saving properties - %%s!') }}"
+                              ).sprintf(jqxhr.statusText).value()
+                            );
+
+                            // Release wizard objects
+                            self.releaseObjects();
+                            self.close();
+                        }
+                      });
+                    }
+                  },
+
+                  // Callback for cancel button
+                  onCancel: function() {
+
+                    // Release wizard objects
+                    self.releaseObjects();
+                    self.close();
+                  }
+                })) ({
+                  collection: this.wizardCollection,
+                  el: el,
+                  model: newModel
+                });
+
+                // Render wizard
+                self.wizard.render();
+              }
+            };
+          });
+        }
+
+        // Call Grant Wizard Dialog
+        alertify.wizardDialog(true);
+      }
+    };
+
+    return pgBrowser.GrantWizard;
+  });
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/acl.json b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/acl.json
new file mode 100644
index 0000000..2d21f14
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/acl.json
@@ -0,0 +1,30 @@
+{# List of allowed privileges of Schema, table sequence, function and view #}
+{#
+    Format for allowed privileges are:
+    "node_name": {
+        "type": "name",
+        "acl": [...]
+    }
+#}
+{
+    "schema": {
+        "type": "SCHEMA",
+        "acl": ["a", "r", "w", "d", "D", "x", "t", "U", "X"]
+    },
+    "table": {
+        "type": "TABLE",
+        "acl": ["a", "w", "d", "D", "x", "t"]
+    },
+    "view": {
+        "type": "VIEW",
+        "acl": ["a", "w", "d", "D", "x", "t"]
+    },
+    "sequence": {
+        "type": "SEQUENCE",
+        "acl": ["w", "U"]
+    },
+    "function": {
+        "type": "FUNCTION",
+        "acl": ["X"]
+    }
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/function.sql
new file mode 100644
index 0000000..61dd88c
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/function.sql
@@ -0,0 +1,19 @@
+{# ===== Fetch list of Database object types(Functions) ====== #}
+{% if type and node_id %}
+{% set func_type = 'Trigger Function' if type == 'trigger_function' else 'Function' %}
+SELECT
+    pr.oid,
+    pg_get_function_identity_arguments(pr.oid) AS proargs,
+    {# pr.proname || '(' || pg_get_function_identity_arguments(pr.oid) || ')' AS name,#}
+    pr.proname AS name,
+    '{{ func_type }}' AS object_type,
+    '{{ "icon-function" if type != "trigger_function" else "icon-trigger_function" }}' AS icon
+FROM pg_proc pr
+    JOIN pg_type typ ON typ.oid=prorettype
+    JOIN pg_namespace typns ON typns.oid=typ.typnamespace
+    JOIN pg_language lng ON lng.oid=prolang
+    LEFT OUTER JOIN pg_description des ON (des.objoid=pr.oid AND des.classoid='pg_proc'::regclass)
+WHERE proisagg = FALSE AND pronamespace = {{ node_id }}::oid
+    AND typname {{ 'NOT' if type != 'trigger_function' else '' }} IN ('trigger', 'event_trigger')
+ORDER BY proname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_function.sql
new file mode 100644
index 0000000..71d69d0
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_function.sql
@@ -0,0 +1,9 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/functions/privilege.macros' as PRIVILEGE_FUNCTION %}
+{% for obj in data.objects -%}
+{%- set priv = data.priv -%}
+{# ===== if object_type is Function then apply function marcros ===== #}
+{% if (obj.object_type == 'Function' or obj.object_type == 'Trigger Function') %}
+{{ PRIVILEGE_FUNCTION.SET(conn, 'FUNCTION', priv[0]['grantee'], obj.name, priv[0]['without_grant'], priv[0]['with_grant'], nspname, obj.proargs)}}
+{% endif -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_sequence.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_sequence.sql
new file mode 100644
index 0000000..77e0e0e
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_sequence.sql
@@ -0,0 +1,8 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% for obj in data.objects -%}
+{%- set priv = data.priv -%}
+{% if obj.object_type == 'Sequence' %}
+{{ PRIVILEGE.SET(conn, 'SEQUENCE', priv[0]['grantee'], obj.name, priv[0]['without_grant'], priv[0]['with_grant'], nspname ) }}
+{% endif %}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_table.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_table.sql
new file mode 100644
index 0000000..2683306
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_table.sql
@@ -0,0 +1,8 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% for obj in data.objects -%}
+{%- set priv = data.priv -%}
+{% if obj.object_type == 'Table' or obj.object_type == 'View' %}
+{{ PRIVILEGE.SET(conn, 'TABLE', priv[0]['grantee'], obj.name, priv[0]['without_grant'], priv[0]['with_grant'], nspname ) }}
+{% endif %}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/sequence.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/sequence.sql
new file mode 100644
index 0000000..faf7119
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/sequence.sql
@@ -0,0 +1,11 @@
+{# ===== Fetch list of Database object types(Sequence) ===== #}
+{% if node_id %}
+SELECT
+    cl.relname AS name,
+    'Sequence' AS object_type,
+    'icon-sequence' AS icon
+FROM pg_class cl
+    LEFT OUTER JOIN pg_description des ON (des.objoid=cl.oid AND des.classoid='pg_class'::regclass)
+WHERE relkind = 'S' AND relnamespace  = {{ node_id }}::oid
+ORDER BY cl.relname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/table.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/table.sql
new file mode 100644
index 0000000..a2d1bff
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/table.sql
@@ -0,0 +1,14 @@
+{# ===== Fetch list of Database object types(Tables) ===== #}
+{% if node_id %}
+SELECT rel.relname AS name,
+    'Table' AS object_type,
+    'icon-table' AS icon
+FROM pg_class rel
+    LEFT OUTER JOIN pg_tablespace spc ON spc.oid=rel.reltablespace
+    LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass)
+    LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'
+    LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid
+    LEFT JOIN pg_type typ ON rel.reloftype=typ.oid
+WHERE rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ node_id }}::oid
+ORDER BY rel.relname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/view.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/view.sql
new file mode 100644
index 0000000..de09161
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/view.sql
@@ -0,0 +1,19 @@
+{# ===== Fetch list of Database object types(View) ===== #}
+{% if node_id %}
+SELECT
+    c.relname AS name,
+    'View' AS object_type,
+    'icon-view' AS icon
+FROM pg_class c
+    LEFT OUTER JOIN pg_tablespace spc ON spc.oid=c.reltablespace
+    LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass)
+    LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid
+WHERE ((c.relhasrules AND (EXISTS (
+    SELECT r.rulename
+    FROM pg_rewrite r
+    WHERE ((r.ev_class = c.oid)
+        AND (bpchar(r.ev_type) = '1'::bpchar)) ))
+       ) OR (c.relkind = 'v'::char))
+AND c.relnamespace = {{ node_id }}::oid
+ORDER BY c.relname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/acl.json b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/acl.json
new file mode 100644
index 0000000..2d21f14
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/acl.json
@@ -0,0 +1,30 @@
+{# List of allowed privileges of Schema, table sequence, function and view #}
+{#
+    Format for allowed privileges are:
+    "node_name": {
+        "type": "name",
+        "acl": [...]
+    }
+#}
+{
+    "schema": {
+        "type": "SCHEMA",
+        "acl": ["a", "r", "w", "d", "D", "x", "t", "U", "X"]
+    },
+    "table": {
+        "type": "TABLE",
+        "acl": ["a", "w", "d", "D", "x", "t"]
+    },
+    "view": {
+        "type": "VIEW",
+        "acl": ["a", "w", "d", "D", "x", "t"]
+    },
+    "sequence": {
+        "type": "SEQUENCE",
+        "acl": ["w", "U"]
+    },
+    "function": {
+        "type": "FUNCTION",
+        "acl": ["X"]
+    }
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/function.sql
new file mode 100644
index 0000000..61dd88c
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/function.sql
@@ -0,0 +1,19 @@
+{# ===== Fetch list of Database object types(Functions) ====== #}
+{% if type and node_id %}
+{% set func_type = 'Trigger Function' if type == 'trigger_function' else 'Function' %}
+SELECT
+    pr.oid,
+    pg_get_function_identity_arguments(pr.oid) AS proargs,
+    {# pr.proname || '(' || pg_get_function_identity_arguments(pr.oid) || ')' AS name,#}
+    pr.proname AS name,
+    '{{ func_type }}' AS object_type,
+    '{{ "icon-function" if type != "trigger_function" else "icon-trigger_function" }}' AS icon
+FROM pg_proc pr
+    JOIN pg_type typ ON typ.oid=prorettype
+    JOIN pg_namespace typns ON typns.oid=typ.typnamespace
+    JOIN pg_language lng ON lng.oid=prolang
+    LEFT OUTER JOIN pg_description des ON (des.objoid=pr.oid AND des.classoid='pg_proc'::regclass)
+WHERE proisagg = FALSE AND pronamespace = {{ node_id }}::oid
+    AND typname {{ 'NOT' if type != 'trigger_function' else '' }} IN ('trigger', 'event_trigger')
+ORDER BY proname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_function.sql
new file mode 100644
index 0000000..71d69d0
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_function.sql
@@ -0,0 +1,9 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/functions/privilege.macros' as PRIVILEGE_FUNCTION %}
+{% for obj in data.objects -%}
+{%- set priv = data.priv -%}
+{# ===== if object_type is Function then apply function marcros ===== #}
+{% if (obj.object_type == 'Function' or obj.object_type == 'Trigger Function') %}
+{{ PRIVILEGE_FUNCTION.SET(conn, 'FUNCTION', priv[0]['grantee'], obj.name, priv[0]['without_grant'], priv[0]['with_grant'], nspname, obj.proargs)}}
+{% endif -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_sequence.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_sequence.sql
new file mode 100644
index 0000000..77e0e0e
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_sequence.sql
@@ -0,0 +1,8 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% for obj in data.objects -%}
+{%- set priv = data.priv -%}
+{% if obj.object_type == 'Sequence' %}
+{{ PRIVILEGE.SET(conn, 'SEQUENCE', priv[0]['grantee'], obj.name, priv[0]['without_grant'], priv[0]['with_grant'], nspname ) }}
+{% endif %}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_table.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_table.sql
new file mode 100644
index 0000000..8e676ac
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_table.sql
@@ -0,0 +1,8 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% for obj in data.objects -%}
+{%- set priv = data.priv -%}
+{% if obj.object_type == 'Table' %}
+{{ PRIVILEGE.SET(conn, 'TABLE', priv[0]['grantee'], obj.name, priv[0]['without_grant'], priv[0]['with_grant'], nspname ) }}
+{% endif %}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/sequence.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/sequence.sql
new file mode 100644
index 0000000..faf7119
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/sequence.sql
@@ -0,0 +1,11 @@
+{# ===== Fetch list of Database object types(Sequence) ===== #}
+{% if node_id %}
+SELECT
+    cl.relname AS name,
+    'Sequence' AS object_type,
+    'icon-sequence' AS icon
+FROM pg_class cl
+    LEFT OUTER JOIN pg_description des ON (des.objoid=cl.oid AND des.classoid='pg_class'::regclass)
+WHERE relkind = 'S' AND relnamespace  = {{ node_id }}::oid
+ORDER BY cl.relname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/table.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/table.sql
new file mode 100644
index 0000000..a2d1bff
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/table.sql
@@ -0,0 +1,14 @@
+{# ===== Fetch list of Database object types(Tables) ===== #}
+{% if node_id %}
+SELECT rel.relname AS name,
+    'Table' AS object_type,
+    'icon-table' AS icon
+FROM pg_class rel
+    LEFT OUTER JOIN pg_tablespace spc ON spc.oid=rel.reltablespace
+    LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass)
+    LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'
+    LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid
+    LEFT JOIN pg_type typ ON rel.reloftype=typ.oid
+WHERE rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ node_id }}::oid
+ORDER BY rel.relname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/view.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/view.sql
new file mode 100644
index 0000000..de09161
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/view.sql
@@ -0,0 +1,19 @@
+{# ===== Fetch list of Database object types(View) ===== #}
+{% if node_id %}
+SELECT
+    c.relname AS name,
+    'View' AS object_type,
+    'icon-view' AS icon
+FROM pg_class c
+    LEFT OUTER JOIN pg_tablespace spc ON spc.oid=c.reltablespace
+    LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass)
+    LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid
+WHERE ((c.relhasrules AND (EXISTS (
+    SELECT r.rulename
+    FROM pg_rewrite r
+    WHERE ((r.ev_class = c.oid)
+        AND (bpchar(r.ev_type) = '1'::bpchar)) ))
+       ) OR (c.relkind = 'v'::char))
+AND c.relnamespace = {{ node_id }}::oid
+ORDER BY c.relname
+{% endif %}


^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-04-07 14:17  Dave Page <[email protected]>
  parent: Surinder Kumar <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Dave Page @ 2016-04-07 14:17 UTC (permalink / raw)
  To: Surinder Kumar <[email protected]>; +Cc: Ashesh Vashi <[email protected]>; pgadmin-hackers

Hi

On Wed, Apr 6, 2016 at 12:37 PM, Surinder Kumar <
[email protected]> wrote:

> Hi
>
> PFA updated patch with resolved review comments.
>
> On Tue, Apr 5, 2016 at 11:06 AM, Ashesh Vashi <
> [email protected]> wrote:
>
>> On Wed, Mar 30, 2016 at 5:14 PM, Surinder Kumar <
>> [email protected]> wrote:
>>
>>> Hi,
>>>
>>> Please find updated patch.
>>>
>>> This patch has following changes:
>>> 1. Improved code commenting.
>>> 2. Properly handling memory leak issues in js code.
>>>
>> Hi Surinder,
>>
>> As discussed offline, here are the list of some of the review comments:
>>
>> * CSS should be relative to its parent element. Please make sure -
>> whenever you make
>>   some changes in CSS, it should not affect the existing CSS unless
>> discussed.
>>
> Done
>
>>
>> * Change class name for 'error_msg_div' as it is common name. Please
>> name a class
>>   with prefixed as the module name.
>>
> Done
>
>>
>> * Add comments for the blow line changed in node.ui.js file. Always add
>> logical
>>   explanation for a change as a comment for any changes.
>> *while(p && p.length > 0) {*
>>
> Done
>
>>
>> * Please make sure, we wrap the code around 80 characters for better
>> readability.
>>   Line length should not be greater than 80 characters.
>>
> Done
>
>>
>> * Put the allowed ACLs logic with server version support. We need to be
>> flexible
>>   enough to accommodate possible future change in ACLs.
>>
> Done
>
>>
>> * Avoid using name as reference in each of the given. It will make the
>> search faster
>>   in the database and less prone to character conversion issue.
>>   i.e.
>>   Use schema/namespace OID instead of nspname, object OID instead of
>> their name.
>>
> Done
>
>>
>> * Use separate templates for each type of objects.
>>
> Done
>
>>
>> * Use the existing functionalities as much as possible instead of
>> introducing new
>>   one. That will make the code/results consistent across the application.
>>   i.e.
>>   Use existing 'parse_priv_to_db' method, instead of creating new one.
>>
> Done
>
>>
>> * Please remove unnecessary suffixed white-spaces.
>>
> Done
>

I get the attached error in the browser console when selecting "Grant
Wizard" from the menu when the current object is a schema. I've tried all
the normal refreshing/restarting.

[image: Inline image 1]


-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


Attachments:

  [image/png] Screen Shot 2016-04-07 at 15.15.21.png (117.8K, 3-Screen%20Shot%202016-04-07%20at%2015.15.21.png)
  download | view image

^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-04-07 17:10  Surinder Kumar <[email protected]>
  parent: Dave Page <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Surinder Kumar @ 2016-04-07 17:10 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: Ashesh Vashi <[email protected]>; pgadmin-hackers

Hi Dave,

Please find updated patch with above issue resolved.

On Thu, Apr 7, 2016 at 7:47 PM, Dave Page <[email protected]> wrote:

> Hi
>
> On Wed, Apr 6, 2016 at 12:37 PM, Surinder Kumar <
> [email protected]> wrote:
>
>> Hi
>>
>> PFA updated patch with resolved review comments.
>>
>> On Tue, Apr 5, 2016 at 11:06 AM, Ashesh Vashi <
>> [email protected]> wrote:
>>
>>> On Wed, Mar 30, 2016 at 5:14 PM, Surinder Kumar <
>>> [email protected]> wrote:
>>>
>>>> Hi,
>>>>
>>>> Please find updated patch.
>>>>
>>>> This patch has following changes:
>>>> 1. Improved code commenting.
>>>> 2. Properly handling memory leak issues in js code.
>>>>
>>> Hi Surinder,
>>>
>>> As discussed offline, here are the list of some of the review comments:
>>>
>>> * CSS should be relative to its parent element. Please make sure -
>>> whenever you make
>>>   some changes in CSS, it should not affect the existing CSS unless
>>> discussed.
>>>
>> Done
>>
>>>
>>> * Change class name for 'error_msg_div' as it is common name. Please
>>> name a class
>>>   with prefixed as the module name.
>>>
>> Done
>>
>>>
>>> * Add comments for the blow line changed in node.ui.js file. Always add
>>> logical
>>>   explanation for a change as a comment for any changes.
>>> *while(p && p.length > 0) {*
>>>
>> Done
>>
>>>
>>> * Please make sure, we wrap the code around 80 characters for better
>>> readability.
>>>   Line length should not be greater than 80 characters.
>>>
>> Done
>>
>>>
>>> * Put the allowed ACLs logic with server version support. We need to be
>>> flexible
>>>   enough to accommodate possible future change in ACLs.
>>>
>> Done
>>
>>>
>>> * Avoid using name as reference in each of the given. It will make the
>>> search faster
>>>   in the database and less prone to character conversion issue.
>>>   i.e.
>>>   Use schema/namespace OID instead of nspname, object OID instead of
>>> their name.
>>>
>> Done
>>
>>>
>>> * Use separate templates for each type of objects.
>>>
>> Done
>>
>>>
>>> * Use the existing functionalities as much as possible instead of
>>> introducing new
>>>   one. That will make the code/results consistent across the application.
>>>   i.e.
>>>   Use existing 'parse_priv_to_db' method, instead of creating new one.
>>>
>> Done
>>
>>>
>>> * Please remove unnecessary suffixed white-spaces.
>>>
>> Done
>>
>
> I get the attached error in the browser console when selecting "Grant
> Wizard" from the menu when the current object is a schema. I've tried all
> the normal refreshing/restarting.
>
> [image: Inline image 1]
>
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [image/png] Screen Shot 2016-04-07 at 15.15.21.png (117.8K, 3-Screen%20Shot%202016-04-07%20at%2015.15.21.png)
  download | view image

  [application/octet-stream] grant_wizard_v8.patch (85.5K, 4-grant_wizard_v8.patch)
  download | inline diff:
diff --git a/web/pgadmin/browser/static/css/wizard.css b/web/pgadmin/browser/static/css/wizard.css
new file mode 100644
index 0000000..a7078fa
--- /dev/null
+++ b/web/pgadmin/browser/static/css/wizard.css
@@ -0,0 +1,143 @@
+/** CSS for Wizard **/
+.pgadmin_grant_wizard_body .ajs-content {
+  padding-top: 0px !important;
+}
+
+.wizard-header {
+  background: #428bca;
+  padding-left: 15px;
+  padding-bottom: 7px;
+  color: #fff;
+  font-size: 18px;
+  height: 76px;
+  line-height: 76px;
+  border-radius: 6px 6px 0 0;
+}
+
+.wizard-header h3 {
+  font-size: 18px;
+  display: inline-block;
+}
+
+.wizard_dlg {
+  float: left;
+  height: 100%;
+  width: 100%;
+}
+
+.grant_wizard_container {
+  position: relative;
+  overflow: hidden;
+  width: 100%;
+  height: 100%;
+}
+
+.grant_wizard_container .pgadmin-wizard {
+  width: 100%;
+  height: 100%;
+}
+
+.grant_wizard_container .wizard-content {
+  position: relative;
+  padding: 0;
+  height: 64%;
+}
+
+.grant_wizard_container .wizard-right-panel {
+  overflow-y: auto;
+  height: 100%;
+  top: 0;
+  right: 0;
+  position: absolute;
+  bottom: 0;
+}
+
+.grant_wizard_container .wizard-left-panel {
+  position: absolute;
+  top: 0;
+  display: flex;
+  bottom: 0;
+  left: 0;
+  align-items: center;
+  justify-content: center;
+  right: 0;
+}
+
+.grant_wizard_container .wizard-left-panel img {
+  width: 140px;
+}
+
+.grant_wizard_container .wizard-right-panel_content {
+  height: 60%;
+}
+
+.grant_wizard_container {
+  height: 100%;
+}
+
+/* Wizard Footer CSS */
+.grant_wizard_container .footer {
+  position: absolute;
+  background: #fff;
+  border-top: 1px solid #ccc;
+  bottom: 0px;
+  height: 62px;
+  right: 0px;
+  padding-top: 22px;
+  border-radius: 0 0 6px 6px;
+  z-index: 10;
+}
+
+/* Wizard Button CSS */
+.grant_wizard_container .wizard-buttons {
+  float: right;
+}
+
+.grant_wizard_container .wizard-buttons button {
+  float: left;
+  padding: 7px 15.2px;
+  font-size: 14px;
+  margin: 0px 5px 0 0 !important;
+}
+
+.grant_wizard_container .wizard-buttons button.wizard-next i.fa {
+  padding-left: 5px;
+}
+
+.grant_wizard_container .wizard-buttons button.wizard-back i.fa,
+.grant_wizard_container .wizard-buttons button.wizard-cancel i.fa {
+  padding-right: 5px;
+}
+
+.grant_wizard_container .wizard-buttons .wizard-finish {
+  margin-right: 0 !important;
+}
+
+/* Wizard Status bar CSS */
+.grant_wizard_container .wizard-description {
+  padding: 1.7em 0.1em;
+}
+
+/* Error message css */
+.grant_wizard_container .error_msg_div {
+  display: block;
+  position: absolute;
+  bottom: 55px;
+  background: #fff;
+}
+
+.grant_wizard_container .error_msg_div p {
+  background: #fff;
+  color: #b92c28;
+}
+
+/* In wizard select2 dropdown doesn't
+ * popup because z-index of alertify
+ * wizard is greater than the z-index
+ * of select2 dropdown. To make select2
+ * visible, set z-index of select2
+ * higher value than wizard's
+ */
+.select2-container--open {
+  z-index: 10000;
+}
diff --git a/web/pgadmin/browser/static/js/node.ui.js b/web/pgadmin/browser/static/js/node.ui.js
index 4d69e1f..de3f577 100644
--- a/web/pgadmin/browser/static/js/node.ui.js
+++ b/web/pgadmin/browser/static/js/node.ui.js
@@ -279,7 +279,8 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
         return false;
       };
 
-      while(p) {
+      // check if p is not empty
+      while(p && p.length > 0) {
         top = p.get(0).offsetTop + p.height();
         p = p.parent();
         if (hasScrollbar(p)) {
diff --git a/web/pgadmin/browser/static/js/wizard.js b/web/pgadmin/browser/static/js/wizard.js
new file mode 100644
index 0000000..383a312
--- /dev/null
+++ b/web/pgadmin/browser/static/js/wizard.js
@@ -0,0 +1,233 @@
+define(
+    ['underscore', 'backbone', 'pgadmin', 'pgadmin.browser'],
+function(_, Backbone, pgAdmin, pgBrowser) {
+
+  pgBrowser = pgBrowser || pgAdmin.Browser || {};
+
+  /* Wizard individual Page Model */
+  var WizardPage = pgBrowser.WizardPage = Backbone.Model.extend({
+    defaults: {
+      id: undefined, /* Id */
+      page_title: undefined, /* Page Title */
+      view: undefined, /* A Backbone View */
+      html: undefined, /* HTML tags to be rendered */
+      image: undefined, /* Left hand side image */
+      disable_prev: false, /*  Previous Button Flag */
+      disable_next: false, /*  Next Button Flag */
+      disable_cancel: false, /* Cancel Button Flag */
+      show_progress_bar: '',
+      /* Callback for OnLoad */
+      onLoad: function() {
+        return true;
+      },
+      /* Callback for before Next */
+      beforeNext: function() {
+        return true;
+      },
+      onNext: function(){},
+      onBefore: function() {},
+      /* Callback for before Previous */
+      beforePrev: function() {
+        return true;
+      }
+    }
+  });
+
+  var Wizard = pgBrowser.Wizard = Backbone.View.extend({
+    options: {
+      title: 'Wizard', /* Main Wizard Title */
+      image: 'left_panel.png', /* TODO:: We can use default image here */
+      curr_page: 0, /* Current Page to Load */
+      disable_next: false,
+      disable_prev: false,
+      disable_finish: false,
+      disable_cancel: false,
+      height: 400,
+      width: 650,
+      show_left_panel: true
+    },
+    tmpl: _.template(
+       "    <div class='pgadmin-wizard' style='height: <%= this.options.height %>px;"
+       + "    width: <%= this.options.width %>px'>"
+       + "      <div class='wizard-header wizard-badge'>"
+       + "        <div class='row'>"
+       + "          <div class='col-sm-9'>"
+       + "              <h3><span id='main-title'><%= this.options.title %></span> -"
+       + "              <span id='step-title'><%= page_title %></span></h3>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "      <div class='wizard-content col-sm-12'>"
+       + "        <% if(this.options.show_left_panel) { %>"
+       + "          <div class='col-sm-3 wizard-left-panel'>"
+       + "              <img src='<%= this.options.image %>'></div>"
+       + "        <% } %>"
+       + "        <div class='col-sm-<% if(this.options.show_left_panel){ %>9<% }"
+       + "          else { %>12<% } %> wizard-right-panel'>"
+       + "          <% if( typeof show_description !=  'undefined'){ %>"
+       + "            <div class='wizard-description'>"
+       + "              <%= show_description %>"
+       + "            </div>"
+       + "          <% } %>"
+       + "          <div class='wizard-right-panel_content'>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "      <div class='col-sm-12 error_msg_div'>"
+       + "       <p></p>"
+       + "      </div>"
+       + "      <div class='footer col-sm-12'>"
+       + "        <div class='row'>"
+       + "          <div class='col-sm-4 wizard-progress-bar'>"
+       + "             <p><% if(show_progress_bar){ %><%= show_progress_bar %><% } %></p>"
+       + "          </div>"
+       + "          <div class='col-sm-8'>"
+       + "            <div class='wizard-buttons'>"
+       + "              <button class='btn btn-primary wizard-back' <%=this.options.disable_prev ? 'disabled' : ''%>>"
+       + "                <i class='fa fa-backward'></i>Back</button>"
+       + "              <button class='btn btn-primary wizard-next' <%=this.options.disable_next ? 'disabled' : ''%>>Next"
+       + "                <i class='fa fa-forward'></i></button>"
+       + "              <button class='btn btn-danger wizard-cancel' <%=this.options.disable_cancel ? 'disabled' : ''%>>"
+       + "                <i class='fa fa-lg fa-close'></i>Cancel</button>"
+       + "              <button class='btn btn-primary wizard-finish' <%=this.options.disable_finish ? 'disabled' : ''%>>"
+       + "                Finish</button>"
+       + "            </div>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "    </div>"),
+    events: {
+      "click button.wizard-next" : "nextPage",
+      "click button.wizard-back" : "prevPage",
+      "click button.wizard-cancel" : "onCancel",
+      "click button.wizard-finish" : "finishWizard",
+    },
+    initialize: function(options) {
+      this.options = _.extend({}, this.options, options.options);
+      this.currPage = this.collection.at(this.options.curr_page).toJSON();
+    },
+    render: function() {
+      var data = this.currPage;
+
+      /* Check Status of the buttons */
+      this.options.disable_next = (this.options.disable_next ? true : this.evalASFunc(this.currPage.disable_next));
+      this.options.disable_prev = (this.options.disable_prev ? true : this.evalASFunc(this.currPage.disable_prev));
+      this.options.disable_cancel = (this.currPage.canCancel ? true : this.evalASFunc(this.currPage.disable_cancel));
+
+      that = this;
+
+      /* HTML Content */
+      if (data.html) { data.content = data.html; }
+      /* Backbone View */
+      else if (data.view) { data.content = data.view.render().el;}
+
+      $(this.el).html(this.tmpl(data));
+      $(this.el).find(".wizard-right-panel_content").html(data.content);
+
+      /* OnLoad Callback */
+      this.onLoad();
+
+      return this;
+    },
+    nextPage: function() {
+      this.options.curr_page.el = this.$el;
+      if (!this.beforeNext()) { return false; }
+
+      page_id = this.onNext();
+
+      if (page_id ) {
+        this.currPage = this.collection.get(page_id).toJSON();
+        this.options.curr_page = this.collection.indexOf(this.collection.get(page_id));
+      }
+      else if (this.options.curr_page < (this.collection.length-1)) {
+        this.options.curr_page = this.options.curr_page + 1;
+        this.currPage = this.collection.at(this.options.curr_page).toJSON();
+      }
+
+      this.enableDisableNext();
+      this.enableDisablePrev();
+
+      return this.render();
+    },
+    prevPage: function() {
+      if (!this.beforePrev()) { return false; }
+
+      page_id = this.onPrev();
+
+      if (page_id){
+        this.currPage = this.collection.get(page_id).toJSON();
+        this.options.curr_page = this.collection.indexOf(this.collection.get(page_id));
+      }
+      else if (this.options.curr_page > 0) {
+        this.options.curr_page = this.options.curr_page - 1;
+        this.currPage = this.collection.at(this.options.curr_page).toJSON();
+      }
+
+      this.enableDisableNext();
+      this.enableDisablePrev();
+
+      return this.render();
+    },
+    finishWizard: function() {
+      this.onFinish();
+      this.remove(); // Remove view from DOM
+      this.unbind(); // Unbind all local event bindings
+      delete this.$el; // Delete the jQuery wrapped object variable
+      delete this.el; // Delete the variable reference to this node
+      return true;
+    },
+    enableDisableNext: function(disable) {
+        if (typeof(disable) != 'undefined') {
+          this.options.disable_next = disable;
+        }
+        else if (this.options.curr_page >= (this.collection.length-1)) {
+          this.options.disable_next = true;
+        }
+        else {
+          this.options.disable_next = false;
+        }
+    },
+    enableDisablePrev: function(disable) {
+        if (typeof(disable) != 'undefined') {
+          this.options.disable_prev = disable;
+        }
+        else if (this.options.curr_page <= 0) {
+          this.options.disable_prev = true;
+        }
+        else {
+          this.options.disable_prev = false;
+        }
+    },
+    beforeNext: function(){
+      return this.evalASFunc(this.currPage.beforeNext);
+    },
+    beforePrev: function(){
+      return this.evalASFunc(this.currPage.beforePrev);
+    },
+    onPrev: function(){
+      return this.evalASFunc(this.currPage.onPrev);
+    },
+    onNext: function(){
+      return this.evalASFunc(this.currPage.onNext);
+    },
+    onLoad: function() {
+      return this.evalASFunc(this.currPage.onLoad);
+    },
+    onFinish: function() {
+      return true;
+    },
+    onCancel: function() {
+      this.$el.remove();
+      return true;
+    },
+    evalASFunc: function(func, ctx) {
+      var self = this;
+      ctx = ctx || self.currPage;
+
+      return (_.isFunction(func) ? func.apply(ctx, [self]) : func);
+    }
+  });
+
+  return pgBrowser;
+
+});
diff --git a/web/pgadmin/tools/grant_wizard/__init__.py b/web/pgadmin/tools/grant_wizard/__init__.py
new file mode 100644
index 0000000..44dab43
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/__init__.py
@@ -0,0 +1,411 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Implements Grant Wizard"""
+
+import simplejson as json
+from flask import render_template, request, current_app
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_response as ajax_response, \
+    make_json_response, internal_server_error
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from pgadmin.browser.server_groups.servers.utils import parse_priv_to_db
+from pgadmin.utils import PgAdminModule
+from flask import Response, url_for
+from flask.ext.security import login_required
+from urllib import unquote
+from pgadmin.utils.ajax import precondition_required
+from functools import wraps
+
+# set template path for sql scripts
+MODULE_NAME = 'grant_wizard'
+server_info = {}
+
+
+class GrantWizardModule(PgAdminModule):
+    """
+    class GrantWizardModule(Object):
+
+        It is a wizard which inherits PgAdminModule
+        class and define methods to load its own
+        javascript file.
+    """
+    def get_own_stylesheets(self):
+        """
+        Returns:
+            list: the stylesheets used by this module.
+        """
+        stylesheets = [
+            url_for('browser.static', filename='css/wizard.css'),
+            url_for('grant_wizard.static', filename='css/grant_wizard.css')
+            ]
+        return stylesheets
+
+    def get_own_javascripts(self):
+        """"
+        Returns:
+            list: js files used by this module
+        """
+        scripts = []
+        scripts.append({
+            'name': 'pgadmin.tools.grant_wizard',
+            'path': url_for('grant_wizard.index') + 'grant_wizard',
+            'when': None
+        })
+        scripts.append({
+            'name': 'pgadmin.browser.wizard',
+            'path': url_for('browser.static', filename='js/wizard'),
+            'when': None
+        })
+        return scripts
+
+# Create blueprint for GrantWizardModule class
+blueprint = GrantWizardModule(
+    MODULE_NAME, __name__, static_url_path='')
+
+
+def check_precondition(f):
+    """
+    This function will behave as a decorator which will checks
+    database connection before running view, it will also attaches
+    manager,conn & template_path properties to instance of the method.
+
+    Assumptions:
+        This function will always be used as decorator of a class method.
+    """
+    @wraps(f)
+    def wrap(*args, **kwargs):
+        # Here args[0] will hold self & kwargs will hold gid,sid,did
+        # import pdb
+        # pdb.set_trace()
+        server_info.clear()
+        server_info['manager'] = get_driver(
+            PG_DEFAULT_DRIVER).connection_manager(
+                kwargs['sid']
+        )
+        server_info['conn'] = server_info['manager'].connection(
+            did=kwargs['did']
+        )
+        # If DB not connected then return error to browser
+        if not server_info['conn'].connected():
+            return precondition_required(
+                gettext("Connection to the server has been lost!")
+            )
+
+        # Set template path for sql scripts
+        server_info['server_type'] = server_info['manager'].server_type
+        server_info['version'] = server_info['manager'].version
+        if server_info['server_type'] == 'pg':
+            server_info['template_path'] = 'grant_wizard/pg/9.1_plus'
+        elif server_info['server_type'] == 'ppas':
+            server_info['template_path'] = 'grant_wizard/ppas/9.1_plus'
+
+        return f(*args, **kwargs)
+
+    return wrap
+
+
[email protected]("/")
+@login_required
+def index():
+    pass
+
+
[email protected]("/grant_wizard.js")
+@login_required
+def script():
+    """render own javascript"""
+    return Response(response=render_template(
+                "grant_wizard/js/grant_wizard.js", _=gettext),
+                status=200,
+                mimetype="application/javascript")
+
+
[email protected](
+    '/acl/<int:gid>/<int:sid>/<int:did>/', methods=('GET', 'POST'))
+@login_required
+@check_precondition
+def acl_list(gid, sid, did):
+    """render list of acls"""
+    server_prop = server_info
+    return Response(response=render_template(
+                server_prop['template_path']+"/acl.json", _=gettext),
+                status=200,
+                mimetype="application/json")
+
+
[email protected](
+    '/properties/<int:gid>/<int:sid>/<int:did>'
+    '/<int:node_id>/<node_type>/',
+    methods=('GET', 'POST'))
+@login_required
+@check_precondition
+def properties(gid, sid, did, node_id, node_type):
+    """It fetches the properties of object types
+       and render into selection page of wizard
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+
+    server_prop = server_info
+
+    res_data = []
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    # Fetch functions against schema
+    if node_type in ['schema', 'function']:
+        SQL = render_template("/".join(
+            [server_prop['template_path'], '/sql/function.sql']),
+            node_id=node_id, type='function')
+
+        status, res = conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch trigger functions
+    if node_type in ['schema', 'trigger_function']:
+        SQL = render_template("/".join(
+            [server_prop['template_path'], '/sql/function.sql']),
+            node_id=node_id, type='trigger_function')
+        status, res = conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch Sequences against schema
+    if node_type in ['schema', 'sequence']:
+        SQL = render_template("/".join(
+            [server_prop['template_path'], '/sql/sequence.sql']),
+            node_id=node_id)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+        res_data.extend(res['rows'])
+
+    # Fetch Tables against schema
+    if node_type in ['schema', 'table']:
+        SQL = render_template("/".join(
+            [server_prop['template_path'], '/sql/table.sql']),
+            node_id=node_id)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    # Fetch Views against schema
+    if node_type in ['schema', 'view']:
+        SQL = render_template("/".join(
+            [server_prop['template_path'], '/sql/view.sql']),
+            node_id=node_id)
+
+        status, res = conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        res_data.extend(res['rows'])
+
+    return ajax_response(
+            response=res_data,
+            status=200
+            )
+
+
[email protected](
+    '/msql/<int:gid>/<int:sid>/<int:did>/',
+    methods=('GET', 'POST'))
+@login_required
+@check_precondition
+def msql(gid, sid, did):
+    """
+    This function will return modified SQL
+    """
+
+    server_prop = server_info
+    data = {}
+    for k, v in request.args.items():
+        try:
+            data[k] = json.loads(v)
+        except ValueError:
+            data[k] = v
+
+    # Form db connection
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    acls = []
+    try:
+        acls = render_template(
+            "/".join([server_prop['template_path'], '/acl.json'])
+            )
+        acls = json.loads(acls)
+    except Exception as e:
+        current_app.logger.exception(e)
+
+    try:
+
+        # Parse privileges
+        data['priv'] = {}
+        if 'acl' in data:
+            # Get function acls
+            data['priv']['function'] = parse_priv_to_db(
+                data['acl'],
+                acls['function']['acl'])
+
+            data['priv']['sequence'] = parse_priv_to_db(
+                data['acl'],
+                acls['sequence']['acl'])
+
+            data['priv']['table'] = parse_priv_to_db(
+                data['acl'],
+                acls['table']['acl'])
+
+        # Pass database objects and get SQL for privileges
+        SQL_data = ''
+        data_func = {}
+        data_func['objects'] = data['objects']
+        data_func['priv'] = data['priv']['function']
+        SQL = render_template(
+              "/".join([server_prop['template_path'],
+                        '/sql/grant_function.sql']),
+              data=data_func, conn=conn)
+        if SQL and SQL.strip('\n') != '':
+            SQL_data += SQL
+
+        data_seq = {}
+        data_seq['objects'] = data['objects']
+        data_seq['priv'] = data['priv']['sequence']
+        SQL = render_template(
+              "/".join([server_prop['template_path'],
+                        '/sql/grant_sequence.sql']),
+              data=data_seq, conn=conn)
+        if SQL and SQL.strip('\n') != '':
+            SQL_data += SQL
+
+        data_table = {}
+        data_table['objects'] = data['objects']
+        data_table['priv'] = data['priv']['table']
+        SQL = render_template(
+              "/".join([server_prop['template_path'], '/sql/grant_table.sql']),
+              data=data_table, conn=conn)
+        if SQL and SQL.strip('\n') != '':
+            SQL_data += SQL
+
+        res = {'data': SQL_data}
+
+        return ajax_response(
+            response=res,
+            status=200
+            )
+
+    except Exception as e:
+        return make_json_response(
+            status=410,
+            success=0,
+            errormsg=e.message
+        )
+
+
[email protected](
+    '/save/<int:gid>/<int:sid>/<int:did>/',
+    methods=('GET', 'POST'))
+@login_required
+@check_precondition
+def save(gid, sid, did):
+    """
+    This function will apply the privileges to the selected
+    Database Objects
+    """
+    server_prop = server_info
+    data = request.form if request.form else json.loads(request.data.decode())
+
+    # Form db connection and we use conn to execute sql
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    acls = []
+    try:
+        acls = render_template(
+              "/".join([server_prop['template_path'], 'acl.json']),
+            )
+        acls = json.loads(acls)
+    except Exception as e:
+        current_app.logger.exception(e)
+
+    try:
+
+        # Parse privileges
+        data['priv'] = {}
+        if 'acl' in data:
+            # Get function acls
+            data['priv']['function'] = parse_priv_to_db(
+                data['acl'],
+                acls['function']['acl'])
+
+            data['priv']['sequence'] = parse_priv_to_db(
+                data['acl'],
+                acls['sequence']['acl'])
+
+            data['priv']['table'] = parse_priv_to_db(
+                data['acl'],
+                acls['table']['acl'])
+
+        # Pass database objects and get SQL for privileges
+        # Pass database objects and get SQL for privileges
+        SQL_data = ''
+        data_func = {}
+        data_func['objects'] = data['objects']
+        data_func['priv'] = data['priv']['function']
+        SQL = render_template(
+              "/".join([server_prop['template_path'],
+                        '/sql/grant_function.sql']),
+              data=data_func, conn=conn)
+        if SQL and SQL.strip('\n') != '':
+            SQL_data += SQL
+
+        data_seq = {}
+        data_seq['objects'] = data['objects']
+        data_seq['priv'] = data['priv']['sequence']
+        SQL = render_template(
+              "/".join([server_prop['template_path'],
+                        '/sql/grant_sequence.sql']),
+              data=data_seq, conn=conn)
+        if SQL and SQL.strip('\n') != '':
+            SQL_data += SQL
+
+        data_table = {}
+        data_table['objects'] = data['objects']
+        data_table['priv'] = data['priv']['table']
+        SQL = render_template(
+              "/".join([server_prop['template_path'], '/sql/grant_table.sql']),
+              data=data_table, conn=conn)
+        if SQL and SQL.strip('\n') != '':
+            SQL_data += SQL
+
+        status, res = conn.execute_dict(SQL_data)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        return make_json_response(
+            success=1,
+            info="Privileges applied"
+        )
+
+    except Exception as e:
+        return internal_server_error(errormsg=e.message)
diff --git a/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
new file mode 100644
index 0000000..18240e9
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
@@ -0,0 +1,109 @@
+/** Grant Wizard CSS **/
+
+/**
+  CSS to make db object type table
+  fixed so that tbody content may
+  scroll
+ */
+
+.db_objects_container  {
+  height: 100%;
+}
+
+.object_type_table {
+  display: inline-block;
+  height: 100%;
+  border: 0 !important;
+}
+
+.object_type_table thead tr {
+  position: relative;
+  display: block;
+  width: 100%;
+}
+
+.object_type_table tbody {
+  display: block;
+  overflow: scroll;
+  border: 1px solid #ddd;
+  width: 100%;
+  min-height: 100%;
+  max-height: 66%;
+  height: 66%;
+}
+
+.object_type_table tbody tr {
+  display: table;
+  max-width: 100%;
+  width: 100%;
+}
+
+.object_type_table tbody tr td {
+  background-position: 4px 4px;
+  border-radius: 0;
+}
+
+.object_type_table tbody tr td:nth-child(1),
+.object_type_table thead tr th:nth-child(1) {
+  width: 28px;
+  min-width: 28px;
+}
+
+.object_type_table tbody tr td:nth-child(2) {
+  width: 161px;
+  min-width: 161px;
+  max-width: 161px;
+}
+
+.object_type_table thead tr th:nth-child(2) {
+  width: 161px;
+  min-width: 161px;
+  max-width: 161px;
+}
+
+.object_type_table tbody tr td:nth-child(3) {
+  width: 109px;
+  min-width: 109px;
+  max-width: 109px;
+}
+
+.object_type_table thead tr th:nth-child(3) {
+  width: 109px;
+  min-width: 109px;
+  max-width: 109px;
+}
+
+.object_type_table thead tr th:nth-child(4) {
+  width: 100%;
+}
+
+.object_type_table tbody tr td:nth-child(4) {
+  width: 100%;
+  max-width: 100%;
+}
+
+/** Override Backgrid filter CSS **/
+.db_objects_container .backgrid-filter.form-search {
+  float: left;
+  margin: 0px 5px 5px 0;
+}
+
+/** Custom styling for Codemirror field **/
+.wizard-right-panel_content {
+  border: 1px solide #ccc;
+}
+
+.wizard-right-panel_content .CodeMirror {
+  border: 1px solid #ccc;
+  height: 285px !important;
+  min-height: 285px !important;
+}
+
+.wizard-right-panel_content .CodeMirror-linenumber {
+  background: #F7F7F7;
+  border-right: 1px solid #DDDDDD;
+}
+
+.wizard-right-panel_content .CodeMirror-gutters {
+  min-height: 285px !important;
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
new file mode 100644
index 0000000..5c9f131
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
@@ -0,0 +1,1100 @@
+define([
+      'jquery', 'underscore', 'underscore.string', 'alertify',
+      'pgadmin.browser', 'backbone', 'backgrid', 'pgadmin.browser.node',
+      'backgrid.select.all', 'backgrid.filter', 'pgadmin.browser.server.privilege',
+      'pgadmin.browser.wizard',
+      ],
+
+  // This defines Grant Wizard dialog
+  function($, _, S, alertify, pgBrowser, Backbone, Backgrid, pgNode) {
+
+    // if module is already initialized, refer to that.
+    if (pgBrowser.GrantWizard) {
+      return pgBrowser.GrantWizard;
+    }
+
+    /**
+      It is sub model for field "Objects". It has fields
+      for database object types such as Schemas, Views and
+      Sequence etc.
+    */
+    var DatabaseObjectModel = pgNode.Model.extend({
+      defaults: {
+        selected: false,
+        icon: 'icon-unknown',
+        name: undefined,
+        name_with_args: undefined,
+        nspname: undefined,
+        proargs: undefined,
+        object_type: undefined,
+        object_id: undefined
+      },
+      idAttribute: 'object_id', // to uniquely identify a model object
+      toJSON: function(obj) {
+        var d = pgNode.Model.prototype.toJSON.apply(this);
+        delete d.icon;
+        return d;
+      },
+      parse: function(res) {
+
+        // Create unique object id
+        res.object_id = res.name_with_args;
+
+        // create name with args if its object is function
+        if(!_.isUndefined(res.object_type) && (res.object_type == 'Function' ||
+          res.object_type == 'Trigger Function'))
+          res.name_with_args = res.name+'('+(typeof(res.proargs) != 'undefined' ? res.proargs : '')+')';
+        else
+          res.name_with_args = res.name;
+
+        return res;
+      },
+
+      validate: function() {
+
+        /*
+        * Triggers error messages for object types "selected"
+        * if it is empty/undefined/null
+        */
+        var err = {},
+          errmsg,
+          node = this.get('objects').toJSON();
+        if (_.isEmpty(node)) {
+          err['selected'] = '{{ _("Please select any database object type") }}';
+          errmsg = errmsg || err['selected'];
+          this.errorModel.set('selected', errmsg);
+          return errmsg;
+        } else {
+          this.errorModel.unset('selected');
+        }
+        return null;
+      }
+    });
+
+    // Define columns for the Db Object Types grid
+    var columns = [{
+      name: "selected",
+
+      /*
+      Override render method of Backgrid.Extension.SelectRowCell
+      class. It has an issue: It doesn't mark rows checked if we move to next
+      page and then go back to previous page. but it must show.
+      so we handle this case by overriding the render method.
+      */
+      cell: Backgrid.Extension.SelectRowCell.extend({
+        render: function() {
+
+          // Use the Backform Control's render function
+          Backgrid.Extension.SelectRowCell.prototype.render.apply(this, arguments);
+
+          var col = this.column.get('name');
+          if (this.model && this.model.has(col)) {
+            if (this.model.get(col)) {
+              this.checkbox().prop("checked", true);
+              this.$el.parent().toggleClass("selected", true);
+              this.model.trigger("backgrid:selected", this.model, true);
+            }
+          }
+          return this;
+        }
+      }),
+
+      headerCell: "select-all",
+
+    },{
+      name: "object_type",
+      label: "Object Type",
+      editable: false,
+      cell: Backgrid.Cell.extend({
+        render: function() {
+
+          // Override render to add icon to Db Object column
+          Backgrid.Cell.prototype.render.apply(this, arguments);
+          this.$el.addClass(this.model.get('icon')).css({"padding-left": "24px"});
+
+          return this;
+        }
+      })
+    },{
+      name: "nspname",
+      label: "Schema",
+      cell: "string",
+      editable: false
+    },{
+      name: "name_with_args",
+      label: "Name",
+      cell: "string",
+      editable: false
+    }];
+
+    // Create an Object GrantWizard of pgBrowser class
+    pgBrowser.GrantWizard  = {
+      init: function() {
+        if (this.initialized)
+          return;
+
+        this.initialized = true;
+
+        // Define list of nodes on which grant wizard context menu option appears
+        var supported_nodes = [
+              'schema', 'coll-function',
+              'coll-sequence', 'coll-table',
+              'coll-view', 'coll-materialized_view'
+            ],
+
+            /**
+              Enable/disable grantwizard menu in tools based
+              on node selected
+              if selected node is present in supported_nodes,
+              menu will be enabled otherwise disabled.
+              Also, hide it for system view in catalogs
+            */
+            menu_enabled = function(itemData, item, data) {
+              var t = pgBrowser.tree, i = item, d = itemData;
+              var parent_item = t.hasParent(i) ? t.parent(i): null,
+                  parent_data = parent_item ? t.itemData(parent_item) : null;
+              if(!_.isUndefined(d) && !_.isNull(d) && !_.isNull(parent_data))
+                return ((_.indexOf(supported_nodes, d._type) !== -1 && parent_data._type != 'catalog') ? true: false);
+              else
+                return false;
+            };
+
+        // Define the nodes on which the menus to be appear
+        var menus = [{
+          name: 'grant_wizard_schema', module: this,
+          applies: ['tools'], callback: 'start_grant_wizard',
+          priority: 10, label: '{{_("Grant Wizard...") }}',
+          icon: 'wcTabIcon', enable: menu_enabled
+        }];
+
+        // Add supported menus into the menus list
+        for (var idx = 0; idx < supported_nodes.length; idx++) {
+          menus.push({
+            name: 'grant_wizard_schema_context_' + supported_nodes[idx],
+            node: supported_nodes[idx], module: this,
+            applies: ['context'], callback: 'start_grant_wizard',
+            priority: 10, label: '{{_("Grant Wizard...") }}',
+            icon: 'wcTabIcon', enable: menu_enabled
+            });
+        }
+        pgAdmin.Browser.add_menus(menus);
+
+        return this;
+      },
+
+      // Callback to draw Wizard Dialog
+      start_grant_wizard: function(action, item) {
+
+        // Declare Wizard dialog
+        if (!alertify.wizardDialog) {
+          alertify.dialog('wizardDialog', function factory() {
+
+            // Generate wizard main container
+            var $container = $("<div class='wizard_dlg'></div>");
+
+            return {
+              main: function(title) {
+                this.set('title', title);
+              },
+              setup:function() {
+                return {
+
+                  // Set options for dialog
+                  options: {
+                    frameless: true,
+                    resizable: true,
+                    autoReset: false,
+                    maximizable: false,
+                    closableByDimmer: false
+                  }
+                };
+              },
+              hooks:{
+                onshow: function() {
+
+                  // Set dimensions for wizard
+                  this.elements.dialog.style.width = '100%';
+                  this.elements.dialog.style.height = '68%';
+
+                  // Add pgadmin_grant_wizard_body class to dialog
+                  $(this.elements.body).addClass('pgadmin_grant_wizard_body');
+                },
+              },
+
+              /**
+                Returns a Paginator Class Object which is again to be rendered
+
+                @class {Backgrid.Extension.Paginator}
+                @param {Backbone.Collection} coll - from which data is fetched
+                @return {Object} paginator
+              */
+              DbPaginator: function(coll){
+                var paginator = this.paginator = new Backgrid.Extension.Paginator({
+                  collection: coll,
+                  windowSize: 8
+                });
+                return paginator;
+              },
+
+              /**
+                Create new Filter which will filter the
+                rendered grid for Select Type Tabular Data
+                @param {Backbone.PageableCollection} coll
+              */
+              DbObjectFilter: function(coll){
+                var clientSideFilter = this.clientSideFilter = new Backgrid.Extension.ClientSideFilter({
+                  collection: coll,
+                  placeholder: _('Search by object type or name'),
+
+                  // The model fields to search for matches
+                  fields: ['object_type', 'name'],
+
+                  // How long to wait after typing has stopped before searching can start
+                  wait: 150
+                });
+                return clientSideFilter;
+              },
+
+              //Enable Disable Next button of PrivilegePage
+              updateButtons: function(modified){
+                if(!modified)
+                  $('.wizard-next').prop('disabled', true);
+                else
+                  $('.wizard-next').prop('disabled', false);
+              },
+
+              /**
+                Callback called when an errorModel is set
+                with invalid value and errormsg is set into
+                status bar element and next button is disabled
+              */
+              onSessionInvalid: function(msg) {
+                $('.error_msg_div p').html(msg).removeClass("hide");
+
+                // Enable disable Next button
+                this.updateButtons(false);
+                return true;
+              },
+
+              /**
+                Callback called when anything is set into model
+                thus hide error msg element and enable next button
+                status bar element and next button is disabled
+              */
+              onSessionValidated: function(sessHasChanged) {
+                $('.error_msg_div p').empty().addClass("hide");
+
+                // Enable disable Next button
+                this.updateButtons(sessHasChanged);
+              },
+
+              /**
+                Remove/Delete objects, attributes
+                in wizard on wizard close or finish
+                to reclaim memory
+              */
+              releaseObjects: function(){
+                var self = this;
+
+                if(!_.isUndefined(self.dbObjectFilter)) {
+                  self.dbObjectFilter.remove();
+                  self.dbObjectFilter = undefined;
+                }
+
+                if(!_.isUndefined(self.clientSideFilter)) {
+                  self.clientSideFilter.remove();
+                  self.clientSideFilter = undefined;
+                }
+
+                // clear object priv array
+                if(!_.isNull(self.obj_priv) &&
+                  !_.isUndefined(self.obj_priv)) {
+                  self.obj_priv = [];
+                  delete self.obj_priv;
+                }
+
+                // Delete Wizard Pages, clear model and cleanup view
+                if(!_.isUndefined(self.dbObjectTypePage) &&
+                  !_.isNull(self.dbObjectTypePage)) {
+                  if(!_.isUndefined(self.dbObjectTypePage.get('model')) &&
+                    !_.isNull(self.dbObjectTypePage.get('model'))) {
+                    self.dbObjectTypePage.get('model').clear();
+                    self.dbObjectTypePage.get('view').cleanup();
+                    self.dbObjectTypePage = undefined;
+                  }
+                }
+
+                if(!_.isUndefined(self.privilegePage) &&
+                  !_.isNull(self.privilegePage)) {
+                  if(!_.isUndefined(self.privilegePage.get('model')) &&
+                    !_.isNull(self.privilegePage.get('model'))) {
+                    self.privilegePage.get('model').clear();
+                    self.privilegePage.get('view').cleanup();
+                    self.privilegePage = undefined;
+                  }
+                }
+
+                if(!_.isUndefined(self.reviewSQLPage) &&
+                  !_.isNull(self.reviewSQLPage)) {
+                  if(!_.isUndefined(self.reviewSQLPage.get('model')) &&
+                    !_.isNull(self.reviewSQLPage.get('model'))) {
+                    self.reviewSQLPage.get('model').clear();
+                    self.reviewSQLPage = undefined;
+                  }
+                }
+
+                // Remove Sql control
+                if (!_.isUndefined(self.sqlControl)) {
+                  self.sqlControl.remove();
+                }
+
+                // Clear privModel
+                if(!_.isNull(self.privModel) &&
+                  !_.isUndefined(self.privModel)) {
+                  self.privModel.clear();
+                }
+
+                // Remove collection containing db object data
+                if(!_.isNull(self.coll) &&
+                  !_.isUndefined(self.coll)) {
+                  self.coll.reset();
+                  self.coll = undefined;
+                }
+                // Delete Wizard
+                if(!_.isNull(self.wizard) &&
+                  !_.isUndefined(self.wizard)) {
+                  self.wizard.collection.reset();
+                  self.wizard.curr_page = undefined;
+                }
+
+              },
+
+              /**
+                Every time a wizard is opened, this function
+                is called everytime. It has Wizard Pages which
+                are rendered by the Wizard Class:
+
+                @class {pgBrowser.WizardPage} dbObjectType1 - This page
+                @extends {Backbone.Model}
+                renders a grid of Database Object Types such as
+                  Schemas, Views and Sequences etc.
+
+                @class {pgBrowser.WizardPage} WizardPage2 - This page
+                @extends {Backbone.Model}
+                adds Privilege Control which provides grant privileges
+                such as "Create, Insert, Delete, Update" so on the
+                database objects selected on Wizard Pages.
+
+                @class {pgBrowser.WizardPage} WizardPage3 - This page
+                displays the generated GRANT SQL query for the Db
+                objects selected with the specific privileges added to it.
+                @extends {Backbone.Model}
+
+                @class {Backbone.Collection} WizardCollection - It is the
+                collection of wizard pages
+
+                @class {pgBrowser.Wizard} wizard - Its task is:
+                - Create a Wizard
+                - Add Buttons, Callbacks to it.
+                - Render WizardPages
+                @extends {Backbone.View}
+
+              */
+              build: function() {
+                this.elements.content.appendChild($container.get(0));
+              },
+
+              //Returns list of Acls defined for nodes
+              get_json_data: function(gid, sid, did) {
+                var url = "{{ url_for('grant_wizard.index') }}" + "acl/" +
+                    S('%s/%s/%s/').sprintf(
+                        encodeURI(gid), encodeURI(sid), encodeURI(did)).value();
+                return $.ajax({
+                  async: false,
+                  url: url,
+                  dataType: 'jsonp'
+                });
+
+              },
+              prepare:function() {
+
+                $container.empty().append("<div class='grant_wizard_container'></div>");
+
+                // Define el for wizard view
+                var el = $('.grant_wizard_container');
+
+                // Extract the data from the selected tree node
+                var t = pgBrowser.tree,
+                    i = t.selected(),
+                    d = this.d = i && i.length == 1 ? t.itemData(i) : undefined,
+                    info = this.info = pgBrowser.Node.getTreeNodeHierarchy(i),
+                    icon = d.icon;
+
+                /**
+                  Generate a URL using:
+                  gid, did, sid(server id), node_id(node id),
+                  node_(node name), node_type(node type)
+                  and pass it to collection which will fetch Object Type properties.
+                */
+                var gid = info['server-group']._id,
+                    sid = info.server._id,
+                    did = info.database._id,
+                    node_id = d._id,
+                    nspname = info.schema.label,
+
+                    /**
+                      get node name only. used in mapping with object types defined
+                      in allowed_acl.json
+                     */
+                    node_type = d._type.replace('coll-', '').replace('materialized_', ''),
+                    node_label = d.label;
+
+                // Fetch privileges specific to nodes
+                var json_data = this.get_json_data(gid, sid, did);
+                var privDict = JSON.parse(json_data.responseText);
+
+                // Collection url to fetch database object types for objects field
+                var baseUrl = "{{ url_for('grant_wizard.index') }}" + "properties/" +
+                    S('%s/%s/%s/%s/%s/').sprintf(
+                        encodeURI(gid), encodeURI(sid), encodeURI(did),
+                        encodeURI(node_id), encodeURI(node_type)).value();
+
+                    // Model's save url
+                    saveUrl = "{{ url_for('grant_wizard.index') }}" + "save/" +
+                        S('%s/%s/%s/').sprintf(
+                            encodeURI(gid), encodeURI(sid),
+                            encodeURI(did)).value(),
+
+                    // generate encoded url based on wizard type
+                    msql_url = this.msql_url = "/grant_wizard/msql/"+
+                      S('%s/%s/%s/').sprintf(
+                          encodeURI(gid), encodeURI(sid),
+                          encodeURI(did)).value(),
+
+                    Coll = Backbone.Collection.extend({
+                      model: DatabaseObjectModel.extend({
+                        defaults: _.extend({}, DatabaseObjectModel.prototype.defaults,
+                              {nspname: nspname}
+                        )
+                      }),
+                      url: baseUrl
+                    }),
+
+                    // Create instances of collection and filter
+                    coll = this.coll = new Coll(),
+                    dbObjectFilter = this.dbObjectFilter = this.DbObjectFilter(coll);
+
+                /**
+                  privArray holds objects selected which further helps
+                  in creating privileges Model
+                */
+                var self = this;
+                self.privArray = [];
+
+                /**
+                  Override backgrid listener "backgrid:selected" to
+                  Add/Remove model to/from objects collection
+                */
+                coll.on('backgrid:selected', function(model, selected) {
+                  model.set('selected', selected);
+
+                  var object_type = model.get('object_type');
+                  switch (object_type)
+                  {
+                    case 'Function':
+                      object_type = 'function';
+                      break;
+                    case 'Trigger Function':
+                      object_type = 'function';
+                      break;
+                    case 'Table':
+                      object_type = 'table';
+                      break;
+                    case 'Sequence':
+                      object_type = 'sequence';
+                      break;
+                    case 'View':
+                      object_type = 'table';
+                      break;
+                  }
+
+                  /**
+                    if a row (checkbox) is checked, add that model
+                    into collection, when unchecked remove it from
+                    model.
+
+                    Also push/pop object type in/from privArray
+                  */
+                  if(selected) {
+                    if(_.indexOf(self.privArray, object_type) == -1)
+                      self.privArray.push(object_type);
+                    newModel.get('objects').add(model, { silent: true });
+                  }
+                  else {
+                    var idx = self.privArray.indexOf(object_type);
+                    if(idx !=-1)
+                      self.privArray.splice(idx, 1);
+                    newModel.get('objects').remove(model);
+                  }
+
+                  // validate model on checkbox check/uncheck
+                  var msg =  model.validate.call(newModel);
+
+                  /**
+                    If no object type is selected, set error msg
+                    and disable next button, else enable next button
+                  */
+                  if(msg)
+                    self.onSessionInvalid.call(self, msg);
+                  else
+                    self.onSessionValidated.call(self, true);
+                });
+
+                /**
+                  It is the main model with schema defined
+                  Every time a new wizard is opened,
+                  a new model should create.
+                */
+                var GrantWizardModel = pgNode.Model.extend({
+                  defaults: {
+                    objects: undefined,
+                    acl: undefined
+                  },
+                  schema: [
+                    {
+                      id: 'objects', label: '{{ _("Objects") }}', model: DatabaseObjectModel,
+                      type: 'collection', group: 'Objects'
+                    },
+                    {
+                      id: 'acl', label: '{{ _("Privileges") }}',
+                      model: pgAdmin.Browser.Node.PrivilegeRoleModel,
+                      type: 'collection', canAdd: true,
+                      canDelete: true, control: 'unique-col-collection'
+                    }
+                  ],
+                  urlRoot: saveUrl
+                });
+
+                /**
+                  Create instance of GrantWizard Model, provide urlRoot
+                  node_info object, Generate fields objects
+                */
+                var newModel = new GrantWizardModel({}, { node_info: info });
+
+                /**
+                  Fetch data from server and set into grid
+                  and show/hide progress bar
+                */
+                $('.wizard-progress-bar p').show();
+
+                coll.fetch({
+                  success: function(collection, data) {
+                    $('.wizard-progress-bar p').html('');
+                  },
+                  reset: true
+                }, this);
+
+                //////////////////////////////////////////////////////////////////////
+                //                                                                  //
+                //            Wizard Page for Db Object Type                        //
+                //                                                                  //
+                //////////////////////////////////////////////////////////////////////
+
+                /**
+                  Create wizard page. It renders a grid of
+                  Database Object Types such as
+                  Schemas, Views and Sequences etc.
+                  Set default values
+                */
+                var dbObjectTypePage = self.dbObjectTypePage = new pgBrowser.WizardPage({
+                  id: 1,
+                  page_title: _('Object Selection (step 1 of 3)'),
+                  disable_prev: true,
+                  disable_next: true,
+                  show_description: _('Please select objects from the below list.'),
+                  show_progress_bar: _('Please wait while fetching records...'),
+                  model: newModel,
+                  view: new (function() {
+
+                    // Set page Instance
+                    var pageView = this;
+
+                    _.extend(pageView, {
+
+                      // Remove grid if it is before render
+                      cleanup: function() {
+                        if (this.grid) {
+                          this.grid.remove();
+                          delete this.grid;
+                          this.grid = null;
+                        }
+
+                        // Remove grid element if exists
+                        if (this.el) {
+                          $(this.el).remove();
+                          delete this.el;
+                        }
+                      },
+
+                      // Delete grid before render
+                      grid: null,
+
+                      render: function() {
+
+                        // Create a grid container
+                        var gridBody =
+                              $('<div class="db_objects_container"></div>');
+
+                        // Remove grid if exits before render
+                        if (this.grid) {
+                          this.cleanup();
+                        }
+
+                        // Initialize a new Grid instance
+                        this.grid = new Backgrid.Grid({
+                          columns: _.clone(columns),
+                          collection: coll,
+                          className: "backgrid table-bordered object_type_table"
+                          });
+
+                        // Render selection Type grid and paginator
+                        gridBody.append( this.grid.render().$el);
+
+                        // Render Search Filter
+                        gridBody.prepend(
+                          self.clientSideFilter.render().el);
+
+                        // Assign gridBody content to page element
+                        this.el = gridBody;
+
+                        /**
+                          Fetch selected models from collection and
+                          make rows checked in grid
+                        */
+                        newModel.get('objects').each(function(m) {
+                          var model = coll.get(m.get('object_id'));
+                          if (model) {
+                            coll.trigger('backgrid:selected', model, true);
+                          }
+                        });
+
+                        // Refresh grid to re render rows.
+                        coll.trigger('backgrid:refresh');
+
+                        return this;
+                      }
+                    });
+                  }),
+
+                  beforeNext: function(obj){
+                    var self = this;
+                    obj.options.disable_next = true;
+
+                    /**
+                      Enable/Disable next button of privilegePage if objects
+                      are present in model
+                    */
+                    if(!_.isNull(newModel.get('acl')) &&
+                      !_.isUndefined(newModel.get('acl'))) {
+                      if(newModel.get('acl').length > 0)
+                        obj.collection.at(1).set('disable_next', false);
+                      }
+
+                    // Clean the view
+                    if (self.view) {
+                      self.view.cleanup();
+                      delete self.view;
+                      self.view = null;
+                    }
+                    return true;
+                  },
+
+                });
+
+                //////////////////////////////////////////////////////////////////////
+                //                                                                  //
+                //            Wizard Page for Privilege Control                     //
+                //                                                                  //
+                //////////////////////////////////////////////////////////////////////
+
+                // Wizard for Privelege control
+                var privilegePage = self.privilegePage = new pgBrowser.WizardPage({
+                  id: 2,
+                  page_title: _('Privileges Selection (step 2 of 3)'),
+                  show_description: _('Please select privileges for the selected objects.'),
+                  disable_next: true,
+                  model: newModel,
+
+                  // Create a view function object
+                  view: new (function() {
+                    var pageView = this;
+                      _.extend(pageView, {
+
+                        // Render Privelege control to generate its html markup
+                        render: function() {
+
+                          var obj_priv = [];
+                          self.privArray = _.uniq(self.privArray);
+                          _.each(self.privArray, function(priv){
+                            self.obj_priv = obj_priv = _.union(obj_priv , privDict[priv].acl);
+                          });
+
+                          /**
+                            Define PrivModel and its instance.
+                            Privileges array is generated based on
+                            the type of nodes selected.
+                           */
+                          var privModel = self.privModel;
+                          var PrivModel = pgNode.Model.extend({
+                            defaults: {
+                              acl: undefined
+                            },
+                            schema: [
+                              {
+                                id: 'acl', label: '{{ _("Privileges") }}',
+                                model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend({
+
+                                  // privileges are selected based on node clicked
+                                  privileges: obj_priv
+                                }), uniqueCol : ['grantee', 'grantor'], editable: true,
+                                type: 'collection', canAdd: true,
+                                canDelete: true, control: 'unique-col-collection'
+                              }
+                            ]
+                          });
+
+                          /**
+                            When privelege control is re-rendered, in order to
+                            render privileges based on object type selected,
+                            delete privileges from privModel which are now not
+                            present in object privileges array(object_priv)
+                           */
+                          var data = {};
+                          if (privModel) {
+                            data = privModel.toJSON();
+                            var rolePrivs = data['acl'];
+
+                            for (var idx in rolePrivs) {
+                              var rolePriv = (rolePrivs[idx])['privileges'],
+                                  removeIdx = [], j;
+
+                              for (j in rolePriv) {
+                                var p = rolePriv[j];
+                                if (_.indexOf(obj_priv, p['privilege_type']) == -1) {
+                                  removeIdx.push(j);
+                                }
+                              }
+
+                              for (j in removeIdx) {
+                                rolePriv.splice(j, 1);
+                              }
+                            }
+                          }
+
+                          // Instantiate privModel
+                          privModel = self.privModel = new PrivModel(data, { node_info: self.info });
+
+                          /*
+                          To track changes into model, start new session
+                          and Add event listener for privileges control
+                          */
+                          self.privModel.startNewSession();
+                          self.privModel.on('pgadmin-session:valid', self.onSessionValidated.bind(self));
+                          self.privModel.on('pgadmin-session:invalid', self.onSessionInvalid.bind(self));
+
+                        /**
+                          Create Field Object which has properties like
+                          node_data, node_info which is required for rendering
+                          Privilege control
+                          */
+                          var fields = Backform.generateViewSchema(
+                              self.info, self.privModel, 'create', self.d._type, self.d
+                              );
+                          var privilegesField = new Backform.Field(fields[0].fields[0]);
+
+                          this.privControl = new (privilegesField.get('control')) ({
+                            field: privilegesField,
+                            model: self.privModel
+                          });
+
+                          return {el: this.privControl.render().$el};
+                        },
+
+                        // Remove the privilege control
+                        cleanup: function() {
+                          if (this.privControl) {
+                            this.privControl.remove();
+                            delete this.privControl;
+                            this.privControl = null;
+                          }
+                        }
+                      });
+                  }),
+
+                  beforePrev: function(wizardObj) {
+
+                    // Remove the privilege control
+                    if (this.view) {
+                      this.view.cleanup();
+                      delete this.view;
+                      this.view = null;
+                    }
+
+                    /**
+                      Enable/Disable next button of DbObjectType page if objects
+                      are present in model
+                     */
+                    var objectsModel = newModel.get('objects');
+
+                    if(!_.isUndefined(objectsModel) && !_.isEmpty(objectsModel) &&
+                        objectsModel.length > 0) {
+                      wizardObj.collection.at(0).set('disable_next', false);
+
+                      // Don't show progress bar
+                      wizardObj.collection.at(0).set('show_progress_bar', '');
+                    }
+
+                    /**
+                      We're re-rendering the controls as they are deleted
+                      before heading to next page
+                      Refresh Backgrid to re-render the elements selected
+                      re-render Filter
+                    */
+                    newModel.trigger("backgrid:refresh", newModel, false);
+                    self.clientSideFilter.render();
+                    return true;
+                  },
+
+                  beforeNext: function() { return true; },
+
+                  onNext: function(obj){
+
+                    // Assign acls of privModel to main model newModel
+                    if (!_.isUndefined(self.privModel)) {
+                      newModel.set({'acl': self.privModel.get('acl')});
+                    }
+
+                    // Remove the privilege control
+                    if (this.view) {
+                      this.view.cleanup();
+                      delete this.view;
+                      this.view = null;
+                    }
+
+                    // Enable finish button
+                    self.wizard.options.disable_finish = false;
+
+                    /**
+                      triggers to get SQL queries data to render
+                      into the reviewSQLPage
+                    */
+                    newModel.trigger('pgadmin-wizard:nextpage:sql', {'node_type': node_type });
+                  }
+                });
+
+
+                //////////////////////////////////////////////////////////////////////
+                //                                                                  //
+                //            Review SQL Query Page                                 //
+                //                                                                  //
+                //////////////////////////////////////////////////////////////////////
+
+                //Create SqlField Object
+                var sqlField = new Backform.Field(
+                    {
+                      id: 'sqltab',
+                      label: _('Sql Tab'),
+
+                      /**
+                        Extend 'SqlTabControl' to define new
+                        function 'onWizardNextPageChange'
+                        which gets triggered on next button
+                        click to fetch generated SQL query
+                        for the selected db objects.
+                      */
+                      control: Backform.SqlTabControl.extend({
+                        initialize: function() {
+
+                          // Initialize parent class
+                          Backform.SqlTabControl.prototype.initialize.apply(this, arguments);
+
+                          this.msql_url = self.msql_url;
+
+                          // define trigger events for prev and next page
+                          this.model.on('pgadmin-wizard:nextpage:sql', this.onWizardNextPageChange, this);
+                          this.model.on('pgadmin-wizard:prevpage:sql', this.onWizardPrevPageChange, this);
+                        },
+
+                        // This method fetches the modified SQL for the wizard
+                        onWizardNextPageChange: function(){
+
+                          var self = this;
+
+                          // Fetches modified SQL
+                          $.ajax({
+                            url: this.msql_url,
+                            type: 'GET',
+                            cache: false,
+                            data: self.model.toJSON(true, 'GET'),
+                            dataType: "json",
+                            contentType: "application/json"
+                          }).done(function(res) {
+                            self.sqlTab.clearHistory();
+                            self.sqlTab.setValue(res.data);
+                          }).fail(function() {
+                            self.model.trigger('pgadmin-view:msql:error');
+                          }).always(function() {
+                            self.model.trigger('pgadmin-view:msql:fetched');
+                          });
+                        },
+
+                        remove: function() {
+
+                          // Clear html dom elements of CodeMirror sql tab
+                          self.sqlControl.unbind(); // Unbind all local event bindings
+                          var cmElem = self.sqlControl.sqlTab.getWrapperElement();
+                          cmElem.remove();
+                          self.sqlControl.sqlTab = undefined;
+                        }
+
+                      })
+                    }),
+
+                  /**
+                    Create sqlField view instance
+                    to render it into wizard page
+                  */
+                  sqlControl = self.sqlControl = new (sqlField.get('control'))({
+                    field: sqlField,
+                    model: newModel
+                  });
+
+                // Wizard for SQL tab control
+                var reviewSQLPage = self.reviewSQLPage = new pgBrowser.WizardPage({
+                  id: 3,
+                  page_title: _('Final (Review Selection) (step 3 of 3)'),
+                  show_description: _('Following query will be executed on the database ' +
+                                'server for the selected objects, and privileges. ' +
+                                'Please click on Finish to complete the process.'),
+                  model: newModel,
+                  view: new(function() {
+
+                    // Render SqlTab control to generate its html markup
+                    var sqlTabHtml = sqlControl.render().$el;
+                    this.render = function() {
+                        return { el: sqlTabHtml };
+                    };
+                  }),
+
+                  beforePrev: function(wizardObj) {
+
+                    /**
+                      Enable next button if privilege
+                      model is not empty else disable
+                      next button
+                     */
+                    var aclModel = newModel.get('acl');
+
+                    if(!_.isUndefined(wizardObj.collection) &&
+                      wizardObj.collection.models.length > 0) {
+                      if(!_.isUndefined(aclModel) && !_.isEmpty(aclModel) &&
+                          aclModel.length > 0) {
+                        wizardObj.collection.at(1).set('disable_next', false);
+                      }
+                      else {
+                        wizardObj.collection.at(1).set('disable_next', true);
+                      }
+
+                      return true;
+                    }
+                  },
+                });
+
+
+                // Create Wizard Collection of Wizard Pages
+                var WizardCollection = Backbone.Collection.extend({
+                  model: pgBrowser.WizardPage
+                });
+
+                // It holds all the wizard pages to be rendered
+                this.wizardCollection = new WizardCollection(
+                  [dbObjectTypePage, privilegePage, reviewSQLPage]
+                );
+
+                /**
+                  Create wizard which has following operations:
+                  - renders wizard pages
+                  - defines the first page to render in wizard
+                  - Save the model on finishbutton
+                  - Remove wizard on cancel button
+                  */
+                self.wizard = new (pgBrowser.Wizard.extend({
+                  options: {
+                    title: _('Grant Wizard'), /* Main Wizard Title */
+                    width: '',
+                    height: '',
+                    curr_page: 0,
+                    show_left_panel: false,
+                    disable_finish: true
+                  },
+
+                  // Callback for finish button
+                  onFinish: function() {
+                    var m = newModel,
+                        d = m.toJSON('GET');
+
+                    // Save model
+                    if (d && !_.isEmpty(d) && !_.isUndefined(d.objects)) {
+                      m.save({}, {
+                        attrs: d,
+                        validate: false,
+                        cache: false,
+                        success: function(res) {
+
+                          // Release wizard objects
+                          self.releaseObjects();
+                          self.close();
+                        },
+                        error: function(m, jqxhr) {
+                          alertify.pgNotifier(
+                            "error", jqxhr,
+                            S(
+                              "{{ _('Error during saving properties - %%s!') }}"
+                              ).sprintf(jqxhr.statusText).value()
+                            );
+
+                            // Release wizard objects
+                            self.releaseObjects();
+                            self.close();
+                        }
+                      });
+                    }
+                  },
+
+                  // Callback for cancel button
+                  onCancel: function() {
+
+                    // Release wizard objects
+                    self.releaseObjects();
+                    self.close();
+                  }
+                })) ({
+                  collection: this.wizardCollection,
+                  el: el,
+                  model: newModel
+                });
+
+                // Render wizard
+                self.wizard.render();
+              }
+            };
+          });
+        }
+
+        // Call Grant Wizard Dialog
+        alertify.wizardDialog(true);
+      }
+    };
+
+    return pgBrowser.GrantWizard;
+  });
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/acl.json b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/acl.json
new file mode 100644
index 0000000..2d21f14
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/acl.json
@@ -0,0 +1,30 @@
+{# List of allowed privileges of Schema, table sequence, function and view #}
+{#
+    Format for allowed privileges are:
+    "node_name": {
+        "type": "name",
+        "acl": [...]
+    }
+#}
+{
+    "schema": {
+        "type": "SCHEMA",
+        "acl": ["a", "r", "w", "d", "D", "x", "t", "U", "X"]
+    },
+    "table": {
+        "type": "TABLE",
+        "acl": ["a", "w", "d", "D", "x", "t"]
+    },
+    "view": {
+        "type": "VIEW",
+        "acl": ["a", "w", "d", "D", "x", "t"]
+    },
+    "sequence": {
+        "type": "SEQUENCE",
+        "acl": ["w", "U"]
+    },
+    "function": {
+        "type": "FUNCTION",
+        "acl": ["X"]
+    }
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/function.sql
new file mode 100644
index 0000000..420d102
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/function.sql
@@ -0,0 +1,22 @@
+{# ===== Fetch list of Database object types(Functions) ====== #}
+{% if type and node_id %}
+{% set func_type = 'Trigger Function' if type == 'trigger_function' else 'Function' %}
+SELECT
+    pr.oid,
+    pg_get_function_identity_arguments(pr.oid) AS proargs,
+    {# pr.proname || '(' || pg_get_function_identity_arguments(pr.oid) || ')' AS name,#}
+    pr.proname AS name,
+    '{{ func_type }}' AS object_type,
+    '{{ "icon-function" if type != "trigger_function" else "icon-trigger_function" }}' AS icon
+FROM
+    pg_proc pr
+JOIN pg_type typ ON typ.oid=prorettype
+JOIN pg_namespace typns ON typns.oid=typ.typnamespace
+JOIN pg_language lng ON lng.oid=prolang
+LEFT OUTER JOIN pg_description des ON (des.objoid=pr.oid AND des.classoid='pg_proc'::regclass)
+WHERE
+    proisagg = FALSE AND pronamespace = {{ node_id }}::oid
+    AND typname {{ 'NOT' if type != 'trigger_function' else '' }} IN ('trigger', 'event_trigger')
+ORDER BY
+    proname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_function.sql
new file mode 100644
index 0000000..60eaefe
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_function.sql
@@ -0,0 +1,10 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/functions/privilege.macros' as PRIVILEGE_FUNCTION %}
+{% for obj in data.objects -%}
+{% for priv in data.priv -%}
+{# ===== if object_type is Function then apply function marcros ===== #}
+{% if (obj.object_type == 'Function' or obj.object_type == 'Trigger Function') %}
+{{ PRIVILEGE_FUNCTION.SET(conn, 'FUNCTION', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], nspname, obj.proargs)}}
+{% endif -%}
+{% endfor -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_sequence.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_sequence.sql
new file mode 100644
index 0000000..8ccfa88
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_sequence.sql
@@ -0,0 +1,9 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% for obj in data.objects -%}
+{% for priv in data.priv -%}
+{% if obj.object_type == 'Sequence' %}
+{{ PRIVILEGE.SET(conn, 'SEQUENCE', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], nspname ) }}
+{% endif %}
+{% endfor -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_table.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_table.sql
new file mode 100644
index 0000000..6c1274f
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_table.sql
@@ -0,0 +1,9 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% for obj in data.objects -%}
+{% for priv in data.priv -%}
+{% if obj.object_type == 'Table' or obj.object_type == 'View' %}
+{{ PRIVILEGE.SET(conn, 'TABLE', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], nspname ) }}
+{% endif %}
+{% endfor -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/sequence.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/sequence.sql
new file mode 100644
index 0000000..c4ed323
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/sequence.sql
@@ -0,0 +1,14 @@
+{# ===== Fetch list of Database object types(Sequence) ===== #}
+{% if node_id %}
+SELECT
+    cl.relname AS name,
+    'Sequence' AS object_type,
+    'icon-sequence' AS icon
+FROM
+    pg_class cl
+LEFT OUTER JOIN pg_description des ON (des.objoid=cl.oid AND des.classoid='pg_class'::regclass)
+WHERE
+    relkind = 'S' AND relnamespace  = {{ node_id }}::oid
+ORDER BY
+    cl.relname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/table.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/table.sql
new file mode 100644
index 0000000..6cc4ee9
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/table.sql
@@ -0,0 +1,18 @@
+{# ===== Fetch list of Database object types(Tables) ===== #}
+{% if node_id %}
+SELECT
+    rel.relname AS name,
+    'Table' AS object_type,
+    'icon-table' AS icon
+FROM
+    pg_class rel
+LEFT OUTER JOIN pg_tablespace spc ON spc.oid=rel.reltablespace
+LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass)
+LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'
+LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid
+LEFT JOIN pg_type typ ON rel.reloftype=typ.oid
+WHERE
+    rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ node_id }}::oid
+ORDER BY
+    rel.relname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/view.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/view.sql
new file mode 100644
index 0000000..57382e3
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/view.sql
@@ -0,0 +1,27 @@
+{# ===== Fetch list of Database object types(View) ===== #}
+{% if node_id %}
+SELECT
+    c.relname AS name,
+    'View' AS object_type,
+    'icon-view' AS icon
+FROM
+    pg_class c
+LEFT OUTER JOIN pg_tablespace spc ON spc.oid=c.reltablespace
+LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass)
+LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid
+WHERE
+    ((c.relhasrules AND (EXISTS (
+      SELECT
+          r.rulename
+      FROM
+          pg_rewrite r
+      WHERE
+          ((r.ev_class = c.oid)
+          AND (bpchar(r.ev_type) = '1'::bpchar))
+      ))
+     ) OR (c.relkind = 'v'::char)
+    )
+    AND c.relnamespace = {{ node_id }}::oid
+ORDER BY
+    c.relname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/acl.json b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/acl.json
new file mode 100644
index 0000000..2d21f14
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/acl.json
@@ -0,0 +1,30 @@
+{# List of allowed privileges of Schema, table sequence, function and view #}
+{#
+    Format for allowed privileges are:
+    "node_name": {
+        "type": "name",
+        "acl": [...]
+    }
+#}
+{
+    "schema": {
+        "type": "SCHEMA",
+        "acl": ["a", "r", "w", "d", "D", "x", "t", "U", "X"]
+    },
+    "table": {
+        "type": "TABLE",
+        "acl": ["a", "w", "d", "D", "x", "t"]
+    },
+    "view": {
+        "type": "VIEW",
+        "acl": ["a", "w", "d", "D", "x", "t"]
+    },
+    "sequence": {
+        "type": "SEQUENCE",
+        "acl": ["w", "U"]
+    },
+    "function": {
+        "type": "FUNCTION",
+        "acl": ["X"]
+    }
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/function.sql
new file mode 100644
index 0000000..420d102
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/function.sql
@@ -0,0 +1,22 @@
+{# ===== Fetch list of Database object types(Functions) ====== #}
+{% if type and node_id %}
+{% set func_type = 'Trigger Function' if type == 'trigger_function' else 'Function' %}
+SELECT
+    pr.oid,
+    pg_get_function_identity_arguments(pr.oid) AS proargs,
+    {# pr.proname || '(' || pg_get_function_identity_arguments(pr.oid) || ')' AS name,#}
+    pr.proname AS name,
+    '{{ func_type }}' AS object_type,
+    '{{ "icon-function" if type != "trigger_function" else "icon-trigger_function" }}' AS icon
+FROM
+    pg_proc pr
+JOIN pg_type typ ON typ.oid=prorettype
+JOIN pg_namespace typns ON typns.oid=typ.typnamespace
+JOIN pg_language lng ON lng.oid=prolang
+LEFT OUTER JOIN pg_description des ON (des.objoid=pr.oid AND des.classoid='pg_proc'::regclass)
+WHERE
+    proisagg = FALSE AND pronamespace = {{ node_id }}::oid
+    AND typname {{ 'NOT' if type != 'trigger_function' else '' }} IN ('trigger', 'event_trigger')
+ORDER BY
+    proname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_function.sql
new file mode 100644
index 0000000..60eaefe
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_function.sql
@@ -0,0 +1,10 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/functions/privilege.macros' as PRIVILEGE_FUNCTION %}
+{% for obj in data.objects -%}
+{% for priv in data.priv -%}
+{# ===== if object_type is Function then apply function marcros ===== #}
+{% if (obj.object_type == 'Function' or obj.object_type == 'Trigger Function') %}
+{{ PRIVILEGE_FUNCTION.SET(conn, 'FUNCTION', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], nspname, obj.proargs)}}
+{% endif -%}
+{% endfor -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_sequence.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_sequence.sql
new file mode 100644
index 0000000..8ccfa88
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_sequence.sql
@@ -0,0 +1,9 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% for obj in data.objects -%}
+{% for priv in data.priv -%}
+{% if obj.object_type == 'Sequence' %}
+{{ PRIVILEGE.SET(conn, 'SEQUENCE', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], nspname ) }}
+{% endif %}
+{% endfor -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_table.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_table.sql
new file mode 100644
index 0000000..6c1274f
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_table.sql
@@ -0,0 +1,9 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% for obj in data.objects -%}
+{% for priv in data.priv -%}
+{% if obj.object_type == 'Table' or obj.object_type == 'View' %}
+{{ PRIVILEGE.SET(conn, 'TABLE', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], nspname ) }}
+{% endif %}
+{% endfor -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/sequence.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/sequence.sql
new file mode 100644
index 0000000..c4ed323
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/sequence.sql
@@ -0,0 +1,14 @@
+{# ===== Fetch list of Database object types(Sequence) ===== #}
+{% if node_id %}
+SELECT
+    cl.relname AS name,
+    'Sequence' AS object_type,
+    'icon-sequence' AS icon
+FROM
+    pg_class cl
+LEFT OUTER JOIN pg_description des ON (des.objoid=cl.oid AND des.classoid='pg_class'::regclass)
+WHERE
+    relkind = 'S' AND relnamespace  = {{ node_id }}::oid
+ORDER BY
+    cl.relname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/table.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/table.sql
new file mode 100644
index 0000000..6cc4ee9
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/table.sql
@@ -0,0 +1,18 @@
+{# ===== Fetch list of Database object types(Tables) ===== #}
+{% if node_id %}
+SELECT
+    rel.relname AS name,
+    'Table' AS object_type,
+    'icon-table' AS icon
+FROM
+    pg_class rel
+LEFT OUTER JOIN pg_tablespace spc ON spc.oid=rel.reltablespace
+LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass)
+LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'
+LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid
+LEFT JOIN pg_type typ ON rel.reloftype=typ.oid
+WHERE
+    rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ node_id }}::oid
+ORDER BY
+    rel.relname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/view.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/view.sql
new file mode 100644
index 0000000..57382e3
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/view.sql
@@ -0,0 +1,27 @@
+{# ===== Fetch list of Database object types(View) ===== #}
+{% if node_id %}
+SELECT
+    c.relname AS name,
+    'View' AS object_type,
+    'icon-view' AS icon
+FROM
+    pg_class c
+LEFT OUTER JOIN pg_tablespace spc ON spc.oid=c.reltablespace
+LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass)
+LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid
+WHERE
+    ((c.relhasrules AND (EXISTS (
+      SELECT
+          r.rulename
+      FROM
+          pg_rewrite r
+      WHERE
+          ((r.ev_class = c.oid)
+          AND (bpchar(r.ev_type) = '1'::bpchar))
+      ))
+     ) OR (c.relkind = 'v'::char)
+    )
+    AND c.relnamespace = {{ node_id }}::oid
+ORDER BY
+    c.relname
+{% endif %}


^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-04-07 19:22  Dave Page <[email protected]>
  parent: Surinder Kumar <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Dave Page @ 2016-04-07 19:22 UTC (permalink / raw)
  To: Surinder Kumar <[email protected]>; +Cc: Ashesh Vashi <[email protected]>; pgadmin-hackers

Hi

Nearly there :-). Assuming no regressions, I believe we'll be ready to
commit once the following issues are resolved:

- The select object grid should fill the available vertical space in the
dialogue - at present there's a gap below it.

- When scrolling to the top of bottom of the select object grid, the
dialogue jumps up or down the screen a little, if the dialogue has been
resized to a larger size.

- Please allow the wizard to be opened from a Database node, in which case
objects from all schemas should be listed. This will also require support
for schemas themselves.

- When selecting privileges, each time I click on a checkbox, the row
closes. Similar grids elsewhere in the app close the row when the cell
loses focus, *however*, that is also the incorrect behaviour - the row
should only close when the row itself loses focus (and should open when it
gets focus).

- The message:

Please select objects from the below list.

should read:

Please select objects from the list below.

- The message:

Following query will be executed on the database server for the selected
objects, and privileges. Please click on Finish to complete the process.

should read:

The SQL below will be executed on the database server to grant the selected
privileges. Please click on <b>Finish</b> to complete the process.

Thanks!

On Thu, Apr 7, 2016 at 6:10 PM, Surinder Kumar <
[email protected]> wrote:

> Hi Dave,
>
> Please find updated patch with above issue resolved.
>
> On Thu, Apr 7, 2016 at 7:47 PM, Dave Page <[email protected]> wrote:
>
>> Hi
>>
>> On Wed, Apr 6, 2016 at 12:37 PM, Surinder Kumar <
>> [email protected]> wrote:
>>
>>> Hi
>>>
>>> PFA updated patch with resolved review comments.
>>>
>>> On Tue, Apr 5, 2016 at 11:06 AM, Ashesh Vashi <
>>> [email protected]> wrote:
>>>
>>>> On Wed, Mar 30, 2016 at 5:14 PM, Surinder Kumar <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi,
>>>>>
>>>>> Please find updated patch.
>>>>>
>>>>> This patch has following changes:
>>>>> 1. Improved code commenting.
>>>>> 2. Properly handling memory leak issues in js code.
>>>>>
>>>> Hi Surinder,
>>>>
>>>> As discussed offline, here are the list of some of the review comments:
>>>>
>>>> * CSS should be relative to its parent element. Please make sure -
>>>> whenever you make
>>>>   some changes in CSS, it should not affect the existing CSS unless
>>>> discussed.
>>>>
>>> Done
>>>
>>>>
>>>> * Change class name for 'error_msg_div' as it is common name. Please
>>>> name a class
>>>>   with prefixed as the module name.
>>>>
>>> Done
>>>
>>>>
>>>> * Add comments for the blow line changed in node.ui.js file. Always
>>>> add logical
>>>>   explanation for a change as a comment for any changes.
>>>> *while(p && p.length > 0) {*
>>>>
>>> Done
>>>
>>>>
>>>> * Please make sure, we wrap the code around 80 characters for better
>>>> readability.
>>>>   Line length should not be greater than 80 characters.
>>>>
>>> Done
>>>
>>>>
>>>> * Put the allowed ACLs logic with server version support. We need to be
>>>> flexible
>>>>   enough to accommodate possible future change in ACLs.
>>>>
>>> Done
>>>
>>>>
>>>> * Avoid using name as reference in each of the given. It will make the
>>>> search faster
>>>>   in the database and less prone to character conversion issue.
>>>>   i.e.
>>>>   Use schema/namespace OID instead of nspname, object OID instead of
>>>> their name.
>>>>
>>> Done
>>>
>>>>
>>>> * Use separate templates for each type of objects.
>>>>
>>> Done
>>>
>>>>
>>>> * Use the existing functionalities as much as possible instead of
>>>> introducing new
>>>>   one. That will make the code/results consistent across the
>>>> application.
>>>>   i.e.
>>>>   Use existing 'parse_priv_to_db' method, instead of creating new one.
>>>>
>>> Done
>>>
>>>>
>>>> * Please remove unnecessary suffixed white-spaces.
>>>>
>>> Done
>>>
>>
>> I get the attached error in the browser console when selecting "Grant
>> Wizard" from the menu when the current object is a schema. I've tried all
>> the normal refreshing/restarting.
>>
>> [image: Inline image 1]
>>
>>
>> --
>> Dave Page
>> Blog: http://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EnterpriseDB UK: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>>
>
>


-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


Attachments:

  [image/png] Screen Shot 2016-04-07 at 15.15.21.png (117.8K, 3-Screen%20Shot%202016-04-07%20at%2015.15.21.png)
  download | view image

^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-04-08 17:59  Surinder Kumar <[email protected]>
  parent: Dave Page <[email protected]>
  0 siblings, 1 reply; 24+ messages in thread

From: Surinder Kumar @ 2016-04-08 17:59 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: Ashesh Vashi <[email protected]>; pgadmin-hackers

Hi

PFA patch with resolved review comments.

On Fri, Apr 8, 2016 at 12:52 AM, Dave Page <[email protected]> wrote:

> Hi
>
> Nearly there :-). Assuming no regressions, I believe we'll be ready to
> commit once the following issues are resolved:
>
> - The select object grid should fill the available vertical space in the
> dialogue - at present there's a gap below it.
>
Done

>
> - When scrolling to the top of bottom of the select object grid, the
> dialogue jumps up or down the screen a little, if the dialogue has been
> resized to a larger size.
>
As this change is generic for all alertifyjs dialogs, I have added its
style css in overrides.css. Done


>
> - Please allow the wizard to be opened from a Database node, in which case
> objects from all schemas should be listed. This will also require support
> for schemas themselves.
>
Done

>
> - When selecting privileges, each time I click on a checkbox, the row
> closes. Similar grids elsewhere in the app close the row when the cell
> loses focus, *however*, that is also the incorrect behaviour - the row
> should only close when the row itself loses focus (and should open when it
> gets focus).
>
I checked the row closes either when gets clicked on row or outside row, it
doesn't seems to close on click on checkbox.

>
> - The message:
>
> Please select objects from the below list.
>
> should read:
>
> Please select objects from the list below.
>
Done

>
> - The message:
>
> Following query will be executed on the database server for the selected
> objects, and privileges. Please click on Finish to complete the process.
>
> should read:
>
> The SQL below will be executed on the database server to grant the
> selected privileges. Please click on <b>Finish</b> to complete the process.
>
Done

>
> Thanks!
>
> On Thu, Apr 7, 2016 at 6:10 PM, Surinder Kumar <
> [email protected]> wrote:
>
>> Hi Dave,
>>
>> Please find updated patch with above issue resolved.
>>
>> On Thu, Apr 7, 2016 at 7:47 PM, Dave Page <[email protected]> wrote:
>>
>>> Hi
>>>
>>> On Wed, Apr 6, 2016 at 12:37 PM, Surinder Kumar <
>>> [email protected]> wrote:
>>>
>>>> Hi
>>>>
>>>> PFA updated patch with resolved review comments.
>>>>
>>>> On Tue, Apr 5, 2016 at 11:06 AM, Ashesh Vashi <
>>>> [email protected]> wrote:
>>>>
>>>>> On Wed, Mar 30, 2016 at 5:14 PM, Surinder Kumar <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi,
>>>>>>
>>>>>> Please find updated patch.
>>>>>>
>>>>>> This patch has following changes:
>>>>>> 1. Improved code commenting.
>>>>>> 2. Properly handling memory leak issues in js code.
>>>>>>
>>>>> Hi Surinder,
>>>>>
>>>>> As discussed offline, here are the list of some of the review comments:
>>>>>
>>>>> * CSS should be relative to its parent element. Please make sure -
>>>>> whenever you make
>>>>>   some changes in CSS, it should not affect the existing CSS unless
>>>>> discussed.
>>>>>
>>>> Done
>>>>
>>>>>
>>>>> * Change class name for 'error_msg_div' as it is common name. Please
>>>>> name a class
>>>>>   with prefixed as the module name.
>>>>>
>>>> Done
>>>>
>>>>>
>>>>> * Add comments for the blow line changed in node.ui.js file. Always
>>>>> add logical
>>>>>   explanation for a change as a comment for any changes.
>>>>> *while(p && p.length > 0) {*
>>>>>
>>>> Done
>>>>
>>>>>
>>>>> * Please make sure, we wrap the code around 80 characters for better
>>>>> readability.
>>>>>   Line length should not be greater than 80 characters.
>>>>>
>>>> Done
>>>>
>>>>>
>>>>> * Put the allowed ACLs logic with server version support. We need to
>>>>> be flexible
>>>>>   enough to accommodate possible future change in ACLs.
>>>>>
>>>> Done
>>>>
>>>>>
>>>>> * Avoid using name as reference in each of the given. It will make
>>>>> the search faster
>>>>>   in the database and less prone to character conversion issue.
>>>>>   i.e.
>>>>>   Use schema/namespace OID instead of nspname, object OID instead of
>>>>> their name.
>>>>>
>>>> Done
>>>>
>>>>>
>>>>> * Use separate templates for each type of objects.
>>>>>
>>>> Done
>>>>
>>>>>
>>>>> * Use the existing functionalities as much as possible instead of
>>>>> introducing new
>>>>>   one. That will make the code/results consistent across the
>>>>> application.
>>>>>   i.e.
>>>>>   Use existing 'parse_priv_to_db' method, instead of creating new one.
>>>>>
>>>> Done
>>>>
>>>>>
>>>>> * Please remove unnecessary suffixed white-spaces.
>>>>>
>>>> Done
>>>>
>>>
>>> I get the attached error in the browser console when selecting "Grant
>>> Wizard" from the menu when the current object is a schema. I've tried all
>>> the normal refreshing/restarting.
>>>
>>> [image: Inline image 1]
>>>
>>>
>>> --
>>> Dave Page
>>> Blog: http://pgsnake.blogspot.com
>>> Twitter: @pgsnake
>>>
>>> EnterpriseDB UK: http://www.enterprisedb.com
>>> The Enterprise PostgreSQL Company
>>>
>>
>>
>
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [image/png] Screen Shot 2016-04-07 at 15.15.21.png (117.8K, 3-Screen%20Shot%202016-04-07%20at%2015.15.21.png)
  download | view image

  [application/octet-stream] grant_wizard_v9.patch (89.7K, 4-grant_wizard_v9.patch)
  download | inline diff:
diff --git a/web/pgadmin/browser/static/css/wizard.css b/web/pgadmin/browser/static/css/wizard.css
new file mode 100644
index 0000000..0be48dc
--- /dev/null
+++ b/web/pgadmin/browser/static/css/wizard.css
@@ -0,0 +1,143 @@
+/** CSS for Wizard **/
+.pgadmin_grant_wizard_body .ajs-content {
+  padding-top: 0px !important;
+}
+
+.wizard-header {
+  background: #428bca;
+  padding-left: 15px;
+  padding-bottom: 7px;
+  color: #fff;
+  font-size: 18px;
+  height: 76px;
+  line-height: 76px;
+  border-radius: 6px 6px 0 0;
+}
+
+.wizard-header h3 {
+  font-size: 18px;
+  display: inline-block;
+}
+
+.wizard_dlg {
+  float: left;
+  height: 100%;
+  width: 100%;
+}
+
+.grant_wizard_container {
+  position: relative;
+  overflow: hidden;
+  width: 100%;
+  height: 100%;
+}
+
+.grant_wizard_container .pgadmin-wizard {
+  width: 100%;
+  height: 100%;
+}
+
+.grant_wizard_container .wizard-content {
+  position: relative;
+  padding: 0;
+  height: 78%;
+}
+
+.grant_wizard_container .wizard-right-panel {
+  overflow-y: auto;
+  height: 100%;
+  top: 0;
+  right: 0;
+  position: absolute;
+  bottom: 0;
+}
+
+.grant_wizard_container .wizard-left-panel {
+  position: absolute;
+  top: 0;
+  display: flex;
+  bottom: 0;
+  left: 0;
+  align-items: center;
+  justify-content: center;
+  right: 0;
+}
+
+.grant_wizard_container .wizard-left-panel img {
+  width: 140px;
+}
+
+.grant_wizard_container .wizard-right-panel_content {
+  height: 60%;
+}
+
+.grant_wizard_container {
+  height: 100%;
+}
+
+/* Wizard Footer CSS */
+.grant_wizard_container .footer {
+  position: absolute;
+  background: #fff;
+  border-top: 1px solid #ccc;
+  bottom: 0px;
+  height: 62px;
+  right: 0px;
+  padding-top: 22px;
+  border-radius: 0 0 6px 6px;
+  z-index: 10;
+}
+
+/* Wizard Button CSS */
+.grant_wizard_container .wizard-buttons {
+  float: right;
+}
+
+.grant_wizard_container .wizard-buttons button {
+  float: left;
+  padding: 7px 15.2px;
+  font-size: 14px;
+  margin: 0px 5px 0 0 !important;
+}
+
+.grant_wizard_container .wizard-buttons button.wizard-next i.fa {
+  padding-left: 5px;
+}
+
+.grant_wizard_container .wizard-buttons button.wizard-back i.fa,
+.grant_wizard_container .wizard-buttons button.wizard-cancel i.fa {
+  padding-right: 5px;
+}
+
+.grant_wizard_container .wizard-buttons .wizard-finish {
+  margin-right: 0 !important;
+}
+
+/* Wizard Status bar CSS */
+.grant_wizard_container .wizard-description {
+  padding: 1.7em 0.1em;
+}
+
+/* Error message css */
+.grant_wizard_container .error_msg_div {
+  display: block;
+  position: absolute;
+  bottom: 55px;
+  background: #fff;
+}
+
+.grant_wizard_container .error_msg_div p {
+  background: #fff;
+  color: #b92c28;
+}
+
+/* In wizard select2 dropdown doesn't
+ * popup because z-index of alertify
+ * wizard is greater than the z-index
+ * of select2 dropdown. To make select2
+ * visible, set z-index of select2
+ * higher value than wizard's
+ */
+.select2-container--open {
+  z-index: 10000;
+}
diff --git a/web/pgadmin/browser/static/js/node.ui.js b/web/pgadmin/browser/static/js/node.ui.js
index 4d69e1f..de3f577 100644
--- a/web/pgadmin/browser/static/js/node.ui.js
+++ b/web/pgadmin/browser/static/js/node.ui.js
@@ -279,7 +279,8 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
         return false;
       };
 
-      while(p) {
+      // check if p is not empty
+      while(p && p.length > 0) {
         top = p.get(0).offsetTop + p.height();
         p = p.parent();
         if (hasScrollbar(p)) {
diff --git a/web/pgadmin/browser/static/js/wizard.js b/web/pgadmin/browser/static/js/wizard.js
new file mode 100644
index 0000000..383a312
--- /dev/null
+++ b/web/pgadmin/browser/static/js/wizard.js
@@ -0,0 +1,233 @@
+define(
+    ['underscore', 'backbone', 'pgadmin', 'pgadmin.browser'],
+function(_, Backbone, pgAdmin, pgBrowser) {
+
+  pgBrowser = pgBrowser || pgAdmin.Browser || {};
+
+  /* Wizard individual Page Model */
+  var WizardPage = pgBrowser.WizardPage = Backbone.Model.extend({
+    defaults: {
+      id: undefined, /* Id */
+      page_title: undefined, /* Page Title */
+      view: undefined, /* A Backbone View */
+      html: undefined, /* HTML tags to be rendered */
+      image: undefined, /* Left hand side image */
+      disable_prev: false, /*  Previous Button Flag */
+      disable_next: false, /*  Next Button Flag */
+      disable_cancel: false, /* Cancel Button Flag */
+      show_progress_bar: '',
+      /* Callback for OnLoad */
+      onLoad: function() {
+        return true;
+      },
+      /* Callback for before Next */
+      beforeNext: function() {
+        return true;
+      },
+      onNext: function(){},
+      onBefore: function() {},
+      /* Callback for before Previous */
+      beforePrev: function() {
+        return true;
+      }
+    }
+  });
+
+  var Wizard = pgBrowser.Wizard = Backbone.View.extend({
+    options: {
+      title: 'Wizard', /* Main Wizard Title */
+      image: 'left_panel.png', /* TODO:: We can use default image here */
+      curr_page: 0, /* Current Page to Load */
+      disable_next: false,
+      disable_prev: false,
+      disable_finish: false,
+      disable_cancel: false,
+      height: 400,
+      width: 650,
+      show_left_panel: true
+    },
+    tmpl: _.template(
+       "    <div class='pgadmin-wizard' style='height: <%= this.options.height %>px;"
+       + "    width: <%= this.options.width %>px'>"
+       + "      <div class='wizard-header wizard-badge'>"
+       + "        <div class='row'>"
+       + "          <div class='col-sm-9'>"
+       + "              <h3><span id='main-title'><%= this.options.title %></span> -"
+       + "              <span id='step-title'><%= page_title %></span></h3>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "      <div class='wizard-content col-sm-12'>"
+       + "        <% if(this.options.show_left_panel) { %>"
+       + "          <div class='col-sm-3 wizard-left-panel'>"
+       + "              <img src='<%= this.options.image %>'></div>"
+       + "        <% } %>"
+       + "        <div class='col-sm-<% if(this.options.show_left_panel){ %>9<% }"
+       + "          else { %>12<% } %> wizard-right-panel'>"
+       + "          <% if( typeof show_description !=  'undefined'){ %>"
+       + "            <div class='wizard-description'>"
+       + "              <%= show_description %>"
+       + "            </div>"
+       + "          <% } %>"
+       + "          <div class='wizard-right-panel_content'>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "      <div class='col-sm-12 error_msg_div'>"
+       + "       <p></p>"
+       + "      </div>"
+       + "      <div class='footer col-sm-12'>"
+       + "        <div class='row'>"
+       + "          <div class='col-sm-4 wizard-progress-bar'>"
+       + "             <p><% if(show_progress_bar){ %><%= show_progress_bar %><% } %></p>"
+       + "          </div>"
+       + "          <div class='col-sm-8'>"
+       + "            <div class='wizard-buttons'>"
+       + "              <button class='btn btn-primary wizard-back' <%=this.options.disable_prev ? 'disabled' : ''%>>"
+       + "                <i class='fa fa-backward'></i>Back</button>"
+       + "              <button class='btn btn-primary wizard-next' <%=this.options.disable_next ? 'disabled' : ''%>>Next"
+       + "                <i class='fa fa-forward'></i></button>"
+       + "              <button class='btn btn-danger wizard-cancel' <%=this.options.disable_cancel ? 'disabled' : ''%>>"
+       + "                <i class='fa fa-lg fa-close'></i>Cancel</button>"
+       + "              <button class='btn btn-primary wizard-finish' <%=this.options.disable_finish ? 'disabled' : ''%>>"
+       + "                Finish</button>"
+       + "            </div>"
+       + "          </div>"
+       + "        </div>"
+       + "      </div>"
+       + "    </div>"),
+    events: {
+      "click button.wizard-next" : "nextPage",
+      "click button.wizard-back" : "prevPage",
+      "click button.wizard-cancel" : "onCancel",
+      "click button.wizard-finish" : "finishWizard",
+    },
+    initialize: function(options) {
+      this.options = _.extend({}, this.options, options.options);
+      this.currPage = this.collection.at(this.options.curr_page).toJSON();
+    },
+    render: function() {
+      var data = this.currPage;
+
+      /* Check Status of the buttons */
+      this.options.disable_next = (this.options.disable_next ? true : this.evalASFunc(this.currPage.disable_next));
+      this.options.disable_prev = (this.options.disable_prev ? true : this.evalASFunc(this.currPage.disable_prev));
+      this.options.disable_cancel = (this.currPage.canCancel ? true : this.evalASFunc(this.currPage.disable_cancel));
+
+      that = this;
+
+      /* HTML Content */
+      if (data.html) { data.content = data.html; }
+      /* Backbone View */
+      else if (data.view) { data.content = data.view.render().el;}
+
+      $(this.el).html(this.tmpl(data));
+      $(this.el).find(".wizard-right-panel_content").html(data.content);
+
+      /* OnLoad Callback */
+      this.onLoad();
+
+      return this;
+    },
+    nextPage: function() {
+      this.options.curr_page.el = this.$el;
+      if (!this.beforeNext()) { return false; }
+
+      page_id = this.onNext();
+
+      if (page_id ) {
+        this.currPage = this.collection.get(page_id).toJSON();
+        this.options.curr_page = this.collection.indexOf(this.collection.get(page_id));
+      }
+      else if (this.options.curr_page < (this.collection.length-1)) {
+        this.options.curr_page = this.options.curr_page + 1;
+        this.currPage = this.collection.at(this.options.curr_page).toJSON();
+      }
+
+      this.enableDisableNext();
+      this.enableDisablePrev();
+
+      return this.render();
+    },
+    prevPage: function() {
+      if (!this.beforePrev()) { return false; }
+
+      page_id = this.onPrev();
+
+      if (page_id){
+        this.currPage = this.collection.get(page_id).toJSON();
+        this.options.curr_page = this.collection.indexOf(this.collection.get(page_id));
+      }
+      else if (this.options.curr_page > 0) {
+        this.options.curr_page = this.options.curr_page - 1;
+        this.currPage = this.collection.at(this.options.curr_page).toJSON();
+      }
+
+      this.enableDisableNext();
+      this.enableDisablePrev();
+
+      return this.render();
+    },
+    finishWizard: function() {
+      this.onFinish();
+      this.remove(); // Remove view from DOM
+      this.unbind(); // Unbind all local event bindings
+      delete this.$el; // Delete the jQuery wrapped object variable
+      delete this.el; // Delete the variable reference to this node
+      return true;
+    },
+    enableDisableNext: function(disable) {
+        if (typeof(disable) != 'undefined') {
+          this.options.disable_next = disable;
+        }
+        else if (this.options.curr_page >= (this.collection.length-1)) {
+          this.options.disable_next = true;
+        }
+        else {
+          this.options.disable_next = false;
+        }
+    },
+    enableDisablePrev: function(disable) {
+        if (typeof(disable) != 'undefined') {
+          this.options.disable_prev = disable;
+        }
+        else if (this.options.curr_page <= 0) {
+          this.options.disable_prev = true;
+        }
+        else {
+          this.options.disable_prev = false;
+        }
+    },
+    beforeNext: function(){
+      return this.evalASFunc(this.currPage.beforeNext);
+    },
+    beforePrev: function(){
+      return this.evalASFunc(this.currPage.beforePrev);
+    },
+    onPrev: function(){
+      return this.evalASFunc(this.currPage.onPrev);
+    },
+    onNext: function(){
+      return this.evalASFunc(this.currPage.onNext);
+    },
+    onLoad: function() {
+      return this.evalASFunc(this.currPage.onLoad);
+    },
+    onFinish: function() {
+      return true;
+    },
+    onCancel: function() {
+      this.$el.remove();
+      return true;
+    },
+    evalASFunc: function(func, ctx) {
+      var self = this;
+      ctx = ctx || self.currPage;
+
+      return (_.isFunction(func) ? func.apply(ctx, [self]) : func);
+    }
+  });
+
+  return pgBrowser;
+
+});
diff --git a/web/pgadmin/static/css/overrides.css b/web/pgadmin/static/css/overrides.css
index 2fcaae2..f1de6a5 100755
--- a/web/pgadmin/static/css/overrides.css
+++ b/web/pgadmin/static/css/overrides.css
@@ -45,6 +45,10 @@ body {
     background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAVElEQVQY05WQuQ0AMQgEB8lN0H9vdhnj4B7ZJwfcJkjsImBCpaIGMMYQIDNjNbe+Su9d1bsee5yM0+AbXMPfkHrdWNKv1ZVn2oJiw5OZ8eABiCrwCW8QwRIof5qAAAAAAElFTkSuQmCC) !important;
 }
 
+.alertify .ajs-modal {
+    overflow: hidden;
+}
+
 /* iFrames should have no border */
 iframe {
     border-width: 0;
diff --git a/web/pgadmin/tools/grant_wizard/__init__.py b/web/pgadmin/tools/grant_wizard/__init__.py
new file mode 100644
index 0000000..f53b979
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/__init__.py
@@ -0,0 +1,461 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Implements Grant Wizard"""
+
+import simplejson as json
+from flask import render_template, request, current_app
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_response as ajax_response, \
+    make_json_response, internal_server_error
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from pgadmin.browser.server_groups.servers.utils import parse_priv_to_db
+from pgadmin.utils import PgAdminModule
+from flask import Response, url_for
+from flask.ext.security import login_required
+from urllib import unquote
+from pgadmin.utils.ajax import precondition_required
+from functools import wraps
+from pgadmin.utils.preferences import Preferences
+
+# set template path for sql scripts
+MODULE_NAME = 'grant_wizard'
+server_info = {}
+
+
+class GrantWizardModule(PgAdminModule):
+    """
+    class GrantWizardModule(Object):
+
+        It is a wizard which inherits PgAdminModule
+        class and define methods to load its own
+        javascript file.
+
+    LABEL = gettext('Browser')
+    """
+    def get_own_stylesheets(self):
+        """
+        Returns:
+            list: the stylesheets used by this module.
+        """
+        stylesheets = [
+            url_for('browser.static', filename='css/wizard.css'),
+            url_for('grant_wizard.static', filename='css/grant_wizard.css')
+            ]
+        return stylesheets
+
+    def get_own_javascripts(self):
+        """"
+        Returns:
+            list: js files used by this module
+        """
+        scripts = []
+        scripts.append({
+            'name': 'pgadmin.tools.grant_wizard',
+            'path': url_for('grant_wizard.index') + 'grant_wizard',
+            'when': None
+        })
+        scripts.append({
+            'name': 'pgadmin.browser.wizard',
+            'path': url_for('browser.static', filename='js/wizard'),
+            'when': None
+        })
+        return scripts
+
+    def show_system_objects(self):
+        """
+        return system preference objects
+        """
+        return self.pref_show_system_objects
+
+    def register_preferences(self):
+        """
+        Get show_system_objects preference
+        """
+        self.browser_preference = Preferences.module('browser')
+        self.pref_show_system_objects = self.browser_preference.preference(
+                'show_system_objects'
+                )
+
+# Create blueprint for GrantWizardModule class
+blueprint = GrantWizardModule(
+    MODULE_NAME, __name__, static_url_path='')
+
+
+def check_precondition(f):
+    """
+    This function will behave as a decorator which will checks
+    database connection before running view, it will also attaches
+    manager,conn & template_path properties to instance of the method.
+
+    Assumptions:
+        This function will always be used as decorator of a class method.
+    """
+    @wraps(f)
+    def wrap(*args, **kwargs):
+        # Here args[0] will hold self & kwargs will hold gid,sid,did
+
+        server_info.clear()
+        server_info['manager'] = get_driver(
+            PG_DEFAULT_DRIVER).connection_manager(
+                kwargs['sid']
+        )
+        server_info['conn'] = server_info['manager'].connection(
+            did=kwargs['did']
+        )
+        # If DB not connected then return error to browser
+        if not server_info['conn'].connected():
+            return precondition_required(
+                gettext("Connection to the server has been lost!")
+            )
+
+        # Set template path for sql scripts
+        server_info['server_type'] = server_info['manager'].server_type
+        server_info['version'] = server_info['manager'].version
+        if server_info['server_type'] == 'pg':
+            server_info['template_path'] = 'grant_wizard/pg/9.1_plus'
+        elif server_info['server_type'] == 'ppas':
+            server_info['template_path'] = 'grant_wizard/ppas/9.1_plus'
+
+        return f(*args, **kwargs)
+
+    return wrap
+
+
[email protected]("/")
+@login_required
+def index():
+    pass
+
+
[email protected]("/grant_wizard.js")
+@login_required
+def script():
+    """render own javascript"""
+    return Response(response=render_template(
+                "grant_wizard/js/grant_wizard.js", _=gettext),
+                status=200,
+                mimetype="application/javascript")
+
+
[email protected](
+    '/acl/<int:gid>/<int:sid>/<int:did>/', methods=('GET', 'POST'))
+@login_required
+@check_precondition
+def acl_list(gid, sid, did):
+    """render list of acls"""
+    server_prop = server_info
+    return Response(response=render_template(
+                server_prop['template_path']+"/acl.json", _=gettext),
+                status=200,
+                mimetype="application/json")
+
+
[email protected](
+    '/properties/<int:gid>/<int:sid>/<int:did>'
+    '/<int:node_id>/<node_type>/',
+    methods=('GET', 'POST'))
+@login_required
+@check_precondition
+def properties(gid, sid, did, node_id, node_type):
+    """It fetches the properties of object types
+       and render into selection page of wizard
+    """
+
+    # unquote encoded url parameter
+    node_type = unquote(node_type)
+
+    server_prop = server_info
+
+    res_data = []
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    node_types = []
+    nspname = ''
+    show_sysobj = blueprint.show_system_objects().get()
+    if node_type == 'database':
+
+        # Fetch list of schemas
+        # Get sys_obj_values and get list of schemas
+        ntype = 'schema'
+        SQL = render_template("/".join(
+            [server_prop['template_path'], '/sql/get_schemas.sql']),
+            show_sysobj=show_sysobj)
+        status, res = conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+        node_types = res['rows']
+    else:
+        SQL = render_template("/".join(
+            [server_prop['template_path'], '/sql/get_schemas.sql']),
+              nspid=node_id, show_sysobj=False)
+        status, res = conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+        node_types = res['rows']
+        ntype = node_type
+        nspname = node_types[0]['name']
+
+    for row in node_types:
+        if 'oid' in row:
+            node_id = row['oid']
+            nspname = row['name']
+
+        # Fetch functions against schema
+        if ntype in ['schema', 'function']:
+            SQL = render_template("/".join(
+                [server_prop['template_path'], '/sql/function.sql']),
+                node_id=node_id, nspname=nspname, type='function')
+
+            status, res = conn.execute_dict(SQL)
+
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            res_data.extend(res['rows'])
+
+        # Fetch trigger functions
+        if ntype in ['schema', 'trigger_function']:
+            SQL = render_template("/".join(
+                [server_prop['template_path'], '/sql/function.sql']),
+                node_id=node_id, nspname=nspname, type='trigger_function')
+            status, res = conn.execute_dict(SQL)
+
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            res_data.extend(res['rows'])
+
+        # Fetch Sequences against schema
+        if ntype in ['schema', 'sequence']:
+            SQL = render_template("/".join(
+                [server_prop['template_path'], '/sql/sequence.sql']),
+                node_id=node_id, nspname=nspname)
+
+            status, res = conn.execute_dict(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+            res_data.extend(res['rows'])
+
+        # Fetch Tables against schema
+        if ntype in ['schema', 'table']:
+            SQL = render_template("/".join(
+                [server_prop['template_path'], '/sql/table.sql']),
+                node_id=node_id, nspname=nspname)
+
+            status, res = conn.execute_dict(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            res_data.extend(res['rows'])
+
+        # Fetch Views against schema
+        if ntype in ['schema', 'view']:
+            SQL = render_template("/".join(
+                [server_prop['template_path'], '/sql/view.sql']),
+                node_id=node_id, nspname=nspname)
+
+            status, res = conn.execute_dict(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            res_data.extend(res['rows'])
+
+    return ajax_response(
+            response=res_data,
+            status=200
+            )
+
+
[email protected](
+    '/msql/<int:gid>/<int:sid>/<int:did>/',
+    methods=('GET', 'POST'))
+@login_required
+@check_precondition
+def msql(gid, sid, did):
+    """
+    This function will return modified SQL
+    """
+
+    server_prop = server_info
+    data = {}
+    for k, v in request.args.items():
+        try:
+            data[k] = json.loads(v)
+        except ValueError:
+            data[k] = v
+
+    # Form db connection
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    acls = []
+    try:
+        acls = render_template(
+            "/".join([server_prop['template_path'], '/acl.json'])
+            )
+        acls = json.loads(acls)
+    except Exception as e:
+        current_app.logger.exception(e)
+
+    try:
+
+        # Parse privileges
+        data['priv'] = {}
+        if 'acl' in data:
+            # Get function acls
+            data['priv']['function'] = parse_priv_to_db(
+                data['acl'],
+                acls['function']['acl'])
+
+            data['priv']['sequence'] = parse_priv_to_db(
+                data['acl'],
+                acls['sequence']['acl'])
+
+            data['priv']['table'] = parse_priv_to_db(
+                data['acl'],
+                acls['table']['acl'])
+
+        # Pass database objects and get SQL for privileges
+        SQL_data = ''
+        data_func = {}
+        data_func['objects'] = data['objects']
+        data_func['priv'] = data['priv']['function']
+        SQL = render_template(
+              "/".join([server_prop['template_path'],
+                        '/sql/grant_function.sql']),
+              data=data_func, conn=conn)
+        if SQL and SQL.strip('\n') != '':
+            SQL_data += SQL
+
+        data_seq = {}
+        data_seq['objects'] = data['objects']
+        data_seq['priv'] = data['priv']['sequence']
+        SQL = render_template(
+              "/".join([server_prop['template_path'],
+                        '/sql/grant_sequence.sql']),
+              data=data_seq, conn=conn)
+        if SQL and SQL.strip('\n') != '':
+            SQL_data += SQL
+
+        data_table = {}
+        data_table['objects'] = data['objects']
+        data_table['priv'] = data['priv']['table']
+        SQL = render_template(
+              "/".join([server_prop['template_path'], '/sql/grant_table.sql']),
+              data=data_table, conn=conn)
+        if SQL and SQL.strip('\n') != '':
+            SQL_data += SQL
+
+        res = {'data': SQL_data}
+
+        return ajax_response(
+            response=res,
+            status=200
+            )
+
+    except Exception as e:
+        return make_json_response(
+            status=410,
+            success=0,
+            errormsg=e.message
+        )
+
+
[email protected](
+    '/save/<int:gid>/<int:sid>/<int:did>/',
+    methods=('GET', 'POST'))
+@login_required
+@check_precondition
+def save(gid, sid, did):
+    """
+    This function will apply the privileges to the selected
+    Database Objects
+    """
+    server_prop = server_info
+    data = request.form if request.form else json.loads(request.data.decode())
+
+    # Form db connection and we use conn to execute sql
+    manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+    conn = manager.connection(did=did)
+
+    acls = []
+    try:
+        acls = render_template(
+              "/".join([server_prop['template_path'], 'acl.json']),
+            )
+        acls = json.loads(acls)
+    except Exception as e:
+        current_app.logger.exception(e)
+
+    try:
+
+        # Parse privileges
+        data['priv'] = {}
+        if 'acl' in data:
+            # Get function acls
+            data['priv']['function'] = parse_priv_to_db(
+                data['acl'],
+                acls['function']['acl'])
+
+            data['priv']['sequence'] = parse_priv_to_db(
+                data['acl'],
+                acls['sequence']['acl'])
+
+            data['priv']['table'] = parse_priv_to_db(
+                data['acl'],
+                acls['table']['acl'])
+
+        # Pass database objects and get SQL for privileges
+        # Pass database objects and get SQL for privileges
+        SQL_data = ''
+        data_func = {}
+        data_func['objects'] = data['objects']
+        data_func['priv'] = data['priv']['function']
+        SQL = render_template(
+              "/".join([server_prop['template_path'],
+                        '/sql/grant_function.sql']),
+              data=data_func, conn=conn)
+        if SQL and SQL.strip('\n') != '':
+            SQL_data += SQL
+
+        data_seq = {}
+        data_seq['objects'] = data['objects']
+        data_seq['priv'] = data['priv']['sequence']
+        SQL = render_template(
+              "/".join([server_prop['template_path'],
+                        '/sql/grant_sequence.sql']),
+              data=data_seq, conn=conn)
+        if SQL and SQL.strip('\n') != '':
+            SQL_data += SQL
+
+        data_table = {}
+        data_table['objects'] = data['objects']
+        data_table['priv'] = data['priv']['table']
+        SQL = render_template(
+              "/".join([server_prop['template_path'], '/sql/grant_table.sql']),
+              data=data_table, conn=conn)
+        if SQL and SQL.strip('\n') != '':
+            SQL_data += SQL
+
+        status, res = conn.execute_dict(SQL_data)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        return make_json_response(
+            success=1,
+            info="Privileges applied"
+        )
+
+    except Exception as e:
+        return internal_server_error(errormsg=e.message)
diff --git a/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
new file mode 100644
index 0000000..18240e9
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
@@ -0,0 +1,109 @@
+/** Grant Wizard CSS **/
+
+/**
+  CSS to make db object type table
+  fixed so that tbody content may
+  scroll
+ */
+
+.db_objects_container  {
+  height: 100%;
+}
+
+.object_type_table {
+  display: inline-block;
+  height: 100%;
+  border: 0 !important;
+}
+
+.object_type_table thead tr {
+  position: relative;
+  display: block;
+  width: 100%;
+}
+
+.object_type_table tbody {
+  display: block;
+  overflow: scroll;
+  border: 1px solid #ddd;
+  width: 100%;
+  min-height: 100%;
+  max-height: 66%;
+  height: 66%;
+}
+
+.object_type_table tbody tr {
+  display: table;
+  max-width: 100%;
+  width: 100%;
+}
+
+.object_type_table tbody tr td {
+  background-position: 4px 4px;
+  border-radius: 0;
+}
+
+.object_type_table tbody tr td:nth-child(1),
+.object_type_table thead tr th:nth-child(1) {
+  width: 28px;
+  min-width: 28px;
+}
+
+.object_type_table tbody tr td:nth-child(2) {
+  width: 161px;
+  min-width: 161px;
+  max-width: 161px;
+}
+
+.object_type_table thead tr th:nth-child(2) {
+  width: 161px;
+  min-width: 161px;
+  max-width: 161px;
+}
+
+.object_type_table tbody tr td:nth-child(3) {
+  width: 109px;
+  min-width: 109px;
+  max-width: 109px;
+}
+
+.object_type_table thead tr th:nth-child(3) {
+  width: 109px;
+  min-width: 109px;
+  max-width: 109px;
+}
+
+.object_type_table thead tr th:nth-child(4) {
+  width: 100%;
+}
+
+.object_type_table tbody tr td:nth-child(4) {
+  width: 100%;
+  max-width: 100%;
+}
+
+/** Override Backgrid filter CSS **/
+.db_objects_container .backgrid-filter.form-search {
+  float: left;
+  margin: 0px 5px 5px 0;
+}
+
+/** Custom styling for Codemirror field **/
+.wizard-right-panel_content {
+  border: 1px solide #ccc;
+}
+
+.wizard-right-panel_content .CodeMirror {
+  border: 1px solid #ccc;
+  height: 285px !important;
+  min-height: 285px !important;
+}
+
+.wizard-right-panel_content .CodeMirror-linenumber {
+  background: #F7F7F7;
+  border-right: 1px solid #DDDDDD;
+}
+
+.wizard-right-panel_content .CodeMirror-gutters {
+  min-height: 285px !important;
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
new file mode 100644
index 0000000..2acb162
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
@@ -0,0 +1,1101 @@
+define([
+      'jquery', 'underscore', 'underscore.string', 'alertify',
+      'pgadmin.browser', 'backbone', 'backgrid', 'pgadmin.browser.node',
+      'backgrid.select.all', 'backgrid.filter', 'pgadmin.browser.server.privilege',
+      'pgadmin.browser.wizard',
+      ],
+
+  // This defines Grant Wizard dialog
+  function($, _, S, alertify, pgBrowser, Backbone, Backgrid, pgNode) {
+
+    // if module is already initialized, refer to that.
+    if (pgBrowser.GrantWizard) {
+      return pgBrowser.GrantWizard;
+    }
+
+    /**
+      It is sub model for field "Objects". It has fields
+      for database object types such as Schemas, Views and
+      Sequence etc.
+    */
+    var DatabaseObjectModel = pgNode.Model.extend({
+      defaults: {
+        selected: false,
+        icon: 'icon-unknown',
+        name: undefined,
+        name_with_args: undefined,
+        nspname: undefined,
+        proargs: undefined,
+        object_type: undefined,
+        object_id: undefined
+      },
+      idAttribute: 'object_id', // to uniquely identify a model object
+      toJSON: function(obj) {
+        var d = pgNode.Model.prototype.toJSON.apply(this);
+        delete d.icon;
+        return d;
+      },
+      parse: function(res) {
+
+        // Create unique object id
+        res.object_id = res.name_with_args;
+
+        // create name with args if its object is function
+        if(!_.isUndefined(res.object_type) && (res.object_type == 'Function' ||
+          res.object_type == 'Trigger Function'))
+          res.name_with_args = res.name+'('+(typeof(res.proargs) != 'undefined' ? res.proargs : '')+')';
+        else
+          res.name_with_args = res.name;
+
+        return res;
+      },
+
+      validate: function() {
+
+        /*
+        * Triggers error messages for object types "selected"
+        * if it is empty/undefined/null
+        */
+        var err = {},
+          errmsg,
+          node = this.get('objects').toJSON();
+        if (_.isEmpty(node)) {
+          err['selected'] = '{{ _("Please select any database object type") }}';
+          errmsg = errmsg || err['selected'];
+          this.errorModel.set('selected', errmsg);
+          return errmsg;
+        } else {
+          this.errorModel.unset('selected');
+        }
+        return null;
+      }
+    });
+
+    // Define columns for the Db Object Types grid
+    var columns = [{
+      name: "selected",
+
+      /*
+      Override render method of Backgrid.Extension.SelectRowCell
+      class. It has an issue: It doesn't mark rows checked if we move to next
+      page and then go back to previous page. but it must show.
+      so we handle this case by overriding the render method.
+      */
+      cell: Backgrid.Extension.SelectRowCell.extend({
+        render: function() {
+
+          // Use the Backform Control's render function
+          Backgrid.Extension.SelectRowCell.prototype.render.apply(this, arguments);
+
+          var col = this.column.get('name');
+          if (this.model && this.model.has(col)) {
+            if (this.model.get(col)) {
+              this.checkbox().prop("checked", true);
+              this.$el.parent().toggleClass("selected", true);
+              this.model.trigger("backgrid:selected", this.model, true);
+            }
+          }
+          return this;
+        }
+      }),
+
+      headerCell: "select-all",
+
+    },{
+      name: "object_type",
+      label: "Object Type",
+      editable: false,
+      cell: Backgrid.Cell.extend({
+        render: function() {
+
+          // Override render to add icon to Db Object column
+          Backgrid.Cell.prototype.render.apply(this, arguments);
+          this.$el.addClass(this.model.get('icon')).css({"padding-left": "24px"});
+
+          return this;
+        }
+      })
+    },{
+      name: "nspname",
+      label: "Schema",
+      cell: "string",
+      editable: false
+    },{
+      name: "name_with_args",
+      label: "Name",
+      cell: "string",
+      editable: false
+    }];
+
+    // Create an Object GrantWizard of pgBrowser class
+    pgBrowser.GrantWizard  = {
+      init: function() {
+        if (this.initialized)
+          return;
+
+        this.initialized = true;
+
+        // Define list of nodes on which grant wizard context menu option appears
+        var supported_nodes = [
+              'schema', 'coll-function', 'coll-sequence',
+              'coll-table', 'coll-view',
+              'coll-materialized_view', 'database'
+            ],
+
+            /**
+              Enable/disable grantwizard menu in tools based
+              on node selected
+              if selected node is present in supported_nodes,
+              menu will be enabled otherwise disabled.
+              Also, hide it for system view in catalogs
+            */
+            menu_enabled = function(itemData, item, data) {
+              var t = pgBrowser.tree, i = item, d = itemData;
+              var parent_item = t.hasParent(i) ? t.parent(i): null,
+                  parent_data = parent_item ? t.itemData(parent_item) : null;
+              if(!_.isUndefined(d) && !_.isNull(d) && !_.isNull(parent_data))
+                return ((_.indexOf(supported_nodes, d._type) !== -1 && parent_data._type != 'catalog') ? true: false);
+              else
+                return false;
+            };
+
+        // Define the nodes on which the menus to be appear
+        var menus = [{
+          name: 'grant_wizard_schema', module: this,
+          applies: ['tools'], callback: 'start_grant_wizard',
+          priority: 10, label: '{{_("Grant Wizard...") }}',
+          icon: 'wcTabIcon', enable: menu_enabled
+        }];
+
+        // Add supported menus into the menus list
+        for (var idx = 0; idx < supported_nodes.length; idx++) {
+          menus.push({
+            name: 'grant_wizard_schema_context_' + supported_nodes[idx],
+            node: supported_nodes[idx], module: this,
+            applies: ['context'], callback: 'start_grant_wizard',
+            priority: 10, label: '{{_("Grant Wizard...") }}',
+            icon: 'wcTabIcon', enable: menu_enabled
+            });
+        }
+        pgAdmin.Browser.add_menus(menus);
+
+        return this;
+      },
+
+      // Callback to draw Wizard Dialog
+      start_grant_wizard: function(action, item) {
+
+        // Declare Wizard dialog
+        if (!alertify.wizardDialog) {
+          alertify.dialog('wizardDialog', function factory() {
+
+            // Generate wizard main container
+            var $container = $("<div class='wizard_dlg'></div>");
+
+            return {
+              main: function(title) {
+                this.set('title', title);
+              },
+              setup:function() {
+                return {
+
+                  // Set options for dialog
+                  options: {
+                    frameless: true,
+                    resizable: true,
+                    autoReset: false,
+                    maximizable: false,
+                    closableByDimmer: false
+                  }
+                };
+              },
+              hooks:{
+                onshow: function() {
+
+                  // Set dimensions for wizard
+                  this.elements.dialog.style.width = '100%';
+                  this.elements.dialog.style.height = '68%';
+
+                  // Add pgadmin_grant_wizard_body class to dialog
+                  $(this.elements.body).addClass('pgadmin_grant_wizard_body');
+                },
+              },
+
+              /**
+                Returns a Paginator Class Object which is again to be rendered
+
+                @class {Backgrid.Extension.Paginator}
+                @param {Backbone.Collection} coll - from which data is fetched
+                @return {Object} paginator
+              */
+              DbPaginator: function(coll){
+                var paginator = this.paginator = new Backgrid.Extension.Paginator({
+                  collection: coll,
+                  windowSize: 8
+                });
+                return paginator;
+              },
+
+              /**
+                Create new Filter which will filter the
+                rendered grid for Select Type Tabular Data
+                @param {Backbone.PageableCollection} coll
+              */
+              DbObjectFilter: function(coll){
+                var clientSideFilter = this.clientSideFilter = new Backgrid.Extension.ClientSideFilter({
+                  collection: coll,
+                  placeholder: _('Search by object type or name'),
+
+                  // The model fields to search for matches
+                  fields: ['object_type', 'name'],
+
+                  // How long to wait after typing has stopped before searching can start
+                  wait: 150
+                });
+                return clientSideFilter;
+              },
+
+              //Enable Disable Next button of PrivilegePage
+              updateButtons: function(modified){
+                if(!modified)
+                  $('.wizard-next').prop('disabled', true);
+                else
+                  $('.wizard-next').prop('disabled', false);
+              },
+
+              /**
+                Callback called when an errorModel is set
+                with invalid value and errormsg is set into
+                status bar element and next button is disabled
+              */
+              onSessionInvalid: function(msg) {
+                $('.error_msg_div p').html(msg).removeClass("hide");
+
+                // Enable disable Next button
+                this.updateButtons(false);
+                return true;
+              },
+
+              /**
+                Callback called when anything is set into model
+                thus hide error msg element and enable next button
+                status bar element and next button is disabled
+              */
+              onSessionValidated: function(sessHasChanged) {
+                $('.error_msg_div p').empty().addClass("hide");
+
+                // Enable disable Next button
+                this.updateButtons(sessHasChanged);
+              },
+
+              /**
+                Remove/Delete objects, attributes
+                in wizard on wizard close or finish
+                to reclaim memory
+              */
+              releaseObjects: function(){
+                var self = this;
+
+                if(!_.isUndefined(self.dbObjectFilter)) {
+                  self.dbObjectFilter.remove();
+                  self.dbObjectFilter = undefined;
+                }
+
+                if(!_.isUndefined(self.clientSideFilter)) {
+                  self.clientSideFilter.remove();
+                  self.clientSideFilter = undefined;
+                }
+
+                // clear object priv array
+                if(!_.isNull(self.obj_priv) &&
+                  !_.isUndefined(self.obj_priv)) {
+                  self.obj_priv = [];
+                  delete self.obj_priv;
+                }
+
+                // Delete Wizard Pages, clear model and cleanup view
+                if(!_.isUndefined(self.dbObjectTypePage) &&
+                  !_.isNull(self.dbObjectTypePage)) {
+                  if(!_.isUndefined(self.dbObjectTypePage.get('model')) &&
+                    !_.isNull(self.dbObjectTypePage.get('model'))) {
+                    self.dbObjectTypePage.get('model').clear();
+                    self.dbObjectTypePage.get('view').cleanup();
+                    self.dbObjectTypePage = undefined;
+                  }
+                }
+
+                if(!_.isUndefined(self.privilegePage) &&
+                  !_.isNull(self.privilegePage)) {
+                  if(!_.isUndefined(self.privilegePage.get('model')) &&
+                    !_.isNull(self.privilegePage.get('model'))) {
+                    self.privilegePage.get('model').clear();
+                    self.privilegePage.get('view').cleanup();
+                    self.privilegePage = undefined;
+                  }
+                }
+
+                if(!_.isUndefined(self.reviewSQLPage) &&
+                  !_.isNull(self.reviewSQLPage)) {
+                  if(!_.isUndefined(self.reviewSQLPage.get('model')) &&
+                    !_.isNull(self.reviewSQLPage.get('model'))) {
+                    self.reviewSQLPage.get('model').clear();
+                    self.reviewSQLPage = undefined;
+                  }
+                }
+
+                // Remove Sql control
+                if (!_.isUndefined(self.sqlControl)) {
+                  self.sqlControl.remove();
+                }
+
+                // Clear privModel
+                if(!_.isNull(self.privModel) &&
+                  !_.isUndefined(self.privModel)) {
+                  self.privModel.clear();
+                }
+
+                // Remove collection containing db object data
+                if(!_.isNull(self.coll) &&
+                  !_.isUndefined(self.coll)) {
+                  self.coll.reset();
+                  self.coll = undefined;
+                }
+                // Delete Wizard
+                if(!_.isNull(self.wizard) &&
+                  !_.isUndefined(self.wizard)) {
+                  self.wizard.collection.reset();
+                  self.wizard.curr_page = undefined;
+                }
+
+              },
+
+              /**
+                Every time a wizard is opened, this function
+                is called everytime. It has Wizard Pages which
+                are rendered by the Wizard Class:
+
+                @class {pgBrowser.WizardPage} dbObjectType1 - This page
+                @extends {Backbone.Model}
+                renders a grid of Database Object Types such as
+                  Schemas, Views and Sequences etc.
+
+                @class {pgBrowser.WizardPage} WizardPage2 - This page
+                @extends {Backbone.Model}
+                adds Privilege Control which provides grant privileges
+                such as "Create, Insert, Delete, Update" so on the
+                database objects selected on Wizard Pages.
+
+                @class {pgBrowser.WizardPage} WizardPage3 - This page
+                displays the generated GRANT SQL query for the Db
+                objects selected with the specific privileges added to it.
+                @extends {Backbone.Model}
+
+                @class {Backbone.Collection} WizardCollection - It is the
+                collection of wizard pages
+
+                @class {pgBrowser.Wizard} wizard - Its task is:
+                - Create a Wizard
+                - Add Buttons, Callbacks to it.
+                - Render WizardPages
+                @extends {Backbone.View}
+
+              */
+              build: function() {
+                this.elements.content.appendChild($container.get(0));
+              },
+
+              //Returns list of Acls defined for nodes
+              get_json_data: function(gid, sid, did) {
+                var url = "{{ url_for('grant_wizard.index') }}" + "acl/" +
+                    S('%s/%s/%s/').sprintf(
+                        encodeURI(gid), encodeURI(sid), encodeURI(did)).value();
+                return $.ajax({
+                  async: false,
+                  url: url,
+                  dataType: 'jsonp'
+                });
+
+              },
+              prepare:function() {
+
+                $container.empty().append("<div class='grant_wizard_container'></div>");
+
+                // Define el for wizard view
+                var el = $('.grant_wizard_container');
+
+                // Extract the data from the selected tree node
+                var t = pgBrowser.tree,
+                    i = t.selected(),
+                    d = this.d = i && i.length == 1 ? t.itemData(i) : undefined,
+                    info = this.info = pgBrowser.Node.getTreeNodeHierarchy(i),
+                    icon = d.icon;
+
+                /**
+                  Generate a URL using:
+                  gid, did, sid(server id), node_id(node id),
+                  node_(node name), node_type(node type)
+                  and pass it to collection which will fetch Object Type properties.
+                */
+                var gid = info['server-group']._id,
+                    sid = info.server._id,
+                    did = info.database._id,
+                    node_id = d._id,
+
+                    /**
+                      get node name only. used in mapping with object types defined
+                      in allowed_acl.json
+                     */
+                    node_type = d._type.replace('coll-', '').replace('materialized_', ''),
+                    node_label = d.label;
+
+                // Fetch privileges specific to nodes
+                var json_data = this.get_json_data(gid, sid, did);
+                var privDict = JSON.parse(json_data.responseText);
+
+                // Collection url to fetch database object types for objects field
+                var baseUrl = "{{ url_for('grant_wizard.index') }}" + "properties/" +
+                    S('%s/%s/%s/%s/%s/').sprintf(
+                        encodeURI(gid), encodeURI(sid), encodeURI(did),
+                        encodeURI(node_id), encodeURI(node_type)).value();
+
+                    // Model's save url
+                    saveUrl = "{{ url_for('grant_wizard.index') }}" + "save/" +
+                        S('%s/%s/%s/').sprintf(
+                            encodeURI(gid), encodeURI(sid),
+                            encodeURI(did)).value(),
+
+                    // generate encoded url based on wizard type
+                    msql_url = this.msql_url = "/grant_wizard/msql/"+
+                      S('%s/%s/%s/').sprintf(
+                          encodeURI(gid), encodeURI(sid),
+                          encodeURI(did)).value(),
+
+                    Coll = Backbone.Collection.extend({
+                      model: DatabaseObjectModel,
+                      url: baseUrl
+                    }),
+
+                    // Create instances of collection and filter
+                    coll = this.coll = new Coll(),
+
+                    coll.comparator = function(model) {
+                      return model.get('object_type');
+                    }
+
+                    coll.sort();
+                    dbObjectFilter = this.dbObjectFilter = this.DbObjectFilter(coll);
+
+                /**
+                  privArray holds objects selected which further helps
+                  in creating privileges Model
+                */
+                var self = this;
+                self.privArray = [];
+
+                /**
+                  Override backgrid listener "backgrid:selected" to
+                  Add/Remove model to/from objects collection
+                */
+                coll.on('backgrid:selected', function(model, selected) {
+                  model.set('selected', selected);
+
+                  var object_type = model.get('object_type');
+                  switch (object_type)
+                  {
+                    case 'Function':
+                      object_type = 'function';
+                      break;
+                    case 'Trigger Function':
+                      object_type = 'function';
+                      break;
+                    case 'Table':
+                      object_type = 'table';
+                      break;
+                    case 'Sequence':
+                      object_type = 'sequence';
+                      break;
+                    case 'View':
+                      object_type = 'table';
+                      break;
+                  }
+
+                  /**
+                    if a row (checkbox) is checked, add that model
+                    into collection, when unchecked remove it from
+                    model.
+
+                    Also push/pop object type in/from privArray
+                  */
+                  if(selected) {
+                    if(_.indexOf(self.privArray, object_type) == -1)
+                      self.privArray.push(object_type);
+                    newModel.get('objects').add(model, { silent: true });
+                  }
+                  else {
+                    var idx = self.privArray.indexOf(object_type);
+                    if(idx !=-1)
+                      self.privArray.splice(idx, 1);
+                    newModel.get('objects').remove(model);
+                  }
+
+                  // validate model on checkbox check/uncheck
+                  var msg =  model.validate.call(newModel);
+
+                  /**
+                    If no object type is selected, set error msg
+                    and disable next button, else enable next button
+                  */
+                  if(msg)
+                    self.onSessionInvalid.call(self, msg);
+                  else
+                    self.onSessionValidated.call(self, true);
+                });
+
+                /**
+                  It is the main model with schema defined
+                  Every time a new wizard is opened,
+                  a new model should create.
+                */
+                var GrantWizardModel = pgNode.Model.extend({
+                  defaults: {
+                    objects: undefined,
+                    acl: undefined
+                  },
+                  schema: [
+                    {
+                      id: 'objects', label: '{{ _("Objects") }}', model: DatabaseObjectModel,
+                      type: 'collection', group: 'Objects'
+                    },
+                    {
+                      id: 'acl', label: '{{ _("Privileges") }}',
+                      model: pgAdmin.Browser.Node.PrivilegeRoleModel,
+                      type: 'collection', canAdd: true,
+                      canDelete: true, control: 'unique-col-collection'
+                    }
+                  ],
+                  urlRoot: saveUrl
+                });
+
+                /**
+                  Create instance of GrantWizard Model, provide urlRoot
+                  node_info object, Generate fields objects
+                */
+                var newModel = new GrantWizardModel({}, { node_info: info });
+
+                /**
+                  Fetch data from server and set into grid
+                  and show/hide progress bar
+                */
+                $('.wizard-progress-bar p').show();
+
+                coll.fetch({
+                  success: function(collection, data) {
+                    $('.wizard-progress-bar p').html('');
+                  },
+                  reset: true
+                }, this);
+
+                //////////////////////////////////////////////////////////////////////
+                //                                                                  //
+                //            Wizard Page for Db Object Type                        //
+                //                                                                  //
+                //////////////////////////////////////////////////////////////////////
+
+                /**
+                  Create wizard page. It renders a grid of
+                  Database Object Types such as
+                  Schemas, Views and Sequences etc.
+                  Set default values
+                */
+                var dbObjectTypePage = self.dbObjectTypePage = new pgBrowser.WizardPage({
+                  id: 1,
+                  page_title: _('Object Selection (step 1 of 3)'),
+                  disable_prev: true,
+                  disable_next: true,
+                  show_description: _('Please select objects from the list below.'),
+                  show_progress_bar: _('Please wait while fetching records...'),
+                  model: newModel,
+                  view: new (function() {
+
+                    // Set page Instance
+                    var pageView = this;
+
+                    _.extend(pageView, {
+
+                      // Remove grid if it is before render
+                      cleanup: function() {
+                        if (this.grid) {
+                          this.grid.remove();
+                          delete this.grid;
+                          this.grid = null;
+                        }
+
+                        // Remove grid element if exists
+                        if (this.el) {
+                          $(this.el).remove();
+                          delete this.el;
+                        }
+                      },
+
+                      // Delete grid before render
+                      grid: null,
+
+                      render: function() {
+
+                        // Create a grid container
+                        var gridBody =
+                              $('<div class="db_objects_container"></div>');
+
+                        // Remove grid if exits before render
+                        if (this.grid) {
+                          this.cleanup();
+                        }
+
+                        // Initialize a new Grid instance
+                        this.grid = new Backgrid.Grid({
+                          columns: _.clone(columns),
+                          collection: coll,
+                          className: "backgrid table-bordered object_type_table"
+                          });
+
+                        // Render selection Type grid and paginator
+                        gridBody.append( this.grid.render().$el);
+
+                        // Render Search Filter
+                        gridBody.prepend(
+                          self.clientSideFilter.render().el);
+
+                        // Assign gridBody content to page element
+                        this.el = gridBody;
+
+                        /**
+                          Fetch selected models from collection and
+                          make rows checked in grid
+                        */
+                        newModel.get('objects').each(function(m) {
+                          var model = coll.get(m.get('object_id'));
+                          if (model) {
+                            coll.trigger('backgrid:selected', model, true);
+                          }
+                        });
+
+                        // Refresh grid to re render rows.
+                        coll.trigger('backgrid:refresh');
+
+                        return this;
+                      }
+                    });
+                  }),
+
+                  beforeNext: function(obj){
+                    var self = this;
+                    obj.options.disable_next = true;
+
+                    /**
+                      Enable/Disable next button of privilegePage if objects
+                      are present in model
+                    */
+                    if(!_.isNull(newModel.get('acl')) &&
+                      !_.isUndefined(newModel.get('acl'))) {
+                      if(newModel.get('acl').length > 0)
+                        obj.collection.at(1).set('disable_next', false);
+                      }
+
+                    // Clean the view
+                    if (self.view) {
+                      self.view.cleanup();
+                      delete self.view;
+                      self.view = null;
+                    }
+                    return true;
+                  },
+
+                });
+
+                //////////////////////////////////////////////////////////////////////
+                //                                                                  //
+                //            Wizard Page for Privilege Control                     //
+                //                                                                  //
+                //////////////////////////////////////////////////////////////////////
+
+                // Wizard for Privelege control
+                var privilegePage = self.privilegePage = new pgBrowser.WizardPage({
+                  id: 2,
+                  page_title: _('Privileges Selection (step 2 of 3)'),
+                  show_description: _('Please select privileges for the selected objects.'),
+                  disable_next: true,
+                  model: newModel,
+
+                  // Create a view function object
+                  view: new (function() {
+                    var pageView = this;
+                      _.extend(pageView, {
+
+                        // Render Privelege control to generate its html markup
+                        render: function() {
+
+                          var obj_priv = [];
+                          self.privArray = _.uniq(self.privArray);
+                          _.each(self.privArray, function(priv){
+                            self.obj_priv = obj_priv = _.union(obj_priv , privDict[priv].acl);
+                          });
+
+                          /**
+                            Define PrivModel and its instance.
+                            Privileges array is generated based on
+                            the type of nodes selected.
+                           */
+                          var privModel = self.privModel;
+                          var PrivModel = pgNode.Model.extend({
+                            defaults: {
+                              acl: undefined
+                            },
+                            schema: [
+                              {
+                                id: 'acl', label: '{{ _("Privileges") }}',
+                                model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend({
+
+                                  // privileges are selected based on node clicked
+                                  privileges: obj_priv
+                                }), uniqueCol : ['grantee', 'grantor'], editable: true,
+                                type: 'collection', canAdd: true,
+                                canDelete: true, control: 'unique-col-collection'
+                              }
+                            ]
+                          });
+
+                          /**
+                            When privelege control is re-rendered, in order to
+                            render privileges based on object type selected,
+                            delete privileges from privModel which are now not
+                            present in object privileges array(object_priv)
+                           */
+                          var data = {};
+                          if (privModel) {
+                            data = privModel.toJSON();
+                            var rolePrivs = data['acl'];
+
+                            for (var idx in rolePrivs) {
+                              var rolePriv = (rolePrivs[idx])['privileges'],
+                                  removeIdx = [], j;
+
+                              for (j in rolePriv) {
+                                var p = rolePriv[j];
+                                if (_.indexOf(obj_priv, p['privilege_type']) == -1) {
+                                  removeIdx.push(j);
+                                }
+                              }
+
+                              for (j in removeIdx) {
+                                rolePriv.splice(j, 1);
+                              }
+                            }
+                          }
+
+                          // Instantiate privModel
+                          privModel = self.privModel = new PrivModel(data, { node_info: self.info });
+
+                          /*
+                          To track changes into model, start new session
+                          and Add event listener for privileges control
+                          */
+                          self.privModel.startNewSession();
+                          self.privModel.on('pgadmin-session:valid', self.onSessionValidated.bind(self));
+                          self.privModel.on('pgadmin-session:invalid', self.onSessionInvalid.bind(self));
+
+                        /**
+                          Create Field Object which has properties like
+                          node_data, node_info which is required for rendering
+                          Privilege control
+                          */
+                          var fields = Backform.generateViewSchema(
+                              self.info, self.privModel, 'create', self.d._type, self.d
+                              );
+                          var privilegesField = new Backform.Field(fields[0].fields[0]);
+
+                          this.privControl = new (privilegesField.get('control')) ({
+                            field: privilegesField,
+                            model: self.privModel
+                          });
+
+                          return {el: this.privControl.render().$el};
+                        },
+
+                        // Remove the privilege control
+                        cleanup: function() {
+                          if (this.privControl) {
+                            this.privControl.remove();
+                            delete this.privControl;
+                            this.privControl = null;
+                          }
+                        }
+                      });
+                  }),
+
+                  beforePrev: function(wizardObj) {
+
+                    // Remove the privilege control
+                    if (this.view) {
+                      this.view.cleanup();
+                      delete this.view;
+                      this.view = null;
+                    }
+
+                    /**
+                      Enable/Disable next button of DbObjectType page if objects
+                      are present in model
+                     */
+                    var objectsModel = newModel.get('objects');
+
+                    if(!_.isUndefined(objectsModel) && !_.isEmpty(objectsModel) &&
+                        objectsModel.length > 0) {
+                      wizardObj.collection.at(0).set('disable_next', false);
+
+                      // Don't show progress bar
+                      wizardObj.collection.at(0).set('show_progress_bar', '');
+                    }
+
+                    /**
+                      We're re-rendering the controls as they are deleted
+                      before heading to next page
+                      Refresh Backgrid to re-render the elements selected
+                      re-render Filter
+                    */
+                    newModel.trigger("backgrid:refresh", newModel, false);
+                    self.clientSideFilter.render();
+                    return true;
+                  },
+
+                  beforeNext: function() { return true; },
+
+                  onNext: function(obj){
+
+                    // Assign acls of privModel to main model newModel
+                    if (!_.isUndefined(self.privModel)) {
+                      newModel.set({'acl': self.privModel.get('acl')});
+                    }
+
+                    // Remove the privilege control
+                    if (this.view) {
+                      this.view.cleanup();
+                      delete this.view;
+                      this.view = null;
+                    }
+
+                    // Enable finish button
+                    self.wizard.options.disable_finish = false;
+
+                    /**
+                      triggers to get SQL queries data to render
+                      into the reviewSQLPage
+                    */
+                    newModel.trigger('pgadmin-wizard:nextpage:sql', {'node_type': node_type });
+                  }
+                });
+
+
+                //////////////////////////////////////////////////////////////////////
+                //                                                                  //
+                //            Review SQL Query Page                                 //
+                //                                                                  //
+                //////////////////////////////////////////////////////////////////////
+
+                //Create SqlField Object
+                var sqlField = new Backform.Field(
+                    {
+                      id: 'sqltab',
+                      label: _('Sql Tab'),
+
+                      /**
+                        Extend 'SqlTabControl' to define new
+                        function 'onWizardNextPageChange'
+                        which gets triggered on next button
+                        click to fetch generated SQL query
+                        for the selected db objects.
+                      */
+                      control: Backform.SqlTabControl.extend({
+                        initialize: function() {
+
+                          // Initialize parent class
+                          Backform.SqlTabControl.prototype.initialize.apply(this, arguments);
+
+                          this.msql_url = self.msql_url;
+
+                          // define trigger events for prev and next page
+                          this.model.on('pgadmin-wizard:nextpage:sql', this.onWizardNextPageChange, this);
+                          this.model.on('pgadmin-wizard:prevpage:sql', this.onWizardPrevPageChange, this);
+                        },
+
+                        // This method fetches the modified SQL for the wizard
+                        onWizardNextPageChange: function(){
+
+                          var self = this;
+
+                          // Fetches modified SQL
+                          $.ajax({
+                            url: this.msql_url,
+                            type: 'GET',
+                            cache: false,
+                            data: self.model.toJSON(true, 'GET'),
+                            dataType: "json",
+                            contentType: "application/json"
+                          }).done(function(res) {
+                            self.sqlTab.clearHistory();
+                            self.sqlTab.setValue(res.data);
+                          }).fail(function() {
+                            self.model.trigger('pgadmin-view:msql:error');
+                          }).always(function() {
+                            self.model.trigger('pgadmin-view:msql:fetched');
+                          });
+                        },
+
+                        remove: function() {
+
+                          // Clear html dom elements of CodeMirror sql tab
+                          self.sqlControl.unbind(); // Unbind all local event bindings
+                          var cmElem = self.sqlControl.sqlTab.getWrapperElement();
+                          cmElem.remove();
+                          self.sqlControl.sqlTab = undefined;
+                        }
+
+                      })
+                    }),
+
+                  /**
+                    Create sqlField view instance
+                    to render it into wizard page
+                  */
+                  sqlControl = self.sqlControl = new (sqlField.get('control'))({
+                    field: sqlField,
+                    model: newModel
+                  });
+
+                // Wizard for SQL tab control
+                var reviewSQLPage = self.reviewSQLPage = new pgBrowser.WizardPage({
+                  id: 3,
+                  page_title: _('Final (Review Selection) (step 3 of 3)'),
+                  show_description: _('The SQL below will be executed on the ' +
+                                'database server to grant the selected privileges. ' +
+                                'Please click on <b>Finish</b> to complete the process. '),
+                  model: newModel,
+                  view: new(function() {
+
+                    // Render SqlTab control to generate its html markup
+                    var sqlTabHtml = sqlControl.render().$el;
+                    this.render = function() {
+                        return { el: sqlTabHtml };
+                    };
+                  }),
+
+                  beforePrev: function(wizardObj) {
+
+                    /**
+                      Enable next button if privilege
+                      model is not empty else disable
+                      next button
+                     */
+                    var aclModel = newModel.get('acl');
+
+                    if(!_.isUndefined(wizardObj.collection) &&
+                      wizardObj.collection.models.length > 0) {
+                      if(!_.isUndefined(aclModel) && !_.isEmpty(aclModel) &&
+                          aclModel.length > 0) {
+                        wizardObj.collection.at(1).set('disable_next', false);
+                      }
+                      else {
+                        wizardObj.collection.at(1).set('disable_next', true);
+                      }
+
+                      return true;
+                    }
+                  },
+                });
+
+
+                // Create Wizard Collection of Wizard Pages
+                var WizardCollection = Backbone.Collection.extend({
+                  model: pgBrowser.WizardPage
+                });
+
+                // It holds all the wizard pages to be rendered
+                this.wizardCollection = new WizardCollection(
+                  [dbObjectTypePage, privilegePage, reviewSQLPage]
+                );
+
+                /**
+                  Create wizard which has following operations:
+                  - renders wizard pages
+                  - defines the first page to render in wizard
+                  - Save the model on finishbutton
+                  - Remove wizard on cancel button
+                  */
+                self.wizard = new (pgBrowser.Wizard.extend({
+                  options: {
+                    title: _('Grant Wizard'), /* Main Wizard Title */
+                    width: '',
+                    height: '',
+                    curr_page: 0,
+                    show_left_panel: false,
+                    disable_finish: true
+                  },
+
+                  // Callback for finish button
+                  onFinish: function() {
+                    var m = newModel,
+                        d = m.toJSON('GET');
+
+                    // Save model
+                    if (d && !_.isEmpty(d) && !_.isUndefined(d.objects)) {
+                      m.save({}, {
+                        attrs: d,
+                        validate: false,
+                        cache: false,
+                        success: function(res) {
+
+                          // Release wizard objects
+                          self.releaseObjects();
+                          self.close();
+                        },
+                        error: function(m, jqxhr) {
+                          alertify.pgNotifier(
+                            "error", jqxhr,
+                            S(
+                              "{{ _('Error during saving properties - %%s!') }}"
+                              ).sprintf(jqxhr.statusText).value()
+                            );
+
+                            // Release wizard objects
+                            self.releaseObjects();
+                            self.close();
+                        }
+                      });
+                    }
+                  },
+
+                  // Callback for cancel button
+                  onCancel: function() {
+
+                    // Release wizard objects
+                    self.releaseObjects();
+                    self.close();
+                  }
+                })) ({
+                  collection: this.wizardCollection,
+                  el: el,
+                  model: newModel
+                });
+
+                // Render wizard
+                self.wizard.render();
+              }
+            };
+          });
+        }
+
+        // Call Grant Wizard Dialog
+        alertify.wizardDialog(true);
+      }
+    };
+
+    return pgBrowser.GrantWizard;
+  });
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/acl.json b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/acl.json
new file mode 100644
index 0000000..2d21f14
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/acl.json
@@ -0,0 +1,30 @@
+{# List of allowed privileges of Schema, table sequence, function and view #}
+{#
+    Format for allowed privileges are:
+    "node_name": {
+        "type": "name",
+        "acl": [...]
+    }
+#}
+{
+    "schema": {
+        "type": "SCHEMA",
+        "acl": ["a", "r", "w", "d", "D", "x", "t", "U", "X"]
+    },
+    "table": {
+        "type": "TABLE",
+        "acl": ["a", "w", "d", "D", "x", "t"]
+    },
+    "view": {
+        "type": "VIEW",
+        "acl": ["a", "w", "d", "D", "x", "t"]
+    },
+    "sequence": {
+        "type": "SEQUENCE",
+        "acl": ["w", "U"]
+    },
+    "function": {
+        "type": "FUNCTION",
+        "acl": ["X"]
+    }
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/function.sql
new file mode 100644
index 0000000..7fad225
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/function.sql
@@ -0,0 +1,23 @@
+{# ===== Fetch list of Database object types(Functions) ====== #}
+{% if type and node_id and nspname %}
+{% set func_type = 'Trigger Function' if type == 'trigger_function' else 'Function' %}
+SELECT
+    pr.oid,
+    pg_get_function_identity_arguments(pr.oid) AS proargs,
+    {# pr.proname || '(' || pg_get_function_identity_arguments(pr.oid) || ')' AS name,#}
+    pr.proname AS name,
+    '{{ nspname }}' AS nspname,
+    '{{ func_type }}' AS object_type,
+    '{{ "icon-function" if type != "trigger_function" else "icon-trigger_function" }}' AS icon
+FROM
+    pg_proc pr
+JOIN pg_type typ ON typ.oid=prorettype
+JOIN pg_namespace typns ON typns.oid=typ.typnamespace
+JOIN pg_language lng ON lng.oid=prolang
+LEFT OUTER JOIN pg_description des ON (des.objoid=pr.oid AND des.classoid='pg_proc'::regclass)
+WHERE
+    proisagg = FALSE AND pronamespace = {{ node_id }}::oid
+    AND typname {{ 'NOT' if type != 'trigger_function' else '' }} IN ('trigger', 'event_trigger')
+ORDER BY
+    proname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/get_schemas.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/get_schemas.sql
new file mode 100644
index 0000000..4f6fd5a
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/get_schemas.sql
@@ -0,0 +1,19 @@
+{# ===== Fetch list of all schemas ===== #}
+{% import 'catalog/pg/macros/catalogs.sql' as CATALOGS %}
+SELECT
+    nsp.oid,
+    nsp.nspname as name
+FROM
+    pg_namespace nsp
+WHERE
+    {% if nspid %}
+    nsp.oid={{nspid}}::int AND
+    {% else %}
+    {% if not show_sysobj %}
+    nspname NOT LIKE E'pg\_%' AND
+    {% endif %}
+    {% endif %}
+    NOT (
+{{ CATALOGS.LIST('nsp') }}
+    )
+ORDER BY nspname;
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_function.sql
new file mode 100644
index 0000000..aea68af
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_function.sql
@@ -0,0 +1,10 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/functions/privilege.macros' as PRIVILEGE_FUNCTION %}
+{% for obj in data.objects -%}
+{% for priv in data.priv -%}
+{# ===== if object_type is Function then apply function marcros ===== #}
+{% if (obj.object_type == 'Function' or obj.object_type == 'Trigger Function') %}
+{{ PRIVILEGE_FUNCTION.SET(conn, 'FUNCTION', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], obj.nspname, obj.proargs)}}
+{% endif -%}
+{% endfor -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_sequence.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_sequence.sql
new file mode 100644
index 0000000..8e25d17
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_sequence.sql
@@ -0,0 +1,9 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% for obj in data.objects -%}
+{% for priv in data.priv -%}
+{% if obj.object_type == 'Sequence' %}
+{{ PRIVILEGE.SET(conn, 'SEQUENCE', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], obj.nspname ) }}
+{% endif %}
+{% endfor -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_table.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_table.sql
new file mode 100644
index 0000000..c74b86e
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_table.sql
@@ -0,0 +1,9 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% for obj in data.objects -%}
+{% for priv in data.priv -%}
+{% if obj.object_type == 'Table' or obj.object_type == 'View' %}
+{{ PRIVILEGE.SET(conn, 'TABLE', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], obj.nspname ) }}
+{% endif %}
+{% endfor -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/sequence.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/sequence.sql
new file mode 100644
index 0000000..2950df1
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/sequence.sql
@@ -0,0 +1,15 @@
+{# ===== Fetch list of Database object types(Sequence) ===== #}
+{% if node_id, nspname %}
+SELECT
+    cl.relname AS name,
+    'Sequence' AS object_type,
+    'icon-sequence' AS icon,
+    '{{ nspname }}' AS nspname
+FROM
+    pg_class cl
+LEFT OUTER JOIN pg_description des ON (des.objoid=cl.oid AND des.classoid='pg_class'::regclass)
+WHERE
+    relkind = 'S' AND relnamespace  = {{ node_id }}::oid
+ORDER BY
+    cl.relname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/table.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/table.sql
new file mode 100644
index 0000000..5f67c49
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/table.sql
@@ -0,0 +1,19 @@
+{# ===== Fetch list of Database object types(Tables) ===== #}
+{% if node_id and nspname %}
+SELECT
+    rel.relname AS name,
+    'Table' AS object_type,
+    'icon-table' AS icon,
+    '{{ nspname }}' AS nspname
+FROM
+    pg_class rel
+LEFT OUTER JOIN pg_tablespace spc ON spc.oid=rel.reltablespace
+LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass)
+LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'
+LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid
+LEFT JOIN pg_type typ ON rel.reloftype=typ.oid
+WHERE
+    rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ node_id }}::oid
+ORDER BY
+    rel.relname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/view.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/view.sql
new file mode 100644
index 0000000..fab77cc
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/view.sql
@@ -0,0 +1,28 @@
+{# ===== Fetch list of Database object types(View) ===== #}
+{% if node_id %}
+SELECT
+    c.relname AS name,
+    'View' AS object_type,
+    'icon-view' AS icon,
+    '{{ nspname }}' AS nspname
+FROM
+    pg_class c
+LEFT OUTER JOIN pg_tablespace spc ON spc.oid=c.reltablespace
+LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass)
+LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid
+WHERE
+    ((c.relhasrules AND (EXISTS (
+      SELECT
+          r.rulename
+      FROM
+          pg_rewrite r
+      WHERE
+          ((r.ev_class = c.oid)
+          AND (bpchar(r.ev_type) = '1'::bpchar))
+      ))
+     ) OR (c.relkind = 'v'::char)
+    )
+    AND c.relnamespace = {{ node_id }}::oid
+ORDER BY
+    c.relname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/acl.json b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/acl.json
new file mode 100644
index 0000000..2d21f14
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/acl.json
@@ -0,0 +1,30 @@
+{# List of allowed privileges of Schema, table sequence, function and view #}
+{#
+    Format for allowed privileges are:
+    "node_name": {
+        "type": "name",
+        "acl": [...]
+    }
+#}
+{
+    "schema": {
+        "type": "SCHEMA",
+        "acl": ["a", "r", "w", "d", "D", "x", "t", "U", "X"]
+    },
+    "table": {
+        "type": "TABLE",
+        "acl": ["a", "w", "d", "D", "x", "t"]
+    },
+    "view": {
+        "type": "VIEW",
+        "acl": ["a", "w", "d", "D", "x", "t"]
+    },
+    "sequence": {
+        "type": "SEQUENCE",
+        "acl": ["w", "U"]
+    },
+    "function": {
+        "type": "FUNCTION",
+        "acl": ["X"]
+    }
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/function.sql
new file mode 100644
index 0000000..7fad225
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/function.sql
@@ -0,0 +1,23 @@
+{# ===== Fetch list of Database object types(Functions) ====== #}
+{% if type and node_id and nspname %}
+{% set func_type = 'Trigger Function' if type == 'trigger_function' else 'Function' %}
+SELECT
+    pr.oid,
+    pg_get_function_identity_arguments(pr.oid) AS proargs,
+    {# pr.proname || '(' || pg_get_function_identity_arguments(pr.oid) || ')' AS name,#}
+    pr.proname AS name,
+    '{{ nspname }}' AS nspname,
+    '{{ func_type }}' AS object_type,
+    '{{ "icon-function" if type != "trigger_function" else "icon-trigger_function" }}' AS icon
+FROM
+    pg_proc pr
+JOIN pg_type typ ON typ.oid=prorettype
+JOIN pg_namespace typns ON typns.oid=typ.typnamespace
+JOIN pg_language lng ON lng.oid=prolang
+LEFT OUTER JOIN pg_description des ON (des.objoid=pr.oid AND des.classoid='pg_proc'::regclass)
+WHERE
+    proisagg = FALSE AND pronamespace = {{ node_id }}::oid
+    AND typname {{ 'NOT' if type != 'trigger_function' else '' }} IN ('trigger', 'event_trigger')
+ORDER BY
+    proname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/get_schemas.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/get_schemas.sql
new file mode 100644
index 0000000..4f6fd5a
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/get_schemas.sql
@@ -0,0 +1,19 @@
+{# ===== Fetch list of all schemas ===== #}
+{% import 'catalog/pg/macros/catalogs.sql' as CATALOGS %}
+SELECT
+    nsp.oid,
+    nsp.nspname as name
+FROM
+    pg_namespace nsp
+WHERE
+    {% if nspid %}
+    nsp.oid={{nspid}}::int AND
+    {% else %}
+    {% if not show_sysobj %}
+    nspname NOT LIKE E'pg\_%' AND
+    {% endif %}
+    {% endif %}
+    NOT (
+{{ CATALOGS.LIST('nsp') }}
+    )
+ORDER BY nspname;
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_function.sql
new file mode 100644
index 0000000..aea68af
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_function.sql
@@ -0,0 +1,10 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/functions/privilege.macros' as PRIVILEGE_FUNCTION %}
+{% for obj in data.objects -%}
+{% for priv in data.priv -%}
+{# ===== if object_type is Function then apply function marcros ===== #}
+{% if (obj.object_type == 'Function' or obj.object_type == 'Trigger Function') %}
+{{ PRIVILEGE_FUNCTION.SET(conn, 'FUNCTION', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], obj.nspname, obj.proargs)}}
+{% endif -%}
+{% endfor -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_sequence.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_sequence.sql
new file mode 100644
index 0000000..8e25d17
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_sequence.sql
@@ -0,0 +1,9 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% for obj in data.objects -%}
+{% for priv in data.priv -%}
+{% if obj.object_type == 'Sequence' %}
+{{ PRIVILEGE.SET(conn, 'SEQUENCE', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], obj.nspname ) }}
+{% endif %}
+{% endfor -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_table.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_table.sql
new file mode 100644
index 0000000..c74b86e
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_table.sql
@@ -0,0 +1,9 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% for obj in data.objects -%}
+{% for priv in data.priv -%}
+{% if obj.object_type == 'Table' or obj.object_type == 'View' %}
+{{ PRIVILEGE.SET(conn, 'TABLE', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], obj.nspname ) }}
+{% endif %}
+{% endfor -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/sequence.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/sequence.sql
new file mode 100644
index 0000000..2950df1
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/sequence.sql
@@ -0,0 +1,15 @@
+{# ===== Fetch list of Database object types(Sequence) ===== #}
+{% if node_id, nspname %}
+SELECT
+    cl.relname AS name,
+    'Sequence' AS object_type,
+    'icon-sequence' AS icon,
+    '{{ nspname }}' AS nspname
+FROM
+    pg_class cl
+LEFT OUTER JOIN pg_description des ON (des.objoid=cl.oid AND des.classoid='pg_class'::regclass)
+WHERE
+    relkind = 'S' AND relnamespace  = {{ node_id }}::oid
+ORDER BY
+    cl.relname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/table.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/table.sql
new file mode 100644
index 0000000..5f67c49
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/table.sql
@@ -0,0 +1,19 @@
+{# ===== Fetch list of Database object types(Tables) ===== #}
+{% if node_id and nspname %}
+SELECT
+    rel.relname AS name,
+    'Table' AS object_type,
+    'icon-table' AS icon,
+    '{{ nspname }}' AS nspname
+FROM
+    pg_class rel
+LEFT OUTER JOIN pg_tablespace spc ON spc.oid=rel.reltablespace
+LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass)
+LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'
+LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid
+LEFT JOIN pg_type typ ON rel.reloftype=typ.oid
+WHERE
+    rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ node_id }}::oid
+ORDER BY
+    rel.relname
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/view.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/view.sql
new file mode 100644
index 0000000..fab77cc
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/view.sql
@@ -0,0 +1,28 @@
+{# ===== Fetch list of Database object types(View) ===== #}
+{% if node_id %}
+SELECT
+    c.relname AS name,
+    'View' AS object_type,
+    'icon-view' AS icon,
+    '{{ nspname }}' AS nspname
+FROM
+    pg_class c
+LEFT OUTER JOIN pg_tablespace spc ON spc.oid=c.reltablespace
+LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass)
+LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid
+WHERE
+    ((c.relhasrules AND (EXISTS (
+      SELECT
+          r.rulename
+      FROM
+          pg_rewrite r
+      WHERE
+          ((r.ev_class = c.oid)
+          AND (bpchar(r.ev_type) = '1'::bpchar))
+      ))
+     ) OR (c.relkind = 'v'::char)
+    )
+    AND c.relnamespace = {{ node_id }}::oid
+ORDER BY
+    c.relname
+{% endif %}


^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-04-13 15:11  Dave Page <[email protected]>
  parent: Surinder Kumar <[email protected]>
  0 siblings, 2 replies; 24+ messages in thread

From: Dave Page @ 2016-04-13 15:11 UTC (permalink / raw)
  To: Surinder Kumar <[email protected]>; +Cc: Ashesh Vashi <[email protected]>; pgadmin-hackers

Hi

On Fri, Apr 8, 2016 at 6:59 PM, Surinder Kumar <
[email protected]> wrote:

> Hi
>
> PFA patch with resolved review comments.
>
> On Fri, Apr 8, 2016 at 12:52 AM, Dave Page <[email protected]> wrote:
>
>> Hi
>>
>> Nearly there :-). Assuming no regressions, I believe we'll be ready to
>> commit once the following issues are resolved:
>>
>> - The select object grid should fill the available vertical space in the
>> dialogue - at present there's a gap below it.
>>
> Done
>

This does not seem to be fixed (see attached screenshot). In fact, the gap
gets bigger proportionally as the window is resized.


>
>> - When selecting privileges, each time I click on a checkbox, the row
>> closes. Similar grids elsewhere in the app close the row when the cell
>> loses focus, *however*, that is also the incorrect behaviour - the row
>> should only close when the row itself loses focus (and should open when it
>> gets focus).
>>
> I checked the row closes either when gets clicked on row or outside row,
> it doesn't seems to close on click on checkbox.
>

This is still broken for me. I'm using Chrome on OS X. As soon as I click
any checkbox, the row closes, and I have to click again to see the
checkboxes again.

One additional issue:

- If I select some objects and permissions (in my case, everything in a PEM
database, and ALL), hit Next so I can see the SQL, then hit back *twice*, I
see the second attached screenshot.

I'm going to commit the code with these issues as they are largely
cosmetic. Please submit a patch to fix them (I'll add a new card to our
internal Kanban chart).

Thanks.

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [image/png] Screen Shot 2016-04-13 at 16.02.14.png (315.3K, 3-Screen%20Shot%202016-04-13%20at%2016.02.14.png)
  download | view image

  [image/png] Screen Shot 2016-04-13 at 16.09.23.png (91.3K, 4-Screen%20Shot%202016-04-13%20at%2016.09.23.png)
  download | view image

^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-04-15 16:03  Surinder Kumar <[email protected]>
  parent: Dave Page <[email protected]>
  1 sibling, 1 reply; 24+ messages in thread

From: Surinder Kumar @ 2016-04-15 16:03 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: Ashesh Vashi <[email protected]>; pgadmin-hackers

Hi,

PFA patch with following issues fixed:

   1. Fixed  'get_schemas.sql' template for PPAS. It was fetching system
   level schemas due to wrong sql query.
   2. Allowed the grant wizard to open on procedures node if server type is
   PPAS.

Also, I will send another patch for the remaining issues.

On Wed, Apr 13, 2016 at 8:41 PM, Dave Page <[email protected]> wrote:

> Hi
>
> On Fri, Apr 8, 2016 at 6:59 PM, Surinder Kumar <
> [email protected]> wrote:
>
>> Hi
>>
>> PFA patch with resolved review comments.
>>
>> On Fri, Apr 8, 2016 at 12:52 AM, Dave Page <[email protected]> wrote:
>>
>>> Hi
>>>
>>> Nearly there :-). Assuming no regressions, I believe we'll be ready to
>>> commit once the following issues are resolved:
>>>
>>> - The select object grid should fill the available vertical space in the
>>> dialogue - at present there's a gap below it.
>>>
>> Done
>>
>
> This does not seem to be fixed (see attached screenshot). In fact, the gap
> gets bigger proportionally as the window is resized.
>
>
>>
>>> - When selecting privileges, each time I click on a checkbox, the row
>>> closes. Similar grids elsewhere in the app close the row when the cell
>>> loses focus, *however*, that is also the incorrect behaviour - the row
>>> should only close when the row itself loses focus (and should open when it
>>> gets focus).
>>>
>> I checked the row closes either when gets clicked on row or outside row,
>> it doesn't seems to close on click on checkbox.
>>
>
> This is still broken for me. I'm using Chrome on OS X. As soon as I click
> any checkbox, the row closes, and I have to click again to see the
> checkboxes again.
>
> One additional issue:
>
> - If I select some objects and permissions (in my case, everything in a
> PEM database, and ALL), hit Next so I can see the SQL, then hit back
> *twice*, I see the second attached screenshot.
>
> I'm going to commit the code with these issues as they are largely
> cosmetic. Please submit a patch to fix them (I'll add a new card to our
> internal Kanban chart).
>
> Thanks.
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [application/octet-stream] grant_wizard_v10.patch (9.9K, 3-grant_wizard_v10.patch)
  download | inline diff:
diff --git a/web/pgadmin/tools/grant_wizard/__init__.py b/web/pgadmin/tools/grant_wizard/__init__.py
index 4002e4e..a60dc36 100644
--- a/web/pgadmin/tools/grant_wizard/__init__.py
+++ b/web/pgadmin/tools/grant_wizard/__init__.py
@@ -227,6 +227,21 @@ def properties(gid, sid, did, node_id, node_type):
 
             res_data.extend(res['rows'])
 
+        # Fetch procedures only if server type is ppas
+        if (len(server_prop) > 0 and
+           server_prop['server_type'] == 'ppas' and
+           ntype in ['schema', 'procedure']):
+            SQL = render_template("/".join(
+                [server_prop['template_path'], '/sql/function.sql']),
+                node_id=node_id, nspname=nspname, type='procedure')
+
+            status, res = conn.execute_dict(SQL)
+
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            res_data.extend(res['rows'])
+
         # Fetch trigger functions
         if ntype in ['schema', 'trigger_function']:
             SQL = render_template("/".join(
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
index 7340ec5..b167447 100644
--- a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
@@ -41,8 +41,11 @@ define([
         res.object_id = res.name_with_args;
 
         // create name with args if its object is function
-        if(!_.isUndefined(res.object_type) && (res.object_type == 'Function' ||
-          res.object_type == 'Trigger Function'))
+        if(!_.isUndefined(res.object_type) &&
+          (res.object_type == 'Function' ||
+          res.object_type == 'Trigger Function' ||
+          res.object_type == 'Procedure'
+          ))
           res.name_with_args = res.name+'('+(typeof(res.proargs) != 'undefined' ? res.proargs : '')+')';
         else
           res.name_with_args = res.name;
@@ -138,7 +141,7 @@ define([
         // Define list of nodes on which grant wizard context menu option appears
         var supported_nodes = [
               'schema', 'coll-function', 'coll-sequence',
-              'coll-table', 'coll-view',
+              'coll-table', 'coll-view', 'coll-procedure',
               'coll-materialized_view', 'database'
             ],
 
@@ -508,6 +511,9 @@ define([
                     case 'Trigger Function':
                       object_type = 'function';
                       break;
+                    case 'Procedure':
+                      object_type = 'procedure';
+                      break;
                     case 'Table':
                       object_type = 'table';
                       break;
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/acl.json b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/acl.json
index 2d21f14..78782b5 100644
--- a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/acl.json
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/acl.json
@@ -26,5 +26,9 @@
     "function": {
         "type": "FUNCTION",
         "acl": ["X"]
+    },
+    "procedure": {
+        "type": "PROCEDURE",
+        "acl": ["X"]
     }
 }
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/function.sql
index 7fad225..067606f 100644
--- a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/function.sql
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/function.sql
@@ -1,14 +1,14 @@
 {# ===== Fetch list of Database object types(Functions) ====== #}
 {% if type and node_id and nspname %}
 {% set func_type = 'Trigger Function' if type == 'trigger_function' else 'Function' %}
+{% set icon = 'icon-function' if type == 'function' else 'icon-trigger_function' %}
 SELECT
     pr.oid,
     pg_get_function_identity_arguments(pr.oid) AS proargs,
-    {# pr.proname || '(' || pg_get_function_identity_arguments(pr.oid) || ')' AS name,#}
     pr.proname AS name,
-    '{{ nspname }}' AS nspname,
     '{{ func_type }}' AS object_type,
-    '{{ "icon-function" if type != "trigger_function" else "icon-trigger_function" }}' AS icon
+    '{{ nspname }}' AS nspname,
+    '{{ icon }}' AS icon
 FROM
     pg_proc pr
 JOIN pg_type typ ON typ.oid=prorettype
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_function.sql
index aea68af..7e8e506 100644
--- a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_function.sql
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_function.sql
@@ -2,9 +2,8 @@
 {% import 'macros/functions/privilege.macros' as PRIVILEGE_FUNCTION %}
 {% for obj in data.objects -%}
 {% for priv in data.priv -%}
-{# ===== if object_type is Function then apply function marcros ===== #}
-{% if (obj.object_type == 'Function' or obj.object_type == 'Trigger Function') %}
-{{ PRIVILEGE_FUNCTION.SET(conn, 'FUNCTION', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], obj.nspname, obj.proargs)}}
+{% if (obj.object_type == 'Function' or obj.object_type == 'Trigger Function' or obj.object_type == 'Procedure') %}{% set func_type = 'PROCEDURE' if obj.object_type == 'Procedure' else 'FUNCTION' if obj.object_type == 'Function' or obj.object_type == 'Trigger Function' -%}
+{{ PRIVILEGE_FUNCTION.SET(conn, func_type, priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], obj.nspname, obj.proargs)}}
 {% endif -%}
 {% endfor -%}
 {% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/acl.json b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/acl.json
index 2d21f14..78782b5 100644
--- a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/acl.json
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/acl.json
@@ -26,5 +26,9 @@
     "function": {
         "type": "FUNCTION",
         "acl": ["X"]
+    },
+    "procedure": {
+        "type": "PROCEDURE",
+        "acl": ["X"]
     }
 }
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/function.sql
index 7fad225..855a841 100644
--- a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/function.sql
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/function.sql
@@ -1,14 +1,14 @@
 {# ===== Fetch list of Database object types(Functions) ====== #}
 {% if type and node_id and nspname %}
-{% set func_type = 'Trigger Function' if type == 'trigger_function' else 'Function' %}
+{% set func_type = 'Trigger Function' if type == 'trigger_function' else 'Procedure' if type == 'procedure' else 'Function' %}
+{% set icon = 'icon-function' if type == 'function' else 'icon-procedure' if type == 'procedure' else 'icon-trigger_function' %}
 SELECT
     pr.oid,
     pg_get_function_identity_arguments(pr.oid) AS proargs,
-    {# pr.proname || '(' || pg_get_function_identity_arguments(pr.oid) || ')' AS name,#}
     pr.proname AS name,
-    '{{ nspname }}' AS nspname,
     '{{ func_type }}' AS object_type,
-    '{{ "icon-function" if type != "trigger_function" else "icon-trigger_function" }}' AS icon
+    '{{ nspname }}' AS nspname,
+    '{{ icon }}' AS icon
 FROM
     pg_proc pr
 JOIN pg_type typ ON typ.oid=prorettype
@@ -18,6 +18,7 @@ LEFT OUTER JOIN pg_description des ON (des.objoid=pr.oid AND des.classoid='pg_pr
 WHERE
     proisagg = FALSE AND pronamespace = {{ node_id }}::oid
     AND typname {{ 'NOT' if type != 'trigger_function' else '' }} IN ('trigger', 'event_trigger')
+    AND pr.protype = {{ 0 if type != 'procedure' else 1 }}
 ORDER BY
     proname
 {% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/get_schemas.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/get_schemas.sql
index 4f6fd5a..4d47fa1 100644
--- a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/get_schemas.sql
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/get_schemas.sql
@@ -1,11 +1,12 @@
 {# ===== Fetch list of all schemas ===== #}
-{% import 'catalog/pg/macros/catalogs.sql' as CATALOGS %}
+{% import 'catalog/ppas/macros/catalogs.sql' as CATALOGS %}
 SELECT
     nsp.oid,
     nsp.nspname as name
 FROM
     pg_namespace nsp
 WHERE
+    nsp.nspparent = 0 AND
     {% if nspid %}
     nsp.oid={{nspid}}::int AND
     {% else %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_function.sql
index aea68af..7e8e506 100644
--- a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_function.sql
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_function.sql
@@ -2,9 +2,8 @@
 {% import 'macros/functions/privilege.macros' as PRIVILEGE_FUNCTION %}
 {% for obj in data.objects -%}
 {% for priv in data.priv -%}
-{# ===== if object_type is Function then apply function marcros ===== #}
-{% if (obj.object_type == 'Function' or obj.object_type == 'Trigger Function') %}
-{{ PRIVILEGE_FUNCTION.SET(conn, 'FUNCTION', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], obj.nspname, obj.proargs)}}
+{% if (obj.object_type == 'Function' or obj.object_type == 'Trigger Function' or obj.object_type == 'Procedure') %}{% set func_type = 'PROCEDURE' if obj.object_type == 'Procedure' else 'FUNCTION' if obj.object_type == 'Function' or obj.object_type == 'Trigger Function' -%}
+{{ PRIVILEGE_FUNCTION.SET(conn, func_type, priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], obj.nspname, obj.proargs)}}
 {% endif -%}
 {% endfor -%}
 {% endfor -%}


^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-04-16 09:01  Dave Page <[email protected]>
  parent: Surinder Kumar <[email protected]>
  0 siblings, 0 replies; 24+ messages in thread

From: Dave Page @ 2016-04-16 09:01 UTC (permalink / raw)
  To: Surinder Kumar <[email protected]>; +Cc: Ashesh Vashi <[email protected]>; pgadmin-hackers

Thanks, patch applied.

On Friday, April 15, 2016, Surinder Kumar <[email protected]>
wrote:

> Hi,
>
> PFA patch with following issues fixed:
>
>    1. Fixed  'get_schemas.sql' template for PPAS. It was fetching system
>    level schemas due to wrong sql query.
>    2. Allowed the grant wizard to open on procedures node if server type
>    is PPAS.
>
> Also, I will send another patch for the remaining issues.
>
> On Wed, Apr 13, 2016 at 8:41 PM, Dave Page <[email protected]
> <javascript:_e(%7B%7D,'cvml','[email protected]');>> wrote:
>
>> Hi
>>
>> On Fri, Apr 8, 2016 at 6:59 PM, Surinder Kumar <
>> [email protected]
>> <javascript:_e(%7B%7D,'cvml','[email protected]');>> wrote:
>>
>>> Hi
>>>
>>> PFA patch with resolved review comments.
>>>
>>> On Fri, Apr 8, 2016 at 12:52 AM, Dave Page <[email protected]
>>> <javascript:_e(%7B%7D,'cvml','[email protected]');>> wrote:
>>>
>>>> Hi
>>>>
>>>> Nearly there :-). Assuming no regressions, I believe we'll be ready to
>>>> commit once the following issues are resolved:
>>>>
>>>> - The select object grid should fill the available vertical space in
>>>> the dialogue - at present there's a gap below it.
>>>>
>>> Done
>>>
>>
>> This does not seem to be fixed (see attached screenshot). In fact, the
>> gap gets bigger proportionally as the window is resized.
>>
>>
>>>
>>>> - When selecting privileges, each time I click on a checkbox, the row
>>>> closes. Similar grids elsewhere in the app close the row when the cell
>>>> loses focus, *however*, that is also the incorrect behaviour - the row
>>>> should only close when the row itself loses focus (and should open when it
>>>> gets focus).
>>>>
>>> I checked the row closes either when gets clicked on row or outside row,
>>> it doesn't seems to close on click on checkbox.
>>>
>>
>> This is still broken for me. I'm using Chrome on OS X. As soon as I click
>> any checkbox, the row closes, and I have to click again to see the
>> checkboxes again.
>>
>> One additional issue:
>>
>> - If I select some objects and permissions (in my case, everything in a
>> PEM database, and ALL), hit Next so I can see the SQL, then hit back
>> *twice*, I see the second attached screenshot.
>>
>> I'm going to commit the code with these issues as they are largely
>> cosmetic. Please submit a patch to fix them (I'll add a new card to our
>> internal Kanban chart).
>>
>> Thanks.
>>
>> --
>> Dave Page
>> Blog: http://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EnterpriseDB UK: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>>
>
>

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Fwd: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-05-06 09:30  Surinder Kumar <[email protected]>
  parent: Dave Page <[email protected]>
  1 sibling, 1 reply; 24+ messages in thread

From: Surinder Kumar @ 2016-05-06 09:30 UTC (permalink / raw)
  To: pgadmin-hackers

Hi,

PFA patch with following issues fixed:
1. As SqlTab Control is renamed to SqlCtrl, throws error on grant wizard
close. it is Fixed.
2. Moved grant wizard specific css from wizard.css to grant_wizard.css.

On Wed, Apr 13, 2016 at 8:41 PM, Dave Page <[email protected]> wrote:

> Hi
>
> On Fri, Apr 8, 2016 at 6:59 PM, Surinder Kumar <
> [email protected]> wrote:
>
>> Hi
>>
>> PFA patch with resolved review comments.
>>
>> On Fri, Apr 8, 2016 at 12:52 AM, Dave Page <[email protected]> wrote:
>>
>>> Hi
>>>
>>> Nearly there :-). Assuming no regressions, I believe we'll be ready to
>>> commit once the following issues are resolved:
>>>
>>> - The select object grid should fill the available vertical space in the
>>> dialogue - at present there's a gap below it.
>>>
>> Done
>>
>
> This does not seem to be fixed (see attached screenshot). In fact, the gap
> gets bigger proportionally as the window is resized.
>
Fixed

>
>
>>
>>> - When selecting privileges, each time I click on a checkbox, the row
>>> closes. Similar grids elsewhere in the app close the row when the cell
>>> loses focus, *however*, that is also the incorrect behaviour - the row
>>> should only close when the row itself loses focus (and should open when it
>>> gets focus).
>>>
>> I checked the row closes either when gets clicked on row or outside row,
>> it doesn't seems to close on click on checkbox.
>>
>
> This is still broken for me. I'm using Chrome on OS X. As soon as I click
> any checkbox, the row closes, and I have to click again to see the
> checkboxes again.
>
I checked with Chrome on OS X, windows and ubuntu, it is working fine. Can
you please send me screenshot if possible?

>
> One additional issue:
>
> - If I select some objects and permissions (in my case, everything in a
> PEM database, and ALL), hit Next so I can see the SQL, then hit back
> *twice*, I see the second attached screenshot.
>
I followed the same steps and when hit back twice on *previous* button of
SQL page, it takes me to first page which is right.
It would be easy for me to find out the exact cause if you send me output
of browser console panel.

>
> I'm going to commit the code with these issues as they are largely
> cosmetic. Please submit a patch to fix them (I'll add a new card to our
> internal Kanban chart).
>
> Thanks.
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [application/octet-stream] grant_wizard_v10.patch (6.5K, 3-grant_wizard_v10.patch)
  download | inline diff:
diff --git a/web/pgadmin/browser/static/css/wizard.css b/web/pgadmin/browser/static/css/wizard.css
index 2c19a09..ab96b6e 100644
--- a/web/pgadmin/browser/static/css/wizard.css
+++ b/web/pgadmin/browser/static/css/wizard.css
@@ -4,7 +4,7 @@
 }
 
 .wizard-header {
-   padding: 6px 10px!important;
+  padding: 6px 10px!important;
   min-height: 35px;
   max-height: 35px;
   border-bottom: 2px solid darkgray;
@@ -29,25 +29,18 @@
   width: 100%;
 }
 
-.grant_wizard_container {
-  position: relative;
-  overflow: hidden;
-  width: 100%;
-  height: 100%;
-}
-
-.grant_wizard_container .pgadmin-wizard {
+.pgadmin-wizard {
   width: 100%;
   height: 100%;
 }
 
-.grant_wizard_container .wizard-content {
+.wizard-content {
   position: relative;
   padding: 0;
-  height: 78%;
+  height: calc(100% - 97px);
 }
 
-.grant_wizard_container .wizard-right-panel {
+.wizard-right-panel {
   overflow-y: auto;
   height: 100%;
   top: 0;
@@ -56,7 +49,7 @@
   bottom: 0;
 }
 
-.grant_wizard_container .wizard-left-panel {
+.wizard-left-panel {
   position: absolute;
   top: 0;
   display: flex;
@@ -67,20 +60,16 @@
   right: 0;
 }
 
-.grant_wizard_container .wizard-left-panel img {
+.wizard-left-panel img {
   width: 140px;
 }
 
-.grant_wizard_container .wizard-right-panel_content {
-  height: 60%;
-}
-
-.grant_wizard_container {
-  height: 100%;
+.wizard-right-panel_content {
+  height: calc(100% - 113px);
 }
 
 /* Wizard Footer CSS */
-.grant_wizard_container .footer {
+.pgadmin-wizard .footer {
   background-color: #D2D2D2;
   border-width: 2px 0px 0px 0px;
   border-style: solid;
@@ -94,41 +83,41 @@
 }
 
 /* Wizard Button CSS */
-.grant_wizard_container .wizard-buttons {
+.pgadmin-wizard .wizard-buttons {
   float: right;
 }
 
-.grant_wizard_container .wizard-buttons button {
+.pgadmin-wizard .wizard-buttons button {
   float: left;
   font-size: 14px;
   margin: 3px 5px 0 0 !important;
 }
 
-.grant_wizard_container .wizard-buttons button.wizard-next i.fa {
+.pgadmin-wizard .wizard-buttons button.wizard-next i.fa {
   padding-left: 5px;
 }
 
-.grant_wizard_container .wizard-buttons button.wizard-back i.fa,
-.grant_wizard_container .wizard-buttons button.wizard-cancel i.fa {
+.pgadmin-wizard .wizard-buttons button.wizard-back i.fa,
+.pgadmin-wizard .wizard-buttons button.wizard-cancel i.fa {
   padding-right: 5px;
 }
 
-.grant_wizard_container .wizard-buttons .wizard-finish {
+.pgadmin-wizard .wizard-buttons .wizard-finish {
   margin-right: 0 !important;
 }
 
 /* Wizard Status bar CSS */
-.grant_wizard_container .wizard-description {
+.pgadmin-wizard .wizard-description {
   padding: 1.0em 0.1em;
 }
 
 /* Error message css */
-.grant_wizard_container .error_msg_div {
-  display: block;
+.pgadmin-wizard .error_msg_div {
   background: #fff;
+  font-size: 13px;
 }
 
-.grant_wizard_container .error_msg_div p {
+.pgadmin-wizard .error_msg_div p {
   background: #fff;
   color: #b92c28;
 }
@@ -146,4 +135,4 @@
 
 .alertify .ajs-body .ajs-content {
   padding: 0px;
-}
\ No newline at end of file
+}
diff --git a/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
index 18240e9..7681b9a 100644
--- a/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
+++ b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
@@ -11,9 +11,14 @@
 }
 
 .object_type_table {
-  display: inline-block;
   height: 100%;
   border: 0 !important;
+  display: inline;
+}
+
+.object_type_table thead {
+  display: table;
+  width: 100%;
 }
 
 .object_type_table thead tr {
@@ -24,7 +29,7 @@
 
 .object_type_table tbody {
   display: block;
-  overflow: scroll;
+  overflow: auto;
   border: 1px solid #ddd;
   width: 100%;
   min-height: 100%;
@@ -107,3 +112,10 @@
 .wizard-right-panel_content .CodeMirror-gutters {
   min-height: 285px !important;
 }
+
+.grant_wizard_container {
+  position: relative;
+  overflow: hidden;
+  width: 100%;
+  height: 100%;
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
index b167447..543455d 100644
--- a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
@@ -93,7 +93,6 @@ define([
           var col = this.column.get('name');
           if (this.model && this.model.has(col)) {
             if (this.model.get(col)) {
-              this.checkbox().prop("checked", true);
               this.$el.parent().toggleClass("selected", true);
               this.model.trigger("backgrid:selected", this.model, true);
             }
@@ -201,7 +200,6 @@ define([
               },
               setup:function() {
                 return {
-
                   // Set options for dialog
                   options: {
                     frameless: true,
@@ -946,8 +944,9 @@ define([
                             dataType: "json",
                             contentType: "application/json"
                           }).done(function(res) {
-                            self.sqlTab.clearHistory();
-                            self.sqlTab.setValue(res.data);
+                            self.sqlCtrl.clearHistory();
+                            self.sqlCtrl.setValue(res.data);
+                            self.sqlCtrl.refresh();
                           }).fail(function() {
                             self.model.trigger('pgadmin-view:msql:error');
                           }).always(function() {
@@ -959,9 +958,9 @@ define([
 
                           // Clear html dom elements of CodeMirror sql tab
                           self.sqlControl.unbind(); // Unbind all local event bindings
-                          var cmElem = self.sqlControl.sqlTab.getWrapperElement();
+                          var cmElem = self.sqlControl.sqlCtrl.getWrapperElement();
                           cmElem.remove();
-                          self.sqlControl.sqlTab = undefined;
+                          self.sqlControl.sqlCtrl = undefined;
                         }
 
                       })
@@ -987,9 +986,9 @@ define([
                   view: new(function() {
 
                     // Render SqlTab control to generate its html markup
-                    var sqlTabHtml = sqlControl.render().$el;
+                    var sqlCtrlHtml = sqlControl.render().$el;
                     this.render = function() {
-                        return { el: sqlTabHtml };
+                        return { el: sqlCtrlHtml };
                     };
                   }),
 


^ permalink  raw  reply  [nested|flat] 24+ messages in thread

* Re: [pgAdmin4] [Patch]: Grant Wizard
@ 2016-05-11 17:28  Surinder Kumar <[email protected]>
  parent: Surinder Kumar <[email protected]>
  0 siblings, 0 replies; 24+ messages in thread

From: Surinder Kumar @ 2016-05-11 17:28 UTC (permalink / raw)
  To: pgadmin-hackers

Hi,

PFA updated patch

*Change*: removed '*ajs_content*' css from *wizard.css* causing padding
issue in alertify dialog.
Thanks murtuza for reporting.


On Fri, May 6, 2016 at 3:00 PM, Surinder Kumar <
[email protected]> wrote:

> Hi,
>
> PFA patch with following issues fixed:
> 1. As SqlTab Control is renamed to SqlCtrl, throws error on grant wizard
> close. it is Fixed.
> 2. Moved grant wizard specific css from wizard.css to grant_wizard.css.
>
> On Wed, Apr 13, 2016 at 8:41 PM, Dave Page <[email protected]> wrote:
>
>> Hi
>>
>> On Fri, Apr 8, 2016 at 6:59 PM, Surinder Kumar <
>> [email protected]> wrote:
>>
>>> Hi
>>>
>>> PFA patch with resolved review comments.
>>>
>>> On Fri, Apr 8, 2016 at 12:52 AM, Dave Page <[email protected]> wrote:
>>>
>>>> Hi
>>>>
>>>> Nearly there :-). Assuming no regressions, I believe we'll be ready to
>>>> commit once the following issues are resolved:
>>>>
>>>> - The select object grid should fill the available vertical space in
>>>> the dialogue - at present there's a gap below it.
>>>>
>>> Done
>>>
>>
>> This does not seem to be fixed (see attached screenshot). In fact, the
>> gap gets bigger proportionally as the window is resized.
>>
> Fixed
>
>>
>>
>>>
>>>> - When selecting privileges, each time I click on a checkbox, the row
>>>> closes. Similar grids elsewhere in the app close the row when the cell
>>>> loses focus, *however*, that is also the incorrect behaviour - the row
>>>> should only close when the row itself loses focus (and should open when it
>>>> gets focus).
>>>>
>>> I checked the row closes either when gets clicked on row or outside row,
>>> it doesn't seems to close on click on checkbox.
>>>
>>
>> This is still broken for me. I'm using Chrome on OS X. As soon as I click
>> any checkbox, the row closes, and I have to click again to see the
>> checkboxes again.
>>
> I checked with Chrome on OS X, windows and ubuntu, it is working fine. Can
> you please send me screenshot if possible?
>
>>
>> One additional issue:
>>
>> - If I select some objects and permissions (in my case, everything in a
>> PEM database, and ALL), hit Next so I can see the SQL, then hit back
>> *twice*, I see the second attached screenshot.
>>
> I followed the same steps and when hit back twice on *previous* button of
> SQL page, it takes me to first page which is right.
> It would be easy for me to find out the exact cause if you send me output
> of browser console panel.
>
>>
>> I'm going to commit the code with these issues as they are largely
>> cosmetic. Please submit a patch to fix them (I'll add a new card to our
>> internal Kanban chart).
>>
>> Thanks.
>>
>> --
>> Dave Page
>> Blog: http://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EnterpriseDB UK: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>>
>
>
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [application/octet-stream] grant_wizard_v11.patch (1.0K, 3-grant_wizard_v11.patch)
  download | inline diff:
diff --git a/web/pgadmin/browser/static/css/wizard.css b/web/pgadmin/browser/static/css/wizard.css
index 2c19a09..da93d57 100644
--- a/web/pgadmin/browser/static/css/wizard.css
+++ b/web/pgadmin/browser/static/css/wizard.css
@@ -143,7 +143,3 @@
 .select2-container--open {
   z-index: 10000;
 }
-
-.alertify .ajs-body .ajs-content {
-  padding: 0px;
-}
\ No newline at end of file
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
index 21f8516..aef7621 100644
--- a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
@@ -142,7 +142,7 @@ define([
         var supported_nodes = [
               'schema', 'coll-function', 'coll-sequence',
               'coll-table', 'coll-view', 'coll-procedure',
-              'coll-materialized_view', 'database'
+              'coll-mview', 'database'
             ],
 
             /**


^ permalink  raw  reply  [nested|flat] 24+ messages in thread


end of thread, other threads:[~2016-05-11 17:28 UTC | newest]

Thread overview: 24+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2016-03-03 12:49 [pgAdmin4] [Patch]: Grant Wizard Surinder Kumar <[email protected]>
2016-03-04 13:39 ` Dave Page <[email protected]>
2016-03-04 14:36   ` Dave Page <[email protected]>
2016-03-08 05:14     ` Surinder Kumar <[email protected]>
2016-03-08 10:00       ` Surinder Kumar <[email protected]>
2016-03-08 13:27         ` Dave Page <[email protected]>
2016-03-08 13:48           ` Surinder Kumar <[email protected]>
2016-03-09 17:34             ` Dave Page <[email protected]>
2016-03-10 09:22               ` Surinder Kumar <[email protected]>
2016-03-10 09:50                 ` Surinder Kumar <[email protected]>
2016-03-10 11:56                   ` Dave Page <[email protected]>
2016-03-28 08:39                     ` Surinder Kumar <[email protected]>
2016-03-30 11:44                       ` Surinder Kumar <[email protected]>
2016-04-05 05:36                         ` Ashesh Vashi <[email protected]>
2016-04-06 11:37                           ` Surinder Kumar <[email protected]>
2016-04-07 14:17                             ` Dave Page <[email protected]>
2016-04-07 17:10                               ` Surinder Kumar <[email protected]>
2016-04-07 19:22                                 ` Dave Page <[email protected]>
2016-04-08 17:59                                   ` Surinder Kumar <[email protected]>
2016-04-13 15:11                                     ` Dave Page <[email protected]>
2016-04-15 16:03                                       ` Surinder Kumar <[email protected]>
2016-04-16 09:01                                         ` Dave Page <[email protected]>
2016-05-06 09:30                                       ` Surinder Kumar <[email protected]>
2016-05-11 17:28                                         ` Surinder Kumar <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox