Subject: Privilege control with select2cell for user role section

From: Harshal Dhumal <[email-masked]>
Date: Fri, 15 Jan 2016 15:02:51 +0530

Hi,

Please find the attached path for Privilege control with select2cell for
user role section

TODO:  Need to add current database logged in user in Granter filed in
privilege grid control.

-- 
*Harshal Dhumal*
*Software Engineer *

EenterpriseDB <http://www.enterprisedb.com>
diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py
index c38265d..04156ee 100644
--- a/web/pgadmin/browser/server_groups/servers/__init__.py
+++ b/web/pgadmin/browser/server_groups/servers/__init__.py
@@ -115,7 +115,8 @@ class ServerModule(sg.ServerGroupPluginModule):
             {
             'name': 'pgadmin.browser.server.privilege',
             'path': url_for('browser.index') + 'server/static/js/privilege',
-            'when': self.node_type
+            'when': self.node_type,
+            'deps': ['pgadmin.browser.node.ui']
             },
             {
             'name': 'pgadmin.browser.server.variable',
diff --git a/web/pgadmin/browser/server_groups/servers/static/js/privilege.js b/web/pgadmin/browser/server_groups/servers/static/js/privilege.js
index 2f6f052..651f837 100644
--- a/web/pgadmin/browser/server_groups/servers/static/js/privilege.js
+++ b/web/pgadmin/browser/server_groups/servers/static/js/privilege.js
@@ -5,7 +5,7 @@
      function(_, $, Backbone, Backform, Backgrid, Alertify, pgNode) {
       // Export global even in AMD case in case this script is loaded with
       // others that may still expect a global Backform.
-      return factory(root, _, $, Backbone, Backform, Alertify, pgNode);
+      return factory(root, _, $, Backbone, Backform, Backgrid, Alertify, pgNode);
     });
 
   // Next for Node.js or CommonJS. jQuery may not be needed as a module.
@@ -14,15 +14,16 @@
       $ = root.jQuery || root.$ || root.Zepto || root.ender,
       Backbone = require('backbone') || root.Backbone,
       Backform = require('backform') || root.Backform;
+      Backgrid = require('backgrid') || root.Backgrid;
       Alertify = require('alertify') || root.Alertify;
       pgAdmin = require('pgadmin.browser.node') || root.pgAdmin.Browser.Node;
     factory(root, _, $, Backbone, Backform, Alertify, pgNode);
 
   // Finally, as a browser global.
   } else {
-    factory(root, root._, (root.jQuery || root.Zepto || root.ender || root.$), root.Backbone, root.Backform, root.pgAdmin.Browser.Node);
+    factory(root, root._, (root.jQuery || root.Zepto || root.ender || root.$), root.Backbone, root.Backform, root.Backgrid, root.alertify, root.pgAdmin.Browser.Node);
   }
-} (this, function(root, _, $, Backbone, Backform, Alertify, pgNode) {
+} (this, function(root, _, $, Backbone, Backform, Backgrid, Alertify, pgNode) {
 
   /**
    * Each Privilege, supporeted by an database object, will be represented
@@ -66,15 +67,39 @@
     privileges:[],
 
     schema: [{
-      id: 'grantee', label:'Grantee', type:'text', group: null, cell: 'string',
-      disabled: true, cellHeaderClasses: 'width_percent_40'
+      id: 'grantee', label:'Grantee', type:'text', group: null,
+      editable: true, cellHeaderClasses: 'width_percent_40',
+      cell: 'node-list-by-name', node: 'role',
+      disabled : function(column, collection) {
+        if (column instanceof Backbone.Collection) {
+          // This has been called during generating the header cell
+          return false;
+        }
+        return !(this.node_info && this.node_info.server.user.name == column.get('grantor'));
+      },
+      transform: function(data) {
+        var res =
+          Backgrid.Extension.NodeListByNameCell.prototype.defaults.transform.apply(
+            this, arguments
+            );
+        res.unshift({label: 'public', value: 'public'});
+        return res;
+      }
     }, {
       id: 'privileges', label:'Privileges',
       type: 'collection', model: PrivilegeModel, group: null,
-      disabled: false, cell: 'privilege', control: 'text',
-      cellHeaderClasses: 'width_percent_40'
+      cell: 'privilege', control: 'text', cellHeaderClasses: 'width_percent_40',
+      disabled : function(column, collection) {
+        if (column instanceof Backbone.Collection) {
+          // This has been called during generating the header cell
+          return false;
+        }
+        return !(this.node_info && this.node_info.server.user.name == column.get('grantor') ||
+                this.attributes.node_info.server.user.name == column.get('grantor'));
+      }
     },{
-      id: 'grantor', label: 'Granter', type: 'text', disabled: true
+      id: 'grantor', label: 'Granter', type: 'text', disabled: true,
+      cell: 'node-list-by-name', node: 'role'
     }],
 
     /*
diff --git a/web/pgadmin/browser/static/js/node.ui.js b/web/pgadmin/browser/static/js/node.ui.js
index 7642a47..e38adcb 100644
--- a/web/pgadmin/browser/static/js/node.ui.js
+++ b/web/pgadmin/browser/static/js/node.ui.js
@@ -4,7 +4,6 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
 
   var pgBrowser = pgAdmin.Browser;
 
-
   // Store value in DOM as stringified JSON.
   var StringOrJSONFormatter = function() {};
   _.extend(StringOrJSONFormatter.prototype, {
@@ -183,7 +182,7 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
 
   var NodeListByIdControl = Backform.NodeListByIdControl = NodeAjaxOptionsControl.extend({
     controlClassName: 'pgadmin-node-select form-control',
-    defaults: _.extend(NodeAjaxOptionsControl.prototype.defaults, {
+    defaults: _.extend({}, NodeAjaxOptionsControl.prototype.defaults, {
       first_empty: true,
       empty_value: '-- None --',
       url: 'nodes',
@@ -229,7 +228,7 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {

   var NodeListByNameControl = Backform.NodeListByNameControl = NodeListByIdControl.extend({
-    defaults: _.extend(NodeListByIdControl.prototype.defaults, {
+    defaults: _.extend({}, NodeListByIdControl.prototype.defaults, {
       transform: function(rows) {
         var self = this,
             node = self.field.get('schema_node'),
@@ -288,5 +287,273 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
     });
   };
 
+
+  /*
+   * NodeAjaxOptionsCell
+   *   This cell will fetch the options required to render the select
+   *   cell, from the url specific to the pgAdmin.Browser node object.
+   *
+   *   In order to use this properly, schema require to set the 'url' property,
+   *   which exposes the data for this node.
+   *
+   *   In case the url is not providing the data in proper format, we can
+   *   specify the 'transform' function too, which will convert the fetched
+   *   data to proper 'label', 'value' format.
+   */
+  var NodeAjaxOptionsCell = Backgrid.Extension.NodeAjaxOptionsCell = Backgrid.Extension.Select2Cell.extend({
+    defaults: _.extend({}, Backgrid.Extension.Select2Cell.prototype.defaults, {
+      url: undefined,
+      transform: undefined,
+      url_with_id: false,
+      select2: {
+        allowClear: true,
+        placeholder: 'Select from the list',
+        width: 'style'
+      }
+    }),
+    template: _.template(
+      '<option <% if (image) { %> data-image=<%= image %> <% } %> value="<%- value %>" <%= selected ? \'selected="selected"\' : "" %>><%- text %></option>'
+    ),
+    initialize: function () {
+      Backgrid.Extension.Select2Cell.prototype.initialize.apply(this, arguments);
+
+      var col = _.defaults(this.column.toJSON(), this.defaults),
+          model = this.model, column = this.column,
+          editable = Backgrid.callByNeed(col.editable, column, model),
+          optionValues = _.clone(this.optionValues || this.column.get('options'));
+
+
+      var self = this,
+          url = self.column.get('url') || self.defaults.url,
+          m = self.model.handler || self.model;
+
+      // Hmm - we found the url option.
+      // That means - we needs to fetch the options from that node.
+      if (url) {
+        var node = this.column.get('schema_node'),
+            node_info = this.column.get('node_info'),
+            full_url = node.generate_url.apply(
+              node, [
+                null, url, this.column.get('node_data'),
+                this.column.get('url_with_id') || false, node_info
+              ]),
+            cache_level = this.column.get('cache_level') || node.type,
+            cache_node = this.column.get('cache_node');
+
+        cache_node = (cache_node && pgAdmin.Browser.Nodes['cache_node']) || node;
+
+        /*
+         * We needs to check, if we have already cached data for this url.
+         * If yes - use that, and do not bother about fetching it again,
+         * and use it.
+         */
+        var data = cache_node.cache(url, node_info, cache_level);
+
+        if (this.column.get('version_compitible') &&
+            (_.isUndefined(data) || _.isNull(data))) {
+          m.trigger('pgadmin:view:fetching', m, self.column);
+          $.ajax({
+            async: false,
+            url: full_url,
+            success: function(res) {
+              /*
+               * We will cache this data for short period of time for avoiding
+               * same calls.
+               */
+              data = cache_node.cache(url, node_info, cache_level, res.data);
+            },
+            error: function() {
+              m.trigger('pgadmin:view:fetch:error', m, self.column);
+            }
+          });
+          m.trigger('pgadmin:view:fetched', m, self.column);
+        }
+        // To fetch only options from cache, we do not need time from 'at'
+        // attribute but only options.
+        //
+        // It is feasible that the data may not have been fetched.
+        data = (data && data.data) || [];
+
+        /*
+         * Transform the data
+         */
+        transform = this.column.get('transform') || self.defaults.transform;
+        if (transform && _.isFunction(transform)) {
+          // We will transform the data later, when rendering.
+          // It will allow us to generate different data based on the
+          // dependencies.
+          self.column.set('options', transform.bind(self, data));
+        } else {
+          self.column.set('options', data);
+        }
+      }
+    },
+    render: function() {
+      /*
+       * Let SelectCell render it, we will do our magic on the
+       * select control in it.
+       */
+
+      var col = _.defaults(this.column.toJSON(), this.defaults),
+          model = this.model, column = this.column,
+          editable = Backgrid.callByNeed(col.editable, column, model),
+          optionValues = _.clone(this.optionValues ||
+                _.isFunction(this.column.get('options')) ?
+                    this.column.get('options').apply(this) :
+                    this.column.get(' options')),
+          select2_opts = _.defaults({}, col.select2, this.defaults.select2),
+          evalF = function(f, col, m) {
+            return (_.isFunction(f) ? !!f.apply(col, [m]) : !!f);
+          };
+
+      this.$el.empty();
+
+      if (!_.isArray(optionValues)) throw new TypeError("optionValues must be an array");
+
+      /*
+       * Add empty option as Select2 requires any empty '<option><option>' for
+       * some of its functionality to work.
+       */
+      optionValues.unshift({'label':null, 'value':null, 'image':null});
+
+      var optionText = null,
+          optionValue = null,
+          model = this.model,
+          selectedValues = model.get(this.column.get("name"));
+
+      delete this.$select;
+
+      this.$select = $("<select>", {tabIndex: -1}).appendTo(this.$el);
+
+      for (var i = 0; i < optionValues.length; i++) {
+        var op = optionValues[i];
+
+        optionText  = op['label'];
+        optionValue = op['value'];
+        optionImage = op['image'];
+
+        this.$select.append(
+          this.template({
+            text: optionText,
+            value: optionValue,
+            image: optionImage,
+            selected: (selectedValues == optionValue) ||
+              (_.indexOf(selectedValues, optionValue) > -1)
+          }));
+      }
+      // Initialize select2 control.
+      this.$select.select2(
+          _.defaults(
+            {'disabled': !editable},
+            col.select2,
+            this.defaults.select2
+            ));
+
+      /*
+       * If select2 options do not have any disabled property on this cell
+       * and schema has disabled property then we need to apply it
+       */
+      if(!_.has(select2_opts, 'disabled') && (col && col.disabled)) {
+        _.extend(select2_opts, {
+          disabled: evalF(col.disabled, col, this.model)
+        });
+      }
+
+      this.$el.find("select").select2(select2_opts);
+
+      this.delegateEvents();
+
+      return this;
+    }
+  });
+
+  var NodeListByIdCell =  Backgrid.Extension.NodeListByIdCell = NodeAjaxOptionsCell.extend({
+    controlClassName: 'pgadmin-node-select backgrid-cell',
+    defaults: _.extend({}, NodeAjaxOptionsCell.prototype.defaults, {
+      url: 'nodes',
+      filter: undefined,
+      transform: function(rows) {
+        var self = this,
+            node = self.column.get('schema_node'),
+            res = [],
+            filter = self.column.get('filter') || function() { return true; };
+
+        filter = filter.bind(self);
+
+        _.each(rows, function(r) {
+          if (filter(r)) {
+            var l = (_.isFunction(node['node_label']) ?
+                  (node['node_label']).apply(node, [r, self.model, self]) :
+                  r.label),
+                image= (_.isFunction(node['node_image']) ?
+                  (node['node_image']).apply(
+                    node, [r, self.model, self]
+                    ) :
+                  (node['node_image'] || ('icon-' + node.type)));
+
+            res.push({
+              'value': r._id,
+              'image': image,
+              'label': l
+            });
+          }
+        });
+
+        return res;
+      },
+      select2: {
+        placeholder: 'Select from the list',
+        width: 'style',
+        templateResult: formatNode,
+        templateSelection: formatNode
+      }
+    })
+  });
+
+
+  var NodeListByNameCell =  Backgrid.Extension.NodeListByNameCell = NodeAjaxOptionsCell.extend({
+    controlClassName: 'pgadmin-node-select backgrid-cell',
+    defaults: _.extend({}, NodeAjaxOptionsCell.prototype.defaults, {
+      url: 'nodes',
+      filter: undefined,
+      transform: function(rows) {
+        var self = this,
+            node = self.column.get('schema_node'),
+            res = [],
+            filter = self.column.get('filter') || function() { return true; };
+
+        filter = filter.bind(self);
+
+        _.each(rows, function(r) {
+          if (filter(r)) {
+            var l = (_.isFunction(node['node_label']) ?
+                  (node['node_label']).apply(node, [r, self.model, self]) :
+                  r.label),
+                image= (_.isFunction(node['node_image']) ?
+                  (node['node_image']).apply(
+                    node, [r, self.model, self]
+                    ) :
+                  (node['node_image'] || ('icon-' + node.type)));
+
+            res.push({
+              'value': r.label,
+              'image': image,
+              'label': l
+            });
+          }
+        });
+
+        return res;
+      },
+      select2: {
+        placeholder: 'Select from the list',
+        width: 'style',
+        templateResult: formatNode,
+        templateSelection: formatNode
+      }
+    })
+  });
+
+
   return Backform;
 });
diff --git a/web/pgadmin/browser/templates/browser/js/node.js b/web/pgadmin/browser/templates/browser/js/node.js
index fe999bb..622e9e1 100644
--- a/web/pgadmin/browser/templates/browser/js/node.js
+++ b/web/pgadmin/browser/templates/browser/js/node.js
@@ -1289,7 +1289,7 @@ function($, _, S, pgAdmin, Menu, Backbone, Alertify, Backform) {
         }
         idx = _.indexOf(self.sessAttrs['changed'], obj);
         if (!'sessChanged' in obj) {
-          if (idx > 0) {
+          if (idx >= 0) {
             return true;
           }
           self.sessAttrs['changed'].push(obj);
@@ -1301,7 +1301,7 @@ function($, _, S, pgAdmin, Menu, Backbone, Alertify, Backform) {
 
           return true;
         }
-        if (idx > 0) {
+        if (idx >= 0) {
 
           (self || self.handler).trigger(
               'pgadmin-session:changed',
diff --git a/web/pgadmin/static/js/backform.pgadmin.js b/web/pgadmin/static/js/backform.pgadmin.js
index 42aaa55..e99c96e 100644
--- a/web/pgadmin/static/js/backform.pgadmin.js
+++ b/web/pgadmin/static/js/backform.pgadmin.js
@@ -76,6 +76,7 @@
     'uniqueColCollection': ['unique-col-collection', 'unique-col-collection', 'string'],
     'switch' : 'switch',
     'select2': 'select2',
+    'radio' : 'radio'
   };
 
   var getMappedControl = Backform.getMappedControl = function(type, mode) {
@@ -724,7 +725,7 @@
            * conflicting with another model value.
            */
 
-          m.set(uniqueChangedAttr[0], m.previous(uniqueChangedAttr[0]), {silent: true});
+          m.set(uniqueChangedAttr[0], m.previous(uniqueChangedAttr[0]));
         }
         if (oldModel) {
           var idx = collection.indexOf(oldModel);
@@ -1204,7 +1205,7 @@
             );
       }
 
-      // After validation we need to set that value into model (only if all falgs are true)
+      // After validation we need to set that value into model (only if all flags are true)
       if (isValid) {
         this.stopListening(this.model, "change:" + name, this.render);
         this.model.set(name, value);
diff --git a/web/pgadmin/static/js/backgrid/backgrid.pgadmin.js b/web/pgadmin/static/js/backgrid/backgrid.pgadmin.js
index 85f99a6..b9a3f81 100644
--- a/web/pgadmin/static/js/backgrid/backgrid.pgadmin.js
+++ b/web/pgadmin/static/js/backgrid/backgrid.pgadmin.js
@@ -367,7 +367,7 @@
     onSave: function (e) {
       var model = this.model;
       var column = this.column;
-      model.set(column.get("name"), this.$select.val(),{silent:true});
+      model.set(column.get("name"), this.$select.val());
     }
   });
   return Backgrid;

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

---

From: Ashesh Vashi <[email-masked]>
Date: Fri, 15 Jan 2016 19:28:18 +0530

> Please find the attached path for Privilege control with select2cell for
I've checked in this one as two commits.
1. Introduction of the NodeAjaxOptionsCell, and variants of them.
2. Privilege changes.

> TODO:  Need to add current database logged in user in Granter filed in
I've taken care of it now.

Thanks,

Ashesh Vashi