public inbox for [email protected]
help / color / mirror / Atom feed[pgAdmin4] [Patch]: Extension Module
13+ messages / 3 participants
[nested] [flat]
* [pgAdmin4] [Patch]: Extension Module
@ 2016-01-12 07:14 Surinder Kumar <[email protected]>
2016-01-12 07:45 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Surinder Kumar @ 2016-01-12 07:14 UTC (permalink / raw)
To: pgadmin-hackers
Hi,
Please find attached patch for the extension module.
Please review it 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:
[text/x-patch] extension.patch (28.4K, 3-extension.patch)
download | inline diff:
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py
new file mode 100644
index 0000000..33f3fba
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py
@@ -0,0 +1,356 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2015, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+from flask import render_template, make_response, request, jsonify
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_json_response, \
+ make_response as ajax_response, internal_server_error
+from pgadmin.browser.utils import NodeView
+from pgadmin.browser.collection import CollectionNodeModule
+import pgadmin.browser.server_groups.servers.databases as databases
+from pgadmin.utils.ajax import precondition_required
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from functools import wraps
+
+
+class ExtensionModule(CollectionNodeModule):
+ NODE_TYPE = "extension"
+ COLLECTION_LABEL = gettext("Extensions")
+
+ def __init__(self, *args, **kwargs):
+ super(ExtensionModule, self).__init__(*args, **kwargs)
+
+ def get_nodes(self, gid, sid, did):
+ """
+ Generate the collection node
+ """
+ yield self.generate_browser_collection_node(did)
+
+ @property
+ def script_load(self):
+ """
+ Load the module script for server, when any of the server-group node is
+ initialized.
+ """
+ return databases.DatabaseModule.NODE_TYPE
+
+
+blueprint = ExtensionModule(__name__)
+
+
+class ExtensionView(NodeView):
+ node_type = blueprint.node_type
+
+ parent_ids = [
+ {'type': 'int', 'id': 'gid'},
+ {'type': 'int', 'id': 'sid'},
+ {'type': 'int', 'id': 'did'}
+ ]
+ ids = [
+ {'type': 'int', 'id': 'eid'}
+ ]
+
+ operations = dict({
+ 'obj': [
+ {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+ {'get': 'list', 'post': 'create'}
+ ],
+ 'delete': [{'delete': 'delete'}],
+ 'nodes': [{'get': 'node'}, {'get': 'nodes'}],
+ 'sql': [{'get': 'sql'}],
+ 'msql': [{'get': 'msql'}, {'get': 'msql'}],
+ 'stats': [{'get': 'statistics'}],
+ 'dependency': [{'get': 'dependencies'}],
+ 'dependent': [{'get': 'dependents'}],
+ 'module.js': [{}, {}, {'get': 'module_js'}],
+ 'avails': [{}, {'get': 'avails'}],
+ 'schemas': [{}, {'get': 'schemas'}],
+ 'children': [{'get': 'children'}]
+ })
+
+ 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 self
+ """
+ @wraps(f)
+ def wrap(*args, **kwargs):
+ # Here args[0] will hold self & kwargs will hold gid,sid,did
+ self = args[0]
+ self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(kwargs['sid'])
+ self.conn = self.manager.connection(did=kwargs['did'])
+ self.template_path = 'extensions/sql'
+
+ return f(*args, **kwargs)
+ return wrap
+
+ @check_precondition
+ def list(self, gid, sid, did):
+ """
+ It fetches all extensions properties and render into properties
+ tab
+ """
+ SQL = render_template("/".join([self.template_path, 'properties.sql']))
+ status, res = self.conn.execute_dict(SQL)
+
+ if not status:
+ return internal_server_error(errormsg=res)
+ return ajax_response(
+ response=res['rows'],
+ status=200
+ )
+
+ @check_precondition
+ def nodes(self, gid, sid, did):
+ """
+ It lists down the all extensions under the Extensions Collection node
+ """
+ res = []
+ SQL = render_template("/".join([self.template_path, 'properties.sql']))
+ status, rset = self.conn.execute_2darray(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+
+ for row in rset['rows']:
+ res.append(
+ self.blueprint.generate_browser_node(
+ row['eid'],
+ row['name'],
+ 'icon-extension'
+ ))
+
+ return make_json_response(
+ data=res,
+ status=200
+ )
+
+ @check_precondition
+ def properties(self, gid, sid, did, eid):
+ """
+ It fetches the properties of a single extension
+ and render in properties tab
+
+ """
+ SQL = render_template("/".join([self.template_path, 'properties.sql']), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return ajax_response(
+ response=res['rows'][0],
+ status=200
+ )
+
+ @check_precondition
+ def create(self, gid, sid, did):
+ """
+ This function will creates new the extension object
+ """
+ required_args = [
+ 'name',
+ 'version'
+ ]
+
+ data = request.form if request.form else json.loads(request.data.decode())
+ for arg in required_args:
+ if arg not in data:
+ return make_json_response(
+ status=410,
+ success=0,
+ errormsg=gettext(
+ "Couldn't find the required parameter (%s)." % arg
+ )
+ )
+
+ status, res = self.conn.execute_dict(
+ render_template(
+ "/".join([self.template_path, 'create.sql']),
+ data=data
+ )
+ )
+
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ status, rset = self.conn.execute_dict(
+ render_template(
+ "/".join([self.template_path, 'properties.sql']),
+ ename=data['name']
+ )
+ )
+
+ if not status:
+ return internal_server_error(errormsg=rset)
+
+ for row in rset['rows']:
+ return jsonify(
+ node=self.blueprint.generate_browser_node(
+ row['eid'],
+ row['name'],
+ 'icon-extension'
+ )
+ )
+
+ @check_precondition
+ def update(self, gid, sid, did, eid):
+ """
+ This function will update extension object
+ """
+ data = request.form if request.form else json.loads(request.data.decode())
+ SQL = self.getSQL(gid, sid, data, did, eid)
+
+ try:
+ if SQL and SQL.strip('\n') and SQL.strip(' '):
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return make_json_response(
+ success=1,
+ info="Extension updated",
+ data={
+ 'id': eid,
+ 'sid': sid,
+ 'gid': gid
+ }
+ )
+ else:
+ return make_json_response(
+ success=1,
+ info="Nothing to update",
+ data={
+ 'id': did,
+ 'sid': sid,
+ 'gid': gid
+ }
+ )
+
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def delete(self, gid, sid, did, eid):
+ """
+ This function will delete drop/drop cascade the extension object
+ """
+ cascade = True if self.cmd == 'delete' else False
+ try:
+ # check if extension with eid exists
+ SQL = render_template("/".join([self.template_path, 'delete.sql']), eid=eid)
+ status, name = self.conn.execute_scalar(SQL)
+ if not status:
+ return internal_server_error(errormsg=name)
+ # drop extension
+ SQL = render_template("/".join([self.template_path, 'delete.sql']), name=name, cascade=cascade)
+ status, res = self.conn.execute_scalar(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return make_json_response(
+ success=1,
+ info=gettext("Extension dropped"),
+ data={
+ 'id': did,
+ 'sid': sid,
+ 'gid': gid,
+ }
+ )
+
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def msql(self, gid, sid, did, eid=None):
+ """
+ This function to return modified SQL
+ """
+ data = request.args.copy()
+ SQL = self.getSQL(gid, sid, data, did, eid)
+ if SQL.strip('\n') and SQL.strip(' '):
+ return make_json_response(
+ data=SQL,
+ status=200
+ )
+ else:
+ return make_json_response(
+ data='-- Modified SQL --',
+ status=200
+ )
+
+ def getSQL(self, gid, sid, data, did, eid=None):
+ """
+ This function will generate sql from model data
+ """
+ required_args = [
+ 'name',
+ 'schema',
+ 'version'
+ ]
+ try:
+ if eid is not None:
+ SQL = render_template("/".join([self.template_path, 'properties.sql']), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+ old_data = res['rows'][0]
+ for arg in required_args:
+ if arg not in data:
+ data[arg] = old_data[arg]
+ SQL = render_template("/".join([self.template_path, 'update.sql']), data=data, o_data=old_data)
+ else:
+ SQL = render_template("/".join([self.template_path, 'create.sql']), data=data)
+ return SQL
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def avails(self, gid, sid, did):
+ """
+ This function with fetch all the available extensions
+ """
+ SQL = render_template("/".join([self.template_path, 'extensions.sql']))
+ status, rset = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+ return make_json_response(
+ data=rset['rows'],
+ status=200
+ )
+
+ @check_precondition
+ def schemas(self, gid, sid, did):
+ """
+ This function with fetch all the schemas
+ """
+ SQL = render_template("/".join([self.template_path, 'schemas.sql']))
+ status, rset = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+ return make_json_response(
+ data=rset['rows'],
+ status=200
+ )
+
+ def module_js(self):
+ """
+ This property defines (if javascript) exists for this node.
+ Override this property for your own logic.
+ """
+ return make_response(
+ render_template(
+ "extensions/js/extensions.js",
+ _=gettext
+ ),
+ 200, {'Content-Type': 'application/x-javascript'}
+ )
+
+ExtensionView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/static/img/coll-extension.png b/web/pgadmin/browser/server_groups/servers/databases/extensions/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/browser/server_groups/servers/databases/extensions/static/img/extension.png b/web/pgadmin/browser/server_groups/servers/databases/extensions/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/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js
new file mode 100644
index 0000000..a21bf7b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js
@@ -0,0 +1,188 @@
+define(
+ ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'pgadmin.browser.collection'],
+function($, _, S, pgAdmin, pgBrowser) {
+
+ if (!pgBrowser.Nodes['coll-extension']) {
+ var extensions = pgAdmin.Browser.Nodes['coll-extension'] =
+ pgAdmin.Browser.Collection.extend({
+ node: 'extension',
+ label: '{{ _('Extension') }}',
+ type: 'coll-extension',
+ columns: ['name', 'owner', 'comment']
+ });
+ };
+
+ if (!pgBrowser.Nodes['extension']) {
+ pgAdmin.Browser.Nodes['extension'] =
+ pgAdmin.Browser.Node.extend({
+ parent_type: 'database',
+ type: 'extension',
+ hasSQL: true,
+ canDrop: true,
+ canDropCascade: true,
+ label: '{{ _('Extension') }}',
+
+ Init: function(){
+ if(this.initialized)
+ return;
+
+ this.initialized = true;
+
+ pgBrowser.add_menus([{
+ name: 'create_extension_on_coll', node: 'coll-extension', module: this,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ category: 'create', priority: 4, label: '{{ _('Extension...') }}',
+ icon: 'wcTabIcon pg-icon-extension', data: {action: 'create'}
+ },{
+ name: 'create_extension', node: 'extension', module: this,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ category: 'create', priority: 4, label: '{{ _('Extension...') }}',
+ icon: 'wcTabIcon pg-icon-extension', data: {action: 'create'}
+ }
+ ]);
+ },
+ model: pgAdmin.Browser.Node.Model.extend({
+ schema: [
+ {
+ id: 'name', label: '{{ _('Name')}}',
+ type: 'text', mode: ['properties', 'create', 'edit'],
+ visible: true, url:'avails', disabled: function(m) {
+ return !m.isNew();
+ },
+ transform: function(data) {
+ var res = [];
+ var label = this.model.get('name');
+ if (!this.model.isNew()){
+ res.push({label: label, value: label});
+ } else {
+ if (data && _.isArray(data)) {
+ res.push({label: '', value: ''});
+ _.each(data, function(d) {
+ if (d.installed_version == null)
+ /* d contains json data and sets into
+ * select's option control
+ */
+ res.push({label: d.name, value: d});
+ })
+ }
+ }
+ return res;
+ },
+ /* extends NodeAjaxOptionsControl to override the properties
+ * getValueFromDOM which takes stringified data from option of
+ * select control and parse it. And `onChange` takes the stringified
+ * data from select's option, thus convert it to json format and set the
+ * data into Model which is used to enable/disable the schema field.
+ */
+ control: Backform.NodeAjaxOptionsControl.extend({
+ getValueFromDOM: function() {
+ var data = this.formatter.toRaw(this.$el.find("select").val(), this.model);
+ return data.name;
+ },
+ onChange: function() {
+ Backform.NodeAjaxOptionsControl.prototype.onChange.apply(this, arguments);
+ var selectedValue = this.$el.find("select").val();
+ if (selectedValue.trim() != ""){
+ var d = this.formatter.toRaw(selectedValue, this.model);
+ var changes = {
+ 'version': (!_.isNull(d.version[0]) ? d.version[0]: ''),
+ 'relocatable': (!_.isNull(d.relocatable[0]) ? d.relocatable[0]: ''),
+ 'schema': (!_.isNull(d.schema[0]) ? d.schema[0]: '')
+ };
+ this.model.set(changes);
+ }else{
+ var changes = {'version': '', 'relocatable': true, 'schema': ''};
+ this.model.set(changes);
+ }
+ },
+ })
+ },{
+ id: 'eid', label: '{{ _('Oid')}}', cell: 'string',
+ type: 'text', disabled: true, mode: ['properties', 'edit', 'create']
+ },
+ {
+ id: 'owner', label: '{{ _('Owner')}}', cell: 'string',
+ type: 'text', mode: ['properties']
+ },
+ {
+ id: 'schema', label: '{{ _('Schema')}}', type: 'text', control: 'node-ajax-options',
+ mode: ['properties', 'create', 'edit'], group: 'Definition', deps: ['relocatable'],
+ url: 'schemas', disabled: function(m) {
+ /* enable or disable schema field if model's relocatable
+ * attribute is True or False
+ */
+ return (m.has('relocatable') ? !m.get('relocatable') : false);
+ },
+ transform: function(data) {
+ var res = [];
+ if (data && _.isArray(data)) {
+ _.each(data, function(d) {
+ res.push({label: d.schema, value: d.schema});
+ })
+ }
+ return res;
+ }
+ },
+ {
+ id: 'relocatable', label: '{{ _('Relocatable?')}}', cell: 'switch',
+ type: 'switch', mode: ['properties'], 'options': {
+ 'onText': 'Yes', 'offText': 'No', 'onColor': 'success',
+ 'offColor': 'default', 'size': 'small'
+ }
+ },
+ {
+ id: 'version', label: '{{ _('Version')}}', cell: 'string',
+ type: 'options', mode: ['properties', 'create', 'edit'], group: 'Definition',
+ disabled: false, control: 'node-ajax-options', url:'avails',
+ /*
+ * Transform the data into version for the selected extension.
+ */
+ transform: function(data) {
+ res = [];
+ var extension = this.model.get('name');
+ _.each(data, function(dt){
+ if(dt.name == extension){
+ if(dt.version && _.isArray(dt.version)){
+ _.each(dt.version, function(v){
+ res.push({ label: v, value: v });
+ });
+ }
+ }
+ });
+ return res;
+ }
+ },
+ {
+ id: 'comment', label: '{{ _('Comment')}}', cell: 'string',
+ type: 'multiline', disabled: true
+ },
+ {
+ id: 'slony', label: '{{ _('Use Slony')}}', cell: 'string',
+ type: 'text', disabled: true, mode: ['create', 'edit']
+ }
+ ],
+ validate: function() {
+ /*
+ * Triggers specific error messages for name and
+ * version if it is empty
+ */
+ var changedAttrs = this.changed;
+ if (_.has(changedAttrs, this.get('name')) && _.isUndefined(this.get('name')) || String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
+ var msg = '{{ _('Name can not be empty!') }}';
+ this.trigger('on-status',{msg:msg});
+ return msg;
+ }
+ if (_.has(changedAttrs, this.get('version')) && _.isUndefined(this.get('version')) || String(this.get('version')).replace(/^\s+|\s+$/g, '') == '') {
+ var msg = '{{ _('Version can not be empty!') }}';
+ this.trigger('on-status',{msg:msg});
+ return msg;
+ }
+ this.trigger('on-status-clear');
+ return true;
+ }
+ })
+ })
+ };
+
+ return pgBrowser.Nodes['coll-extension'];
+});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql
new file mode 100644
index 0000000..a68b7ce
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql
@@ -0,0 +1,10 @@
+{#=========================Create new extension======================#}
+{% if data.name %}
+CREATE EXTENSION {{ data.name }}
+{% if data.schema %}
+ SCHEMA {{ data.schema }}
+{% endif %}
+{% if data.version %}
+ VERSION '{{ data.version }}';
+{% endif %}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql
new file mode 100644
index 0000000..12741fb
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql
@@ -0,0 +1,8 @@
+{#============================Drop Extension by name=========================#}
+{% if eid %}
+SELECT x.extname from pg_extension x
+ WHERE x.oid = {{ eid }}::int
+{% endif %}
+{% if name %}
+DROP EXTENSION {{ name }} {% if cascade %} CASCADE {% endif %}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql
new file mode 100644
index 0000000..bf3979d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql
@@ -0,0 +1,12 @@
+{# ======================Fetch extensions names=====================#}
+SELECT
+ a.name, a.installed_version,
+ array_agg(av.version) as version,
+ array_agg(av.schema) as schema,
+ array_agg(av.superuser) as superuser,
+ array_agg(av.relocatable) as relocatable
+FROM
+ pg_available_extensions a
+ LEFT JOIN pg_available_extension_versions av ON (a.name = av.name)
+GROUP BY a.name, a.installed_version
+ORDER BY a.name
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql
new file mode 100644
index 0000000..b609883
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql
@@ -0,0 +1,17 @@
+{#===================Fetch properties of each extension by name or oid===================#}
+SELECT
+ x.oid AS eid, pg_get_userbyid(extowner) AS owner,
+ x.extname AS name, n.nspname AS schema,
+ x.extrelocatable AS relocatable, x.extversion AS version,
+ e.comment
+FROM
+ pg_extension x
+ LEFT JOIN pg_namespace n ON x.extnamespace=n.oid
+ JOIN pg_available_extensions() e(name, default_version, comment) ON x.extname=e.name
+{%- if eid %}
+ WHERE x.oid = {{eid}}::int
+{% elif ename %}
+ WHERE x.extname = '{{ename}}'::text
+{% else %}
+ ORDER BY x.extname
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql
new file mode 100644
index 0000000..b8e3c97
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql
@@ -0,0 +1,3 @@
+{#===================fetch all schemas==========================#}
+SELECT nspname As schema FROM pg_namespace
+ ORDER BY nspname
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql
new file mode 100644
index 0000000..cbdaa60
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql
@@ -0,0 +1,10 @@
+{# =============Update extension schema============= #}
+{% if data.schema != o_data.schema %}
+ALTER EXTENSION {{ o_data.name }}
+ SET SCHEMA {{ data.schema|e }};
+{% endif %}
+{# =============Update extension version============= #}
+{% if data.version and data.version != o_data.version %}
+ALTER EXTENSION {{ o_data.name }}
+ UPDATE TO '{{ data.version }}';
+{% endif %}
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin4] [Patch]: Extension Module
2016-01-12 07:14 [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
@ 2016-01-12 07:45 ` Surinder Kumar <[email protected]>
2016-01-18 11:22 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Surinder Kumar @ 2016-01-12 07:45 UTC (permalink / raw)
To: pgadmin-hackers
Please find the updated patch with following changes:
1. corrected copyright.
2. Added proper comment for script_module function in __init__.py file.
3. Renamed collection Node's label to Extensions in extensions.js file.
On Tue, Jan 12, 2016 at 12:44 PM, Surinder Kumar <
[email protected]> wrote:
> Hi,
>
> Please find attached patch for the extension module.
> Please review it 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:
[text/x-patch] extension.patch (28.5K, 3-extension.patch)
download | inline diff:
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py
new file mode 100644
index 0000000..33f3fba
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py
@@ -0,0 +1,356 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+from flask import render_template, make_response, request, jsonify
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_json_response, \
+ make_response as ajax_response, internal_server_error
+from pgadmin.browser.utils import NodeView
+from pgadmin.browser.collection import CollectionNodeModule
+import pgadmin.browser.server_groups.servers.databases as databases
+from pgadmin.utils.ajax import precondition_required
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from functools import wraps
+
+
+class ExtensionModule(CollectionNodeModule):
+ NODE_TYPE = "extension"
+ COLLECTION_LABEL = gettext("Extensions")
+
+ def __init__(self, *args, **kwargs):
+ super(ExtensionModule, self).__init__(*args, **kwargs)
+
+ def get_nodes(self, gid, sid, did):
+ """
+ Generate the collection node
+ """
+ yield self.generate_browser_collection_node(did)
+
+ @property
+ def script_load(self):
+ """
+ Load the module script for extension, when any of the database node is
+ initialized.
+ """
+ return databases.DatabaseModule.NODE_TYPE
+
+
+blueprint = ExtensionModule(__name__)
+
+
+class ExtensionView(NodeView):
+ node_type = blueprint.node_type
+
+ parent_ids = [
+ {'type': 'int', 'id': 'gid'},
+ {'type': 'int', 'id': 'sid'},
+ {'type': 'int', 'id': 'did'}
+ ]
+ ids = [
+ {'type': 'int', 'id': 'eid'}
+ ]
+
+ operations = dict({
+ 'obj': [
+ {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+ {'get': 'list', 'post': 'create'}
+ ],
+ 'delete': [{'delete': 'delete'}],
+ 'nodes': [{'get': 'node'}, {'get': 'nodes'}],
+ 'sql': [{'get': 'sql'}],
+ 'msql': [{'get': 'msql'}, {'get': 'msql'}],
+ 'stats': [{'get': 'statistics'}],
+ 'dependency': [{'get': 'dependencies'}],
+ 'dependent': [{'get': 'dependents'}],
+ 'module.js': [{}, {}, {'get': 'module_js'}],
+ 'avails': [{}, {'get': 'avails'}],
+ 'schemas': [{}, {'get': 'schemas'}],
+ 'children': [{'get': 'children'}]
+ })
+
+ 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 self
+ """
+ @wraps(f)
+ def wrap(*args, **kwargs):
+ # Here args[0] will hold self & kwargs will hold gid,sid,did
+ self = args[0]
+ self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(kwargs['sid'])
+ self.conn = self.manager.connection(did=kwargs['did'])
+ self.template_path = 'extensions/sql'
+
+ return f(*args, **kwargs)
+ return wrap
+
+ @check_precondition
+ def list(self, gid, sid, did):
+ """
+ It fetches all extensions properties and render into properties
+ tab
+ """
+ SQL = render_template("/".join([self.template_path, 'properties.sql']))
+ status, res = self.conn.execute_dict(SQL)
+
+ if not status:
+ return internal_server_error(errormsg=res)
+ return ajax_response(
+ response=res['rows'],
+ status=200
+ )
+
+ @check_precondition
+ def nodes(self, gid, sid, did):
+ """
+ It lists down the all extensions under the Extensions Collection node
+ """
+ res = []
+ SQL = render_template("/".join([self.template_path, 'properties.sql']))
+ status, rset = self.conn.execute_2darray(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+
+ for row in rset['rows']:
+ res.append(
+ self.blueprint.generate_browser_node(
+ row['eid'],
+ row['name'],
+ 'icon-extension'
+ ))
+
+ return make_json_response(
+ data=res,
+ status=200
+ )
+
+ @check_precondition
+ def properties(self, gid, sid, did, eid):
+ """
+ It fetches the properties of a single extension
+ and render in properties tab
+
+ """
+ SQL = render_template("/".join([self.template_path, 'properties.sql']), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return ajax_response(
+ response=res['rows'][0],
+ status=200
+ )
+
+ @check_precondition
+ def create(self, gid, sid, did):
+ """
+ This function will creates new the extension object
+ """
+ required_args = [
+ 'name',
+ 'version'
+ ]
+
+ data = request.form if request.form else json.loads(request.data.decode())
+ for arg in required_args:
+ if arg not in data:
+ return make_json_response(
+ status=410,
+ success=0,
+ errormsg=gettext(
+ "Couldn't find the required parameter (%s)." % arg
+ )
+ )
+
+ status, res = self.conn.execute_dict(
+ render_template(
+ "/".join([self.template_path, 'create.sql']),
+ data=data
+ )
+ )
+
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ status, rset = self.conn.execute_dict(
+ render_template(
+ "/".join([self.template_path, 'properties.sql']),
+ ename=data['name']
+ )
+ )
+
+ if not status:
+ return internal_server_error(errormsg=rset)
+
+ for row in rset['rows']:
+ return jsonify(
+ node=self.blueprint.generate_browser_node(
+ row['eid'],
+ row['name'],
+ 'icon-extension'
+ )
+ )
+
+ @check_precondition
+ def update(self, gid, sid, did, eid):
+ """
+ This function will update extension object
+ """
+ data = request.form if request.form else json.loads(request.data.decode())
+ SQL = self.getSQL(gid, sid, data, did, eid)
+
+ try:
+ if SQL and SQL.strip('\n') and SQL.strip(' '):
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return make_json_response(
+ success=1,
+ info="Extension updated",
+ data={
+ 'id': eid,
+ 'sid': sid,
+ 'gid': gid
+ }
+ )
+ else:
+ return make_json_response(
+ success=1,
+ info="Nothing to update",
+ data={
+ 'id': did,
+ 'sid': sid,
+ 'gid': gid
+ }
+ )
+
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def delete(self, gid, sid, did, eid):
+ """
+ This function will delete drop/drop cascade the extension object
+ """
+ cascade = True if self.cmd == 'delete' else False
+ try:
+ # check if extension with eid exists
+ SQL = render_template("/".join([self.template_path, 'delete.sql']), eid=eid)
+ status, name = self.conn.execute_scalar(SQL)
+ if not status:
+ return internal_server_error(errormsg=name)
+ # drop extension
+ SQL = render_template("/".join([self.template_path, 'delete.sql']), name=name, cascade=cascade)
+ status, res = self.conn.execute_scalar(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return make_json_response(
+ success=1,
+ info=gettext("Extension dropped"),
+ data={
+ 'id': did,
+ 'sid': sid,
+ 'gid': gid,
+ }
+ )
+
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def msql(self, gid, sid, did, eid=None):
+ """
+ This function to return modified SQL
+ """
+ data = request.args.copy()
+ SQL = self.getSQL(gid, sid, data, did, eid)
+ if SQL.strip('\n') and SQL.strip(' '):
+ return make_json_response(
+ data=SQL,
+ status=200
+ )
+ else:
+ return make_json_response(
+ data='-- Modified SQL --',
+ status=200
+ )
+
+ def getSQL(self, gid, sid, data, did, eid=None):
+ """
+ This function will generate sql from model data
+ """
+ required_args = [
+ 'name',
+ 'schema',
+ 'version'
+ ]
+ try:
+ if eid is not None:
+ SQL = render_template("/".join([self.template_path, 'properties.sql']), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+ old_data = res['rows'][0]
+ for arg in required_args:
+ if arg not in data:
+ data[arg] = old_data[arg]
+ SQL = render_template("/".join([self.template_path, 'update.sql']), data=data, o_data=old_data)
+ else:
+ SQL = render_template("/".join([self.template_path, 'create.sql']), data=data)
+ return SQL
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def avails(self, gid, sid, did):
+ """
+ This function with fetch all the available extensions
+ """
+ SQL = render_template("/".join([self.template_path, 'extensions.sql']))
+ status, rset = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+ return make_json_response(
+ data=rset['rows'],
+ status=200
+ )
+
+ @check_precondition
+ def schemas(self, gid, sid, did):
+ """
+ This function with fetch all the schemas
+ """
+ SQL = render_template("/".join([self.template_path, 'schemas.sql']))
+ status, rset = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+ return make_json_response(
+ data=rset['rows'],
+ status=200
+ )
+
+ def module_js(self):
+ """
+ This property defines (if javascript) exists for this node.
+ Override this property for your own logic.
+ """
+ return make_response(
+ render_template(
+ "extensions/js/extensions.js",
+ _=gettext
+ ),
+ 200, {'Content-Type': 'application/x-javascript'}
+ )
+
+ExtensionView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/static/img/coll-extension.png b/web/pgadmin/browser/server_groups/servers/databases/extensions/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/browser/server_groups/servers/databases/extensions/static/img/extension.png b/web/pgadmin/browser/server_groups/servers/databases/extensions/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/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js
new file mode 100644
index 0000000..a21bf7b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js
@@ -0,0 +1,188 @@
+define(
+ ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'pgadmin.browser.collection'],
+function($, _, S, pgAdmin, pgBrowser) {
+
+ if (!pgBrowser.Nodes['coll-extension']) {
+ var extensions = pgAdmin.Browser.Nodes['coll-extension'] =
+ pgAdmin.Browser.Collection.extend({
+ node: 'extension',
+ label: '{{ _('Extensions') }}',
+ type: 'coll-extension',
+ columns: ['name', 'owner', 'comment']
+ });
+ };
+
+ if (!pgBrowser.Nodes['extension']) {
+ pgAdmin.Browser.Nodes['extension'] =
+ pgAdmin.Browser.Node.extend({
+ parent_type: 'database',
+ type: 'extension',
+ hasSQL: true,
+ canDrop: true,
+ canDropCascade: true,
+ label: '{{ _('Extension') }}',
+
+ Init: function(){
+ if(this.initialized)
+ return;
+
+ this.initialized = true;
+
+ pgBrowser.add_menus([{
+ name: 'create_extension_on_coll', node: 'coll-extension', module: this,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ category: 'create', priority: 4, label: '{{ _('Extension...') }}',
+ icon: 'wcTabIcon pg-icon-extension', data: {action: 'create'}
+ },{
+ name: 'create_extension', node: 'extension', module: this,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ category: 'create', priority: 4, label: '{{ _('Extension...') }}',
+ icon: 'wcTabIcon pg-icon-extension', data: {action: 'create'}
+ }
+ ]);
+ },
+ model: pgAdmin.Browser.Node.Model.extend({
+ schema: [
+ {
+ id: 'name', label: '{{ _('Name')}}',
+ type: 'text', mode: ['properties', 'create', 'edit'],
+ visible: true, url:'avails', disabled: function(m) {
+ return !m.isNew();
+ },
+ transform: function(data) {
+ var res = [];
+ var label = this.model.get('name');
+ if (!this.model.isNew()){
+ res.push({label: label, value: label});
+ } else {
+ if (data && _.isArray(data)) {
+ res.push({label: '', value: ''});
+ _.each(data, function(d) {
+ if (d.installed_version == null)
+ /* d contains json data and sets into
+ * select's option control
+ */
+ res.push({label: d.name, value: d});
+ })
+ }
+ }
+ return res;
+ },
+ /* extends NodeAjaxOptionsControl to override the properties
+ * getValueFromDOM which takes stringified data from option of
+ * select control and parse it. And `onChange` takes the stringified
+ * data from select's option, thus convert it to json format and set the
+ * data into Model which is used to enable/disable the schema field.
+ */
+ control: Backform.NodeAjaxOptionsControl.extend({
+ getValueFromDOM: function() {
+ var data = this.formatter.toRaw(this.$el.find("select").val(), this.model);
+ return data.name;
+ },
+ onChange: function() {
+ Backform.NodeAjaxOptionsControl.prototype.onChange.apply(this, arguments);
+ var selectedValue = this.$el.find("select").val();
+ if (selectedValue.trim() != ""){
+ var d = this.formatter.toRaw(selectedValue, this.model);
+ var changes = {
+ 'version': (!_.isNull(d.version[0]) ? d.version[0]: ''),
+ 'relocatable': (!_.isNull(d.relocatable[0]) ? d.relocatable[0]: ''),
+ 'schema': (!_.isNull(d.schema[0]) ? d.schema[0]: '')
+ };
+ this.model.set(changes);
+ }else{
+ var changes = {'version': '', 'relocatable': true, 'schema': ''};
+ this.model.set(changes);
+ }
+ },
+ })
+ },{
+ id: 'eid', label: '{{ _('Oid')}}', cell: 'string',
+ type: 'text', disabled: true, mode: ['properties', 'edit', 'create']
+ },
+ {
+ id: 'owner', label: '{{ _('Owner')}}', cell: 'string',
+ type: 'text', mode: ['properties']
+ },
+ {
+ id: 'schema', label: '{{ _('Schema')}}', type: 'text', control: 'node-ajax-options',
+ mode: ['properties', 'create', 'edit'], group: 'Definition', deps: ['relocatable'],
+ url: 'schemas', disabled: function(m) {
+ /* enable or disable schema field if model's relocatable
+ * attribute is True or False
+ */
+ return (m.has('relocatable') ? !m.get('relocatable') : false);
+ },
+ transform: function(data) {
+ var res = [];
+ if (data && _.isArray(data)) {
+ _.each(data, function(d) {
+ res.push({label: d.schema, value: d.schema});
+ })
+ }
+ return res;
+ }
+ },
+ {
+ id: 'relocatable', label: '{{ _('Relocatable?')}}', cell: 'switch',
+ type: 'switch', mode: ['properties'], 'options': {
+ 'onText': 'Yes', 'offText': 'No', 'onColor': 'success',
+ 'offColor': 'default', 'size': 'small'
+ }
+ },
+ {
+ id: 'version', label: '{{ _('Version')}}', cell: 'string',
+ type: 'options', mode: ['properties', 'create', 'edit'], group: 'Definition',
+ disabled: false, control: 'node-ajax-options', url:'avails',
+ /*
+ * Transform the data into version for the selected extension.
+ */
+ transform: function(data) {
+ res = [];
+ var extension = this.model.get('name');
+ _.each(data, function(dt){
+ if(dt.name == extension){
+ if(dt.version && _.isArray(dt.version)){
+ _.each(dt.version, function(v){
+ res.push({ label: v, value: v });
+ });
+ }
+ }
+ });
+ return res;
+ }
+ },
+ {
+ id: 'comment', label: '{{ _('Comment')}}', cell: 'string',
+ type: 'multiline', disabled: true
+ },
+ {
+ id: 'slony', label: '{{ _('Use Slony')}}', cell: 'string',
+ type: 'text', disabled: true, mode: ['create', 'edit']
+ }
+ ],
+ validate: function() {
+ /*
+ * Triggers specific error messages for name and
+ * version if it is empty
+ */
+ var changedAttrs = this.changed;
+ if (_.has(changedAttrs, this.get('name')) && _.isUndefined(this.get('name')) || String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
+ var msg = '{{ _('Name can not be empty!') }}';
+ this.trigger('on-status',{msg:msg});
+ return msg;
+ }
+ if (_.has(changedAttrs, this.get('version')) && _.isUndefined(this.get('version')) || String(this.get('version')).replace(/^\s+|\s+$/g, '') == '') {
+ var msg = '{{ _('Version can not be empty!') }}';
+ this.trigger('on-status',{msg:msg});
+ return msg;
+ }
+ this.trigger('on-status-clear');
+ return true;
+ }
+ })
+ })
+ };
+
+ return pgBrowser.Nodes['coll-extension'];
+});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql
new file mode 100644
index 0000000..a68b7ce
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql
@@ -0,0 +1,10 @@
+{#=========================Create new extension======================#}
+{% if data.name %}
+CREATE EXTENSION {{ data.name }}
+{% if data.schema %}
+ SCHEMA {{ data.schema }}
+{% endif %}
+{% if data.version %}
+ VERSION '{{ data.version }}';
+{% endif %}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql
new file mode 100644
index 0000000..12741fb
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql
@@ -0,0 +1,8 @@
+{#============================Drop/Cascade Extension by name=========================#}
+{% if eid %}
+SELECT x.extname from pg_extension x
+ WHERE x.oid = {{ eid }}::int
+{% endif %}
+{% if name %}
+DROP EXTENSION {{ name }} {% if cascade %} CASCADE {% endif %}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql
new file mode 100644
index 0000000..bf3979d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql
@@ -0,0 +1,12 @@
+{# ======================Fetch extensions names=====================#}
+SELECT
+ a.name, a.installed_version,
+ array_agg(av.version) as version,
+ array_agg(av.schema) as schema,
+ array_agg(av.superuser) as superuser,
+ array_agg(av.relocatable) as relocatable
+FROM
+ pg_available_extensions a
+ LEFT JOIN pg_available_extension_versions av ON (a.name = av.name)
+GROUP BY a.name, a.installed_version
+ORDER BY a.name
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql
new file mode 100644
index 0000000..b609883
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql
@@ -0,0 +1,17 @@
+{#===================Fetch properties of each extension by name or oid===================#}
+SELECT
+ x.oid AS eid, pg_get_userbyid(extowner) AS owner,
+ x.extname AS name, n.nspname AS schema,
+ x.extrelocatable AS relocatable, x.extversion AS version,
+ e.comment
+FROM
+ pg_extension x
+ LEFT JOIN pg_namespace n ON x.extnamespace=n.oid
+ JOIN pg_available_extensions() e(name, default_version, comment) ON x.extname=e.name
+{%- if eid %}
+ WHERE x.oid = {{eid}}::int
+{% elif ename %}
+ WHERE x.extname = '{{ename}}'::text
+{% else %}
+ ORDER BY x.extname
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql
new file mode 100644
index 0000000..b8e3c97
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql
@@ -0,0 +1,3 @@
+{#===================fetch all schemas==========================#}
+SELECT nspname As schema FROM pg_namespace
+ ORDER BY nspname
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql
new file mode 100644
index 0000000..cbdaa60
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql
@@ -0,0 +1,10 @@
+{# =============Update extension schema============= #}
+{% if data.schema != o_data.schema %}
+ALTER EXTENSION {{ o_data.name }}
+ SET SCHEMA {{ data.schema|e }};
+{% endif %}
+{# =============Update extension version============= #}
+{% if data.version and data.version != o_data.version %}
+ALTER EXTENSION {{ o_data.name }}
+ UPDATE TO '{{ data.version }}';
+{% endif %}
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin4] [Patch]: Extension Module
2016-01-12 07:14 [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-12 07:45 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
@ 2016-01-18 11:22 ` Neel Patel <[email protected]>
2016-01-18 12:14 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Neel Patel @ 2016-01-18 11:22 UTC (permalink / raw)
To: Surinder Kumar <[email protected]>; +Cc: pgadmin-hackers
Hi Surinder,
We have applied/tested the patch and below are the review comments.
1. When we select the extension "plpython3u", "plperl", "plperu" etc. then
it gives 'TypeError' in Javascript.
TypeError: d.version is undefined
'version': (!_.isNull(d.version[0]) ? d.version[0]: '')
We are getting this error while selecting many extensions so please test
with all types of extensions, it should not give any error at client side.
2. Use 2 space indentation instead of 4 space in javascript file.
3. In "validate" function in "extension.js" file, validate only the changed
values not all, and "this.get('name') - should be called only one time not
multiple
time".
4. When we pass object identifier, use the function 'qtIdent', and for the
values, use function 'qtLiteral' in all the sql files.
5. By default, when we create the extension, "schema_name" and "version"
should not be be set with value. It should be set blank by default.
6. When we create any extension like "citext" then we are not able to
create the same extension again after deleting the same extension. May be
issue
with caching mechanism.
7. When we remove the schema_name during the "Edit" operation then wrong
SQL is getting generated.
8. Remove "Use Slony" option. As discussed with Ashesh, we will implement
it as separate module.
Please fix the above issues. Let us know if you want more information.
Thanks,
Neel Patel
Thanks,
Neel Patel
On Tue, Jan 12, 2016 at 1:15 PM, Surinder Kumar <
[email protected]> wrote:
> Please find the updated patch with following changes:
>
> 1. corrected copyright.
> 2. Added proper comment for script_module function in __init__.py file.
> 3. Renamed collection Node's label to Extensions in extensions.js file.
>
>
> On Tue, Jan 12, 2016 at 12:44 PM, Surinder Kumar <
> [email protected]> wrote:
>
>> Hi,
>>
>> Please find attached patch for the extension module.
>> Please review it 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
>
>
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin4] [Patch]: Extension Module
2016-01-12 07:14 [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-12 07:45 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-18 11:22 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
@ 2016-01-18 12:14 ` Surinder Kumar <[email protected]>
2016-01-21 14:34 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Surinder Kumar @ 2016-01-18 12:14 UTC (permalink / raw)
To: Neel Patel <[email protected]>; +Cc: pgadmin-hackers
Thanks Neel for reviewing. I'll send the patch with the fixes suggested.
On Mon, Jan 18, 2016 at 4:52 PM, Neel Patel <[email protected]>
wrote:
> Hi Surinder,
>
> We have applied/tested the patch and below are the review comments.
>
> 1. When we select the extension "plpython3u", "plperl", "plperu" etc. then
> it gives 'TypeError' in Javascript.
> TypeError: d.version is undefined
> 'version': (!_.isNull(d.version[0]) ? d.version[0]: '')
>
> We are getting this error while selecting many extensions so please test
> with all types of extensions, it should not give any error at client side.
>
> 2. Use 2 space indentation instead of 4 space in javascript file.
>
> 3. In "validate" function in "extension.js" file, validate only the
> changed values not all, and "this.get('name') - should be called only one
> time not multiple
> time".
>
> 4. When we pass object identifier, use the function 'qtIdent', and for
> the values, use function 'qtLiteral' in all the sql files.
>
> 5. By default, when we create the extension, "schema_name" and "version"
> should not be be set with value. It should be set blank by default.
>
> 6. When we create any extension like "citext" then we are not able to
> create the same extension again after deleting the same extension. May be
> issue
> with caching mechanism.
>
> 7. When we remove the schema_name during the "Edit" operation then wrong
> SQL is getting generated.
>
> 8. Remove "Use Slony" option. As discussed with Ashesh, we will implement
> it as separate module.
>
>
> Please fix the above issues. Let us know if you want more information.
>
> Thanks,
> Neel Patel
>
>
> Thanks,
> Neel Patel
>
>
> On Tue, Jan 12, 2016 at 1:15 PM, Surinder Kumar <
> [email protected]> wrote:
>
>> Please find the updated patch with following changes:
>>
>> 1. corrected copyright.
>> 2. Added proper comment for script_module function in __init__.py
>> file.
>> 3. Renamed collection Node's label to Extensions in extensions.js
>> file.
>>
>>
>> On Tue, Jan 12, 2016 at 12:44 PM, Surinder Kumar <
>> [email protected]> wrote:
>>
>>> Hi,
>>>
>>> Please find attached patch for the extension module.
>>> Please review it 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
>>
>>
>
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin4] [Patch]: Extension Module
2016-01-12 07:14 [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-12 07:45 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-18 11:22 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
2016-01-18 12:14 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
@ 2016-01-21 14:34 ` Surinder Kumar <[email protected]>
2016-01-22 06:55 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Surinder Kumar @ 2016-01-21 14:34 UTC (permalink / raw)
To: Neel Patel <[email protected]>; pgadmin-hackers
Hi,
I've fixed the issues given in review comments.
Please find the attached updated patch for extension module, review it and
let me know for any comments.
On Mon, Jan 18, 2016 at 5:44 PM, Surinder Kumar <
[email protected]> wrote:
> Thanks Neel for reviewing. I'll send the patch with the fixes suggested.
>
> On Mon, Jan 18, 2016 at 4:52 PM, Neel Patel <[email protected]>
> wrote:
>
>> Hi Surinder,
>>
>> We have applied/tested the patch and below are the review comments.
>>
>> 1. When we select the extension "plpython3u", "plperl", "plperu" etc.
>> then it gives 'TypeError' in Javascript.
>> TypeError: d.version is undefined
>> 'version': (!_.isNull(d.version[0]) ? d.version[0]: '')
>>
>> We are getting this error while selecting many extensions so please test
>> with all types of extensions, it should not give any error at client side.
>>
> Fixed.
>
>> 2. Use 2 space indentation instead of 4 space in javascript file.
>>
> Done
>
>> 3. In "validate" function in "extension.js" file, validate only the
>> changed values not all, and "this.get('name') - should be called only one
>> time not multiple
>> time".
>>
> Fixed.
>
>> 4. When we pass object identifier, use the function 'qtIdent', and for
>> the values, use function 'qtLiteral' in all the sql files.
>>
> Done.
>
>> 5. By default, when we create the extension, "schema_name" and "version"
>> should not be be set with value. It should be set blank by default.
>>
> Kept blank while creating extension.
>
>> 6. When we create any extension like "citext" then we are not able to
>> create the same extension again after deleting the same extension. May be
>> issue
>> with caching mechanism.
>>
> It is an architecture change. we'll fix it later.
>
>> 7. When we remove the schema_name during the "Edit" operation then wrong
>> SQL is getting generated.
>>
> Fixed, Now it generates right SQL.
>
>>
> 8. Remove "Use Slony" option. As discussed with Ashesh, we will implement
>> it as separate module.
>>
> Removed.
>
>> Please fix the above issues. Let us know if you want more information.
>>
>> Thanks,
>> Neel Patel
>>
>>
>> Thanks,
>> Neel Patel
>>
>>
>> On Tue, Jan 12, 2016 at 1:15 PM, Surinder Kumar <
>> [email protected]> wrote:
>>
>>> Please find the updated patch with following changes:
>>>
>>> 1. corrected copyright.
>>> 2. Added proper comment for script_module function in __init__.py
>>> file.
>>> 3. Renamed collection Node's label to Extensions in extensions.js
>>> file.
>>>
>>>
>>> On Tue, Jan 12, 2016 at 12:44 PM, Surinder Kumar <
>>> [email protected]> wrote:
>>>
>>>> Hi,
>>>>
>>>> Please find attached patch for the extension module.
>>>> Please review it 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
>>>
>>>
>>
>
--
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] extension_v2.patch (28.7K, 3-extension_v2.patch)
download | inline diff:
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py
new file mode 100644
index 0000000..8ae1bd7
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py
@@ -0,0 +1,373 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+from flask import render_template, make_response, request, jsonify
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_json_response, \
+ make_response as ajax_response, internal_server_error
+from pgadmin.browser.utils import NodeView
+from pgadmin.browser.collection import CollectionNodeModule
+import pgadmin.browser.server_groups.servers.databases as databases
+from pgadmin.utils.ajax import precondition_required
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from functools import wraps
+
+
+class ExtensionModule(CollectionNodeModule):
+ NODE_TYPE = "extension"
+ COLLECTION_LABEL = gettext("Extensions")
+
+ def __init__(self, *args, **kwargs):
+ super(ExtensionModule, self).__init__(*args, **kwargs)
+
+ def get_nodes(self, gid, sid, did):
+ """
+ Generate the collection node
+ """
+ yield self.generate_browser_collection_node(did)
+
+ @property
+ def script_load(self):
+ """
+ Load the module script for extension, when any of the database node is
+ initialized.
+ """
+ return databases.DatabaseModule.NODE_TYPE
+
+
+blueprint = ExtensionModule(__name__)
+
+
+class ExtensionView(NodeView):
+ node_type = blueprint.node_type
+
+ parent_ids = [
+ {'type': 'int', 'id': 'gid'},
+ {'type': 'int', 'id': 'sid'},
+ {'type': 'int', 'id': 'did'}
+ ]
+ ids = [
+ {'type': 'int', 'id': 'eid'}
+ ]
+
+ operations = dict({
+ 'obj': [
+ {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+ {'get': 'list', 'post': 'create'}
+ ],
+ 'delete': [{'delete': 'delete'}],
+ 'nodes': [{'get': 'node'}, {'get': 'nodes'}],
+ 'sql': [{'get': 'sql'}],
+ 'msql': [{'get': 'msql'}, {'get': 'msql'}],
+ 'stats': [{'get': 'statistics'}],
+ 'dependency': [{'get': 'dependencies'}],
+ 'dependent': [{'get': 'dependents'}],
+ 'module.js': [{}, {}, {'get': 'module_js'}],
+ 'avails': [{}, {'get': 'avails'}],
+ 'schemas': [{}, {'get': 'schemas'}],
+ 'children': [{'get': 'children'}]
+ })
+
+ 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 self
+ """
+ @wraps(f)
+ def wrap(*args, **kwargs):
+ # Here args[0] will hold self & kwargs will hold gid,sid,did
+ self = args[0]
+ self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(kwargs['sid'])
+ self.conn = self.manager.connection(did=kwargs['did'])
+ self.template_path = 'extensions/sql'
+
+ return f(*args, **kwargs)
+ return wrap
+
+ @check_precondition
+ def list(self, gid, sid, did):
+ """
+ It fetches all extensions properties and render into properties
+ tab
+ """
+ SQL = render_template("/".join([self.template_path, 'properties.sql']))
+ status, res = self.conn.execute_dict(SQL)
+
+ if not status:
+ return internal_server_error(errormsg=res)
+ return ajax_response(
+ response=res['rows'],
+ status=200
+ )
+
+ @check_precondition
+ def nodes(self, gid, sid, did):
+ """
+ It lists down the all extensions under the Extensions Collection node
+ """
+ res = []
+ SQL = render_template("/".join([self.template_path, 'properties.sql']))
+ status, rset = self.conn.execute_2darray(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+
+ for row in rset['rows']:
+ res.append(
+ self.blueprint.generate_browser_node(
+ row['eid'],
+ row['name'],
+ 'icon-extension'
+ ))
+
+ return make_json_response(
+ data=res,
+ status=200
+ )
+
+ @check_precondition
+ def properties(self, gid, sid, did, eid):
+ """
+ It fetches the properties of a single extension
+ and render in properties tab
+
+ """
+ SQL = render_template("/".join([self.template_path, 'properties.sql']), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return ajax_response(
+ response=res['rows'][0],
+ status=200
+ )
+
+ @check_precondition
+ def create(self, gid, sid, did):
+ """
+ This function will creates new the extension object
+ """
+ required_args = [
+ 'name'
+ ]
+
+ data = request.form if request.form else json.loads(request.data.decode())
+ for arg in required_args:
+ if arg not in data:
+ return make_json_response(
+ status=410,
+ success=0,
+ errormsg=gettext(
+ "Couldn't find the required parameter (%s)." % arg
+ )
+ )
+
+ status, res = self.conn.execute_dict(
+ render_template(
+ "/".join([self.template_path, 'create.sql']),
+ data=data
+ )
+ )
+
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ status, rset = self.conn.execute_dict(
+ render_template(
+ "/".join([self.template_path, 'properties.sql']),
+ ename=data['name']
+ )
+ )
+
+ if not status:
+ return internal_server_error(errormsg=rset)
+
+ for row in rset['rows']:
+ return jsonify(
+ node=self.blueprint.generate_browser_node(
+ row['eid'],
+ row['name'],
+ 'icon-extension'
+ )
+ )
+
+ @check_precondition
+ def update(self, gid, sid, did, eid):
+ """
+ This function will update extension object
+ """
+ data = request.form if request.form else json.loads(request.data.decode())
+ SQL = self.getSQL(gid, sid, data, did, eid)
+
+ try:
+ if SQL and SQL.strip('\n') and SQL.strip(' '):
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return make_json_response(
+ success=1,
+ info="Extension updated",
+ data={
+ 'id': eid,
+ 'sid': sid,
+ 'gid': gid
+ }
+ )
+ else:
+ return make_json_response(
+ success=1,
+ info="Nothing to update",
+ data={
+ 'id': did,
+ 'sid': sid,
+ 'gid': gid
+ }
+ )
+
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def delete(self, gid, sid, did, eid):
+ """
+ This function will delete drop/drop cascade the extension object
+ """
+ cascade = True if self.cmd == 'delete' else False
+ try:
+ # check if extension with eid exists
+ SQL = render_template("/".join([self.template_path, 'delete.sql']), eid=eid)
+ status, name = self.conn.execute_scalar(SQL)
+ if not status:
+ return internal_server_error(errormsg=name)
+ # drop extension
+ SQL = render_template("/".join([self.template_path, 'delete.sql']), name=name, cascade=cascade)
+ status, res = self.conn.execute_scalar(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return make_json_response(
+ success=1,
+ info=gettext("Extension dropped"),
+ data={
+ 'id': did,
+ 'sid': sid,
+ 'gid': gid,
+ }
+ )
+
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def msql(self, gid, sid, did, eid=None):
+ """
+ This function to return modified SQL
+ """
+ data = request.args.copy()
+ SQL = self.getSQL(gid, sid, data, did, eid)
+ if SQL.strip('\n') and SQL.strip(' '):
+ return make_json_response(
+ data=SQL,
+ status=200
+ )
+ else:
+ return make_json_response(
+ data='-- Modified SQL --',
+ status=200
+ )
+
+ def getSQL(self, gid, sid, data, did, eid=None):
+ """
+ This function will generate sql from model data
+ """
+ required_args = [
+ 'name'
+ ]
+ try:
+ if eid is not None:
+ SQL = render_template("/".join([self.template_path, 'properties.sql']), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+ old_data = res['rows'][0]
+ for arg in required_args:
+ if arg not in data:
+ data[arg] = old_data[arg]
+ SQL = render_template("/".join([self.template_path, 'update.sql']), data=data, o_data=old_data)
+ else:
+ SQL = render_template("/".join([self.template_path, 'create.sql']), data=data)
+ return SQL
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def avails(self, gid, sid, did):
+ """
+ This function with fetch all the available extensions
+ """
+ SQL = render_template("/".join([self.template_path, 'extensions.sql']))
+ status, rset = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+ return make_json_response(
+ data=rset['rows'],
+ status=200
+ )
+
+ @check_precondition
+ def schemas(self, gid, sid, did):
+ """
+ This function with fetch all the schemas
+ """
+ SQL = render_template("/".join([self.template_path, 'schemas.sql']))
+ status, rset = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+ return make_json_response(
+ data=rset['rows'],
+ status=200
+ )
+
+ def module_js(self):
+ """
+ This property defines (if javascript) exists for this node.
+ Override this property for your own logic.
+ """
+ return make_response(
+ render_template(
+ "extensions/js/extensions.js",
+ _=gettext
+ ),
+ 200, {'Content-Type': 'application/x-javascript'}
+ )
+
+ @check_precondition
+ def sql(self, gid, sid, did, eid):
+ """
+ This function will generate sql for sql panel
+ """
+ SQL = render_template("/".join([self.template_path, 'properties.sql']), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ result = res['rows'][0]
+
+ SQL = render_template("/".join([self.template_path, 'create.sql']),
+ data=result,
+ conn=self.conn,
+ display_comments=True
+ )
+
+ return ajax_response(response=SQL)
+
+ExtensionView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/static/img/coll-extension.png b/web/pgadmin/browser/server_groups/servers/databases/extensions/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/browser/server_groups/servers/databases/extensions/static/img/extension.png b/web/pgadmin/browser/server_groups/servers/databases/extensions/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/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js
new file mode 100644
index 0000000..dc8a621
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js
@@ -0,0 +1,193 @@
+define(
+ ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'pgadmin.browser.collection'],
+function($, _, S, pgAdmin, pgBrowser) {
+
+ if (!pgBrowser.Nodes['coll-extension']) {
+ var extensions = pgAdmin.Browser.Nodes['coll-extension'] =
+ pgAdmin.Browser.Collection.extend({
+ node: 'extension',
+ label: '{{ _('Extension') }}',
+ type: 'coll-extension',
+ columns: ['name', 'owner', 'comment']
+ });
+ };
+
+ if (!pgBrowser.Nodes['extension']) {
+ pgAdmin.Browser.Nodes['extension'] =
+ pgAdmin.Browser.Node.extend({
+ parent_type: 'database',
+ type: 'extension',
+ hasSQL: true,
+ canDrop: true,
+ canDropCascade: true,
+ label: '{{ _('Extension') }}',
+
+ Init: function(){
+ if(this.initialized)
+ return;
+
+ this.initialized = true;
+
+ pgBrowser.add_menus([{
+ name: 'create_extension_on_coll', node: 'coll-extension', module: this,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ category: 'create', priority: 4, label: '{{ _('Extension...') }}',
+ icon: 'wcTabIcon pg-icon-extension', data: {action: 'create'}
+ },{
+ name: 'create_extension', node: 'extension', module: this,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ category: 'create', priority: 4, label: '{{ _('Extension...') }}',
+ icon: 'wcTabIcon pg-icon-extension', data: {action: 'create'}
+ }
+ ]);
+ },
+ model: pgAdmin.Browser.Node.Model.extend({
+ schema: [
+ {
+ id: 'name', label: '{{ _('Name')}}', first_empty: true,
+ type: 'text', mode: ['properties', 'create', 'edit'],
+ visible: true, url:'avails', disabled: function(m) {
+ return !m.isNew();
+ },
+ transform: function(data) {
+ var res = [];
+ var label = this.model.get('name');
+ if (!this.model.isNew()){
+ res.push({label: label, value: label});
+ } else {
+ if (data && _.isArray(data)) {
+ _.each(data, function(d) {
+ if (d.installed_version === null)
+ /* d contains json data and sets into
+ * select's option control
+ */
+ res.push({label: d.name, value: d});
+ })
+ }
+ }
+ return res;
+ },
+ /* extends NodeAjaxOptionsControl to override the properties
+ * getValueFromDOM which takes stringified data from option of
+ * select control and parse it. And `onChange` takes the stringified
+ * data from select's option, thus convert it to json format and set the
+ * data into Model which is used to enable/disable the schema field.
+ */
+ control: Backform.NodeAjaxOptionsControl.extend({
+ getValueFromDOM: function() {
+ var data = this.formatter.toRaw(this.$el.find("select").val(), this.model);
+ /*
+ * return null if data is empty to prevent it from
+ * throwing parsing error. Adds check as name can be empty
+ */
+ if (data === ''){
+ return null;
+ }
+ else if (typeof(data) === 'string') {
+ data=JSON.parse(data);
+ }
+ return data.name;
+ },
+ onChange: function() {
+ Backform.NodeAjaxOptionsControl.prototype.onChange.apply(this, arguments);
+ var selectedValue = this.$el.find("select").val();
+ if (selectedValue.trim() != ""){
+ var d = this.formatter.toRaw(selectedValue, this.model);
+ if(typeof(d) === 'string')
+ d=JSON.parse(d);
+ var changes = {
+ 'version' : '',
+ 'relocatable': ((!_.isNull(d.relocatable[0]) && !_.isUndefined(d.relocatable[0])) ? d.relocatable[0]: ''),
+ 'schema': ((!_.isNull(d.schema[0]) && !_.isUndefined(d.schema[0])) ? d.schema[0]: '')
+ };
+ this.model.set(changes);
+ }else{
+ var changes = {'version': '', 'relocatable': true, 'schema': ''};
+ this.model.set(changes);
+ }
+ },
+ })
+ },{
+ id: 'eid', label: '{{ _('Oid')}}', cell: 'string',
+ type: 'text', disabled: true, mode: ['properties', 'edit', 'create']
+ },{
+ id: 'owner', label:'{{ _('Owner') }}', control: 'node-list-by-name',
+ mode: ['properties'], node: 'role'
+ },{
+ id: 'schema', label: '{{ _('Schema')}}', type: 'text', control: 'node-ajax-options',
+ mode: ['properties', 'create', 'edit'], group: 'Definition', deps: ['relocatable'],
+ url: 'schemas', first_empty: true, disabled: function(m) {
+ /* enable or disable schema field if model's relocatable
+ * attribute is True or False
+ */
+ return (m.has('relocatable') ? !m.get('relocatable') : false);
+ },
+ transform: function(data) {
+ var res = [];
+ if (data && _.isArray(data)) {
+ _.each(data, function(d) {
+ res.push({label: d.schema, value: d.schema});
+ })
+ }
+ return res;
+ }
+ },
+ {
+ id: 'relocatable', label: '{{ _('Relocatable?')}}', cell: 'switch',
+ type: 'switch', mode: ['properties'], 'options': {
+ 'onText': 'Yes', 'offText': 'No', 'onColor': 'success',
+ 'offColor': 'default', 'size': 'small'
+ }
+ },
+ {
+ id: 'version', label: '{{ _('Version')}}', cell: 'string',
+ mode: ['properties', 'create', 'edit'], group: 'Definition',
+ control: 'node-ajax-options', url:'avails', first_empty: true,
+ /*
+ * Transform the data into version for the selected extension.
+ */
+ transform: function(data) {
+ res = [];
+ var extension = this.model.get('name');
+ _.each(data, function(dt){
+ if(dt.name == extension){
+ if(dt.version && _.isArray(dt.version)){
+ _.each(dt.version, function(v){
+ res.push({ label: v, value: v });
+ });
+ }
+ }
+ });
+ return res;
+ }
+ },
+ {
+ id: 'comment', label: '{{ _('Comment')}}', cell: 'string',
+ type: 'multiline', disabled: true
+ }
+ ],
+ validate: function() {
+ /*
+ * Triggers error messages for name
+ * if it is empty/undefined/null
+ */
+ var err = {},
+ errmsg,
+ name = this.get('name');
+ if (_.isUndefined(name) || _.isNull(name) ||
+ String(name).replace(/^\s+|\s+$/g, '') == '') {
+ err['name'] = '{{ _('Name can not be empty!') }}';
+ errmsg = errmsg || err['name'];
+ this.errorModel.set('name', errmsg);
+ return errmsg;
+ }else{
+ this.errorModel.unset('name');
+ }
+ return null;
+ }
+ })
+ })
+ };
+
+ return pgBrowser.Nodes['coll-extension'];
+});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql
new file mode 100644
index 0000000..f81a314
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql
@@ -0,0 +1,19 @@
+{#=========================Create new extension======================#}
+{#===Generates comments and code for SQL tab===#}
+{% if display_comments %}
+-- Extension: {{ conn|qtIdent(data.name) }}
+
+-- DROP EXTENSION {{ conn|qtIdent(data.name) }};
+
+{% endif %}
+{% if data.name %}
+ CREATE EXTENSION {{ conn|qtIdent(data.name) }}{% if data.schema == '' and data.version == '' %};{% endif %}
+{% if data.schema %}
+
+ SCHEMA {{ data.schema }}{% if data.version == '' %};{% endif %}
+{% endif %}
+{% if data.version %}
+
+ VERSION {{ data.version|qtLiteral }};
+{% endif %}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql
new file mode 100644
index 0000000..44155f6
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql
@@ -0,0 +1,8 @@
+{#============================Drop/Cascade Extension by name=========================#}
+{% if eid %}
+SELECT x.extname from pg_extension x
+ WHERE x.oid = {{ eid }}::int
+{% endif %}
+{% if name %}
+DROP EXTENSION {{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql
new file mode 100644
index 0000000..bf3979d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql
@@ -0,0 +1,12 @@
+{# ======================Fetch extensions names=====================#}
+SELECT
+ a.name, a.installed_version,
+ array_agg(av.version) as version,
+ array_agg(av.schema) as schema,
+ array_agg(av.superuser) as superuser,
+ array_agg(av.relocatable) as relocatable
+FROM
+ pg_available_extensions a
+ LEFT JOIN pg_available_extension_versions av ON (a.name = av.name)
+GROUP BY a.name, a.installed_version
+ORDER BY a.name
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql
new file mode 100644
index 0000000..f652676
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql
@@ -0,0 +1,17 @@
+{#===================Fetch properties of each extension by name or oid===================#}
+SELECT
+ x.oid AS eid, pg_get_userbyid(extowner) AS owner,
+ x.extname AS name, n.nspname AS schema,
+ x.extrelocatable AS relocatable, x.extversion AS version,
+ e.comment
+FROM
+ pg_extension x
+ LEFT JOIN pg_namespace n ON x.extnamespace=n.oid
+ JOIN pg_available_extensions() e(name, default_version, comment) ON x.extname=e.name
+{%- if eid %}
+ WHERE x.oid = {{eid}}::int
+{% elif ename %}
+ WHERE x.extname = {{ename|qtLiteral}}::text
+{% else %}
+ ORDER BY x.extname
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql
new file mode 100644
index 0000000..b8e3c97
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql
@@ -0,0 +1,3 @@
+{#===================fetch all schemas==========================#}
+SELECT nspname As schema FROM pg_namespace
+ ORDER BY nspname
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql
new file mode 100644
index 0000000..c4cd626
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql
@@ -0,0 +1,10 @@
+{# =============Update extension schema============= #}
+{% if data.schema and data.schema != o_data.schema %}
+ALTER EXTENSION {{ conn|qtIdent(o_data.name) }}
+ SET SCHEMA {{ conn|qtIdent(data.schema) }};
+{% endif %}
+{# =============Update extension version============= #}
+{% if data.version and data.version != o_data.version %}
+ALTER EXTENSION {{ conn|qtIdent(o_data.name) }}
+ UPDATE TO {{ conn|qtIdent(data.version) }};
+{% endif %}
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin4] [Patch]: Extension Module
2016-01-12 07:14 [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-12 07:45 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-18 11:22 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
2016-01-18 12:14 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-21 14:34 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
@ 2016-01-22 06:55 ` Surinder Kumar <[email protected]>
2016-02-04 04:46 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Surinder Kumar @ 2016-01-22 06:55 UTC (permalink / raw)
To: Neel Patel <[email protected]>; pgadmin-hackers
Hi
Please find the updated patch with following fixes:
1. Missing `owner` column under properties for `extensions collection`. Add
cell: 'string' property for owner fixed it
2. Schema object identifier should be wrapped with in function qtIdent .
Using function `qtIdent` for schema in create.sql fixed it.
Thanks
Surinder Kumar
On Thu, Jan 21, 2016 at 8:04 PM, Surinder Kumar <
[email protected]> wrote:
> Hi,
>
> I've fixed the issues given in review comments.
>
> Please find the attached updated patch for extension module, review it and
> let me know for any comments.
>
> On Mon, Jan 18, 2016 at 5:44 PM, Surinder Kumar <
> [email protected]> wrote:
>
>> Thanks Neel for reviewing. I'll send the patch with the fixes suggested.
>>
>> On Mon, Jan 18, 2016 at 4:52 PM, Neel Patel <[email protected]>
>> wrote:
>>
>>> Hi Surinder,
>>>
>>> We have applied/tested the patch and below are the review comments.
>>>
>>> 1. When we select the extension "plpython3u", "plperl", "plperu" etc.
>>> then it gives 'TypeError' in Javascript.
>>> TypeError: d.version is undefined
>>> 'version': (!_.isNull(d.version[0]) ? d.version[0]: '')
>>>
>>> We are getting this error while selecting many extensions so please test
>>> with all types of extensions, it should not give any error at client side.
>>>
>> Fixed.
>
>>
>>> 2. Use 2 space indentation instead of 4 space in javascript file.
>>>
>> Done
>
>>
>>> 3. In "validate" function in "extension.js" file, validate only the
>>> changed values not all, and "this.get('name') - should be called only one
>>> time not multiple
>>> time".
>>>
>> Fixed.
>
>>
>>> 4. When we pass object identifier, use the function 'qtIdent', and for
>>> the values, use function 'qtLiteral' in all the sql files.
>>>
>> Done.
>
>>
>>> 5. By default, when we create the extension, "schema_name" and "version"
>>> should not be be set with value. It should be set blank by default.
>>>
>> Kept blank while creating extension.
>
>>
>>> 6. When we create any extension like "citext" then we are not able to
>>> create the same extension again after deleting the same extension. May be
>>> issue
>>> with caching mechanism.
>>>
>> It is an architecture change. we'll fix it later.
>
>>
>>> 7. When we remove the schema_name during the "Edit" operation then wrong
>>> SQL is getting generated.
>>>
>> Fixed, Now it generates right SQL.
>
>>
>>>
>> 8. Remove "Use Slony" option. As discussed with Ashesh, we will implement
>>> it as separate module.
>>>
>> Removed.
>
>>
>>> Please fix the above issues. Let us know if you want more information.
>>>
>>> Thanks,
>>> Neel Patel
>>>
>>>
>>> Thanks,
>>> Neel Patel
>>>
>>>
>>> On Tue, Jan 12, 2016 at 1:15 PM, Surinder Kumar <
>>> [email protected]> wrote:
>>>
>>>> Please find the updated patch with following changes:
>>>>
>>>> 1. corrected copyright.
>>>> 2. Added proper comment for script_module function in __init__.py
>>>> file.
>>>> 3. Renamed collection Node's label to Extensions in extensions.js
>>>> file.
>>>>
>>>>
>>>> On Tue, Jan 12, 2016 at 12:44 PM, Surinder Kumar <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi,
>>>>>
>>>>> Please find attached patch for the extension module.
>>>>> Please review it 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
>>>>
>>>>
>>>
>>
>
--
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] extension_v3.patch (28.7K, 3-extension_v3.patch)
download | inline diff:
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py
new file mode 100644
index 0000000..8ae1bd7
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py
@@ -0,0 +1,373 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+from flask import render_template, make_response, request, jsonify
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_json_response, \
+ make_response as ajax_response, internal_server_error
+from pgadmin.browser.utils import NodeView
+from pgadmin.browser.collection import CollectionNodeModule
+import pgadmin.browser.server_groups.servers.databases as databases
+from pgadmin.utils.ajax import precondition_required
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from functools import wraps
+
+
+class ExtensionModule(CollectionNodeModule):
+ NODE_TYPE = "extension"
+ COLLECTION_LABEL = gettext("Extensions")
+
+ def __init__(self, *args, **kwargs):
+ super(ExtensionModule, self).__init__(*args, **kwargs)
+
+ def get_nodes(self, gid, sid, did):
+ """
+ Generate the collection node
+ """
+ yield self.generate_browser_collection_node(did)
+
+ @property
+ def script_load(self):
+ """
+ Load the module script for extension, when any of the database node is
+ initialized.
+ """
+ return databases.DatabaseModule.NODE_TYPE
+
+
+blueprint = ExtensionModule(__name__)
+
+
+class ExtensionView(NodeView):
+ node_type = blueprint.node_type
+
+ parent_ids = [
+ {'type': 'int', 'id': 'gid'},
+ {'type': 'int', 'id': 'sid'},
+ {'type': 'int', 'id': 'did'}
+ ]
+ ids = [
+ {'type': 'int', 'id': 'eid'}
+ ]
+
+ operations = dict({
+ 'obj': [
+ {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+ {'get': 'list', 'post': 'create'}
+ ],
+ 'delete': [{'delete': 'delete'}],
+ 'nodes': [{'get': 'node'}, {'get': 'nodes'}],
+ 'sql': [{'get': 'sql'}],
+ 'msql': [{'get': 'msql'}, {'get': 'msql'}],
+ 'stats': [{'get': 'statistics'}],
+ 'dependency': [{'get': 'dependencies'}],
+ 'dependent': [{'get': 'dependents'}],
+ 'module.js': [{}, {}, {'get': 'module_js'}],
+ 'avails': [{}, {'get': 'avails'}],
+ 'schemas': [{}, {'get': 'schemas'}],
+ 'children': [{'get': 'children'}]
+ })
+
+ 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 self
+ """
+ @wraps(f)
+ def wrap(*args, **kwargs):
+ # Here args[0] will hold self & kwargs will hold gid,sid,did
+ self = args[0]
+ self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(kwargs['sid'])
+ self.conn = self.manager.connection(did=kwargs['did'])
+ self.template_path = 'extensions/sql'
+
+ return f(*args, **kwargs)
+ return wrap
+
+ @check_precondition
+ def list(self, gid, sid, did):
+ """
+ It fetches all extensions properties and render into properties
+ tab
+ """
+ SQL = render_template("/".join([self.template_path, 'properties.sql']))
+ status, res = self.conn.execute_dict(SQL)
+
+ if not status:
+ return internal_server_error(errormsg=res)
+ return ajax_response(
+ response=res['rows'],
+ status=200
+ )
+
+ @check_precondition
+ def nodes(self, gid, sid, did):
+ """
+ It lists down the all extensions under the Extensions Collection node
+ """
+ res = []
+ SQL = render_template("/".join([self.template_path, 'properties.sql']))
+ status, rset = self.conn.execute_2darray(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+
+ for row in rset['rows']:
+ res.append(
+ self.blueprint.generate_browser_node(
+ row['eid'],
+ row['name'],
+ 'icon-extension'
+ ))
+
+ return make_json_response(
+ data=res,
+ status=200
+ )
+
+ @check_precondition
+ def properties(self, gid, sid, did, eid):
+ """
+ It fetches the properties of a single extension
+ and render in properties tab
+
+ """
+ SQL = render_template("/".join([self.template_path, 'properties.sql']), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return ajax_response(
+ response=res['rows'][0],
+ status=200
+ )
+
+ @check_precondition
+ def create(self, gid, sid, did):
+ """
+ This function will creates new the extension object
+ """
+ required_args = [
+ 'name'
+ ]
+
+ data = request.form if request.form else json.loads(request.data.decode())
+ for arg in required_args:
+ if arg not in data:
+ return make_json_response(
+ status=410,
+ success=0,
+ errormsg=gettext(
+ "Couldn't find the required parameter (%s)." % arg
+ )
+ )
+
+ status, res = self.conn.execute_dict(
+ render_template(
+ "/".join([self.template_path, 'create.sql']),
+ data=data
+ )
+ )
+
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ status, rset = self.conn.execute_dict(
+ render_template(
+ "/".join([self.template_path, 'properties.sql']),
+ ename=data['name']
+ )
+ )
+
+ if not status:
+ return internal_server_error(errormsg=rset)
+
+ for row in rset['rows']:
+ return jsonify(
+ node=self.blueprint.generate_browser_node(
+ row['eid'],
+ row['name'],
+ 'icon-extension'
+ )
+ )
+
+ @check_precondition
+ def update(self, gid, sid, did, eid):
+ """
+ This function will update extension object
+ """
+ data = request.form if request.form else json.loads(request.data.decode())
+ SQL = self.getSQL(gid, sid, data, did, eid)
+
+ try:
+ if SQL and SQL.strip('\n') and SQL.strip(' '):
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return make_json_response(
+ success=1,
+ info="Extension updated",
+ data={
+ 'id': eid,
+ 'sid': sid,
+ 'gid': gid
+ }
+ )
+ else:
+ return make_json_response(
+ success=1,
+ info="Nothing to update",
+ data={
+ 'id': did,
+ 'sid': sid,
+ 'gid': gid
+ }
+ )
+
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def delete(self, gid, sid, did, eid):
+ """
+ This function will delete drop/drop cascade the extension object
+ """
+ cascade = True if self.cmd == 'delete' else False
+ try:
+ # check if extension with eid exists
+ SQL = render_template("/".join([self.template_path, 'delete.sql']), eid=eid)
+ status, name = self.conn.execute_scalar(SQL)
+ if not status:
+ return internal_server_error(errormsg=name)
+ # drop extension
+ SQL = render_template("/".join([self.template_path, 'delete.sql']), name=name, cascade=cascade)
+ status, res = self.conn.execute_scalar(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return make_json_response(
+ success=1,
+ info=gettext("Extension dropped"),
+ data={
+ 'id': did,
+ 'sid': sid,
+ 'gid': gid,
+ }
+ )
+
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def msql(self, gid, sid, did, eid=None):
+ """
+ This function to return modified SQL
+ """
+ data = request.args.copy()
+ SQL = self.getSQL(gid, sid, data, did, eid)
+ if SQL.strip('\n') and SQL.strip(' '):
+ return make_json_response(
+ data=SQL,
+ status=200
+ )
+ else:
+ return make_json_response(
+ data='-- Modified SQL --',
+ status=200
+ )
+
+ def getSQL(self, gid, sid, data, did, eid=None):
+ """
+ This function will generate sql from model data
+ """
+ required_args = [
+ 'name'
+ ]
+ try:
+ if eid is not None:
+ SQL = render_template("/".join([self.template_path, 'properties.sql']), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+ old_data = res['rows'][0]
+ for arg in required_args:
+ if arg not in data:
+ data[arg] = old_data[arg]
+ SQL = render_template("/".join([self.template_path, 'update.sql']), data=data, o_data=old_data)
+ else:
+ SQL = render_template("/".join([self.template_path, 'create.sql']), data=data)
+ return SQL
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def avails(self, gid, sid, did):
+ """
+ This function with fetch all the available extensions
+ """
+ SQL = render_template("/".join([self.template_path, 'extensions.sql']))
+ status, rset = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+ return make_json_response(
+ data=rset['rows'],
+ status=200
+ )
+
+ @check_precondition
+ def schemas(self, gid, sid, did):
+ """
+ This function with fetch all the schemas
+ """
+ SQL = render_template("/".join([self.template_path, 'schemas.sql']))
+ status, rset = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+ return make_json_response(
+ data=rset['rows'],
+ status=200
+ )
+
+ def module_js(self):
+ """
+ This property defines (if javascript) exists for this node.
+ Override this property for your own logic.
+ """
+ return make_response(
+ render_template(
+ "extensions/js/extensions.js",
+ _=gettext
+ ),
+ 200, {'Content-Type': 'application/x-javascript'}
+ )
+
+ @check_precondition
+ def sql(self, gid, sid, did, eid):
+ """
+ This function will generate sql for sql panel
+ """
+ SQL = render_template("/".join([self.template_path, 'properties.sql']), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ result = res['rows'][0]
+
+ SQL = render_template("/".join([self.template_path, 'create.sql']),
+ data=result,
+ conn=self.conn,
+ display_comments=True
+ )
+
+ return ajax_response(response=SQL)
+
+ExtensionView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/static/img/coll-extension.png b/web/pgadmin/browser/server_groups/servers/databases/extensions/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/browser/server_groups/servers/databases/extensions/static/img/extension.png b/web/pgadmin/browser/server_groups/servers/databases/extensions/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/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js
new file mode 100644
index 0000000..4ccf06a
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js
@@ -0,0 +1,193 @@
+define(
+ ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'pgadmin.browser.collection'],
+function($, _, S, pgAdmin, pgBrowser) {
+
+ if (!pgBrowser.Nodes['coll-extension']) {
+ var extensions = pgAdmin.Browser.Nodes['coll-extension'] =
+ pgAdmin.Browser.Collection.extend({
+ node: 'extension',
+ label: '{{ _('Extension') }}',
+ type: 'coll-extension',
+ columns: ['name', 'owner', 'comment']
+ });
+ };
+
+ if (!pgBrowser.Nodes['extension']) {
+ pgAdmin.Browser.Nodes['extension'] =
+ pgAdmin.Browser.Node.extend({
+ parent_type: 'database',
+ type: 'extension',
+ hasSQL: true,
+ canDrop: true,
+ canDropCascade: true,
+ label: '{{ _('Extension') }}',
+
+ Init: function(){
+ if(this.initialized)
+ return;
+
+ this.initialized = true;
+
+ pgBrowser.add_menus([{
+ name: 'create_extension_on_coll', node: 'coll-extension', module: this,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ category: 'create', priority: 4, label: '{{ _('Extension...') }}',
+ icon: 'wcTabIcon pg-icon-extension', data: {action: 'create'}
+ },{
+ name: 'create_extension', node: 'extension', module: this,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ category: 'create', priority: 4, label: '{{ _('Extension...') }}',
+ icon: 'wcTabIcon pg-icon-extension', data: {action: 'create'}
+ }
+ ]);
+ },
+ model: pgAdmin.Browser.Node.Model.extend({
+ schema: [
+ {
+ id: 'name', label: '{{ _('Name')}}', first_empty: true,
+ type: 'text', mode: ['properties', 'create', 'edit'],
+ visible: true, url:'avails', disabled: function(m) {
+ return !m.isNew();
+ },
+ transform: function(data) {
+ var res = [];
+ var label = this.model.get('name');
+ if (!this.model.isNew()){
+ res.push({label: label, value: label});
+ } else {
+ if (data && _.isArray(data)) {
+ _.each(data, function(d) {
+ if (d.installed_version === null)
+ /* d contains json data and sets into
+ * select's option control
+ */
+ res.push({label: d.name, value: d});
+ })
+ }
+ }
+ return res;
+ },
+ /* extends NodeAjaxOptionsControl to override the properties
+ * getValueFromDOM which takes stringified data from option of
+ * select control and parse it. And `onChange` takes the stringified
+ * data from select's option, thus convert it to json format and set the
+ * data into Model which is used to enable/disable the schema field.
+ */
+ control: Backform.NodeAjaxOptionsControl.extend({
+ getValueFromDOM: function() {
+ var data = this.formatter.toRaw(this.$el.find("select").val(), this.model);
+ /*
+ * return null if data is empty to prevent it from
+ * throwing parsing error. Adds check as name can be empty
+ */
+ if (data === ''){
+ return null;
+ }
+ else if (typeof(data) === 'string') {
+ data=JSON.parse(data);
+ }
+ return data.name;
+ },
+ onChange: function() {
+ Backform.NodeAjaxOptionsControl.prototype.onChange.apply(this, arguments);
+ var selectedValue = this.$el.find("select").val();
+ if (selectedValue.trim() != ""){
+ var d = this.formatter.toRaw(selectedValue, this.model);
+ if(typeof(d) === 'string')
+ d=JSON.parse(d);
+ var changes = {
+ 'version' : '',
+ 'relocatable': ((!_.isNull(d.relocatable[0]) && !_.isUndefined(d.relocatable[0])) ? d.relocatable[0]: ''),
+ 'schema': ((!_.isNull(d.schema[0]) && !_.isUndefined(d.schema[0])) ? d.schema[0]: '')
+ };
+ this.model.set(changes);
+ }else{
+ var changes = {'version': '', 'relocatable': true, 'schema': ''};
+ this.model.set(changes);
+ }
+ },
+ })
+ },{
+ id: 'eid', label: '{{ _('Oid')}}', cell: 'string',
+ type: 'text', disabled: true, mode: ['properties', 'edit', 'create']
+ },{
+ id: 'owner', label:'{{ _('Owner') }}', control: 'node-list-by-name',
+ mode: ['properties'], node: 'role', cell: 'string'
+ },{
+ id: 'schema', label: '{{ _('Schema')}}', type: 'text', control: 'node-ajax-options',
+ mode: ['properties', 'create', 'edit'], group: 'Definition', deps: ['relocatable'],
+ url: 'schemas', first_empty: true, disabled: function(m) {
+ /* enable or disable schema field if model's relocatable
+ * attribute is True or False
+ */
+ return (m.has('relocatable') ? !m.get('relocatable') : false);
+ },
+ transform: function(data) {
+ var res = [];
+ if (data && _.isArray(data)) {
+ _.each(data, function(d) {
+ res.push({label: d.schema, value: d.schema});
+ })
+ }
+ return res;
+ }
+ },
+ {
+ id: 'relocatable', label: '{{ _('Relocatable?')}}', cell: 'switch',
+ type: 'switch', mode: ['properties'], 'options': {
+ 'onText': 'Yes', 'offText': 'No', 'onColor': 'success',
+ 'offColor': 'default', 'size': 'small'
+ }
+ },
+ {
+ id: 'version', label: '{{ _('Version')}}', cell: 'string',
+ mode: ['properties', 'create', 'edit'], group: 'Definition',
+ control: 'node-ajax-options', url:'avails', first_empty: true,
+ /*
+ * Transform the data into version for the selected extension.
+ */
+ transform: function(data) {
+ res = [];
+ var extension = this.model.get('name');
+ _.each(data, function(dt){
+ if(dt.name == extension){
+ if(dt.version && _.isArray(dt.version)){
+ _.each(dt.version, function(v){
+ res.push({ label: v, value: v });
+ });
+ }
+ }
+ });
+ return res;
+ }
+ },
+ {
+ id: 'comment', label: '{{ _('Comment')}}', cell: 'string',
+ type: 'multiline', disabled: true
+ }
+ ],
+ validate: function() {
+ /*
+ * Triggers error messages for name
+ * if it is empty/undefined/null
+ */
+ var err = {},
+ errmsg,
+ name = this.get('name');
+ if (_.isUndefined(name) || _.isNull(name) ||
+ String(name).replace(/^\s+|\s+$/g, '') == '') {
+ err['name'] = '{{ _('Name can not be empty!') }}';
+ errmsg = errmsg || err['name'];
+ this.errorModel.set('name', errmsg);
+ return errmsg;
+ }else{
+ this.errorModel.unset('name');
+ }
+ return null;
+ }
+ })
+ })
+ };
+
+ return pgBrowser.Nodes['coll-extension'];
+});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql
new file mode 100644
index 0000000..b2274db
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql
@@ -0,0 +1,19 @@
+{#=========================Create new extension======================#}
+{#===Generates comments and code for SQL tab===#}
+{% if display_comments %}
+-- Extension: {{ conn|qtIdent(data.name) }}
+
+-- DROP EXTENSION {{ conn|qtIdent(data.name) }};
+
+{% endif %}
+{% if data.name %}
+ CREATE EXTENSION {{ conn|qtIdent(data.name) }}{% if data.schema == '' and data.version == '' %};{% endif %}
+{% if data.schema %}
+
+ SCHEMA {{ conn|qtIdent(data.schema) }}{% if data.version == '' %};{% endif %}
+{% endif %}
+{% if data.version %}
+
+ VERSION {{ conn|qtIdent(data.version) }};
+{% endif %}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql
new file mode 100644
index 0000000..44155f6
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql
@@ -0,0 +1,8 @@
+{#============================Drop/Cascade Extension by name=========================#}
+{% if eid %}
+SELECT x.extname from pg_extension x
+ WHERE x.oid = {{ eid }}::int
+{% endif %}
+{% if name %}
+DROP EXTENSION {{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql
new file mode 100644
index 0000000..bf3979d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql
@@ -0,0 +1,12 @@
+{# ======================Fetch extensions names=====================#}
+SELECT
+ a.name, a.installed_version,
+ array_agg(av.version) as version,
+ array_agg(av.schema) as schema,
+ array_agg(av.superuser) as superuser,
+ array_agg(av.relocatable) as relocatable
+FROM
+ pg_available_extensions a
+ LEFT JOIN pg_available_extension_versions av ON (a.name = av.name)
+GROUP BY a.name, a.installed_version
+ORDER BY a.name
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql
new file mode 100644
index 0000000..f652676
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql
@@ -0,0 +1,17 @@
+{#===================Fetch properties of each extension by name or oid===================#}
+SELECT
+ x.oid AS eid, pg_get_userbyid(extowner) AS owner,
+ x.extname AS name, n.nspname AS schema,
+ x.extrelocatable AS relocatable, x.extversion AS version,
+ e.comment
+FROM
+ pg_extension x
+ LEFT JOIN pg_namespace n ON x.extnamespace=n.oid
+ JOIN pg_available_extensions() e(name, default_version, comment) ON x.extname=e.name
+{%- if eid %}
+ WHERE x.oid = {{eid}}::int
+{% elif ename %}
+ WHERE x.extname = {{ename|qtLiteral}}::text
+{% else %}
+ ORDER BY x.extname
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql
new file mode 100644
index 0000000..b8e3c97
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql
@@ -0,0 +1,3 @@
+{#===================fetch all schemas==========================#}
+SELECT nspname As schema FROM pg_namespace
+ ORDER BY nspname
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql
new file mode 100644
index 0000000..c4cd626
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql
@@ -0,0 +1,10 @@
+{# =============Update extension schema============= #}
+{% if data.schema and data.schema != o_data.schema %}
+ALTER EXTENSION {{ conn|qtIdent(o_data.name) }}
+ SET SCHEMA {{ conn|qtIdent(data.schema) }};
+{% endif %}
+{# =============Update extension version============= #}
+{% if data.version and data.version != o_data.version %}
+ALTER EXTENSION {{ conn|qtIdent(o_data.name) }}
+ UPDATE TO {{ conn|qtIdent(data.version) }};
+{% endif %}
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin4] [Patch]: Extension Module
2016-01-12 07:14 [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-12 07:45 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-18 11:22 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
2016-01-18 12:14 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-21 14:34 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-22 06:55 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
@ 2016-02-04 04:46 ` Neel Patel <[email protected]>
2016-02-04 06:10 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Neel Patel @ 2016-02-04 04:46 UTC (permalink / raw)
To: Surinder Kumar <[email protected]>; +Cc: pgadmin-hackers
Hi Surinder,
While applying the patch, we are getting below warnings.
extension_v3.patch:362: trailing whitespace.
This function will generate sql for sql panel
extension_v3.patch:646: trailing whitespace.
-- Extension: {{ conn|qtIdent(data.name) }}
warning: 2 lines add whitespace errors.
Can you please resend the patch file, after fixing above warnings ?
Thanks,
Neel Patel
On Fri, Jan 22, 2016 at 12:25 PM, Surinder Kumar <
[email protected]> wrote:
> Hi
>
> Please find the updated patch with following fixes:
> 1. Missing `owner` column under properties for `extensions collection`.
> Add cell: 'string' property for owner fixed it
> 2. Schema object identifier should be wrapped with in function qtIdent .
> Using function `qtIdent` for schema in create.sql fixed it.
>
>
> Thanks
> Surinder Kumar
>
> On Thu, Jan 21, 2016 at 8:04 PM, Surinder Kumar <
> [email protected]> wrote:
>
>> Hi,
>>
>> I've fixed the issues given in review comments.
>>
>> Please find the attached updated patch for extension module, review it
>> and let me know for any comments.
>>
>> On Mon, Jan 18, 2016 at 5:44 PM, Surinder Kumar <
>> [email protected]> wrote:
>>
>>> Thanks Neel for reviewing. I'll send the patch with the fixes suggested.
>>>
>>> On Mon, Jan 18, 2016 at 4:52 PM, Neel Patel <[email protected]
>>> > wrote:
>>>
>>>> Hi Surinder,
>>>>
>>>> We have applied/tested the patch and below are the review comments.
>>>>
>>>> 1. When we select the extension "plpython3u", "plperl", "plperu" etc.
>>>> then it gives 'TypeError' in Javascript.
>>>> TypeError: d.version is undefined
>>>> 'version': (!_.isNull(d.version[0]) ? d.version[0]: '')
>>>>
>>>> We are getting this error while selecting many extensions so please
>>>> test with all types of extensions, it should not give any error at client
>>>> side.
>>>>
>>> Fixed.
>>
>>>
>>>> 2. Use 2 space indentation instead of 4 space in javascript file.
>>>>
>>> Done
>>
>>>
>>>> 3. In "validate" function in "extension.js" file, validate only the
>>>> changed values not all, and "this.get('name') - should be called only one
>>>> time not multiple
>>>> time".
>>>>
>>> Fixed.
>>
>>>
>>>> 4. When we pass object identifier, use the function 'qtIdent', and for
>>>> the values, use function 'qtLiteral' in all the sql files.
>>>>
>>> Done.
>>
>>>
>>>> 5. By default, when we create the extension, "schema_name" and
>>>> "version" should not be be set with value. It should be set blank by
>>>> default.
>>>>
>>> Kept blank while creating extension.
>>
>>>
>>>> 6. When we create any extension like "citext" then we are not able to
>>>> create the same extension again after deleting the same extension. May be
>>>> issue
>>>> with caching mechanism.
>>>>
>>> It is an architecture change. we'll fix it later.
>>
>>>
>>>> 7. When we remove the schema_name during the "Edit" operation then
>>>> wrong SQL is getting generated.
>>>>
>>> Fixed, Now it generates right SQL.
>>
>>>
>>>>
>>> 8. Remove "Use Slony" option. As discussed with Ashesh, we will
>>>> implement it as separate module.
>>>>
>>> Removed.
>>
>>>
>>>> Please fix the above issues. Let us know if you want more information.
>>>>
>>>> Thanks,
>>>> Neel Patel
>>>>
>>>>
>>>> Thanks,
>>>> Neel Patel
>>>>
>>>>
>>>> On Tue, Jan 12, 2016 at 1:15 PM, Surinder Kumar <
>>>> [email protected]> wrote:
>>>>
>>>>> Please find the updated patch with following changes:
>>>>>
>>>>> 1. corrected copyright.
>>>>> 2. Added proper comment for script_module function in __init__.py
>>>>> file.
>>>>> 3. Renamed collection Node's label to Extensions in extensions.js
>>>>> file.
>>>>>
>>>>>
>>>>> On Tue, Jan 12, 2016 at 12:44 PM, Surinder Kumar <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi,
>>>>>>
>>>>>> Please find attached patch for the extension module.
>>>>>> Please review it 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
>>>>>
>>>>>
>>>>
>>>
>>
>
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin4] [Patch]: Extension Module
2016-01-12 07:14 [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-12 07:45 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-18 11:22 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
2016-01-18 12:14 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-21 14:34 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-22 06:55 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-02-04 04:46 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
@ 2016-02-04 06:10 ` Surinder Kumar <[email protected]>
2016-02-04 06:29 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Surinder Kumar @ 2016-02-04 06:10 UTC (permalink / raw)
To: Neel Patel <[email protected]>; +Cc: pgadmin-hackers
Hi Neel,
Please find the patch with following changes:
1. Removed Whitespace from files.
2. Fixed an issue in which json object converted into [object object]
string.
3. Fixed python3 issue of unicode type where code breaks at
"isinstance(SQL, str)" in python3 if str is unicode,
because python no longer support unicode type of string.
Please review the patch and Let me know for any comments.
Thanks,
Surinder Kumar
On Thu, Feb 4, 2016 at 10:16 AM, Neel Patel <[email protected]>
wrote:
> Hi Surinder,
>
> While applying the patch, we are getting below warnings.
>
> extension_v3.patch:362: trailing whitespace.
> This function will generate sql for sql panel
> extension_v3.patch:646: trailing whitespace.
> -- Extension: {{ conn|qtIdent(data.name) }}
> warning: 2 lines add whitespace errors.
>
>
> Can you please resend the patch file, after fixing above warnings ?
>
> Thanks,
> Neel Patel
>
> On Fri, Jan 22, 2016 at 12:25 PM, Surinder Kumar <
> [email protected]> wrote:
>
>> Hi
>>
>> Please find the updated patch with following fixes:
>> 1. Missing `owner` column under properties for `extensions collection`.
>> Add cell: 'string' property for owner fixed it
>> 2. Schema object identifier should be wrapped with in function qtIdent .
>> Using function `qtIdent` for schema in create.sql fixed it.
>>
>>
>> Thanks
>> Surinder Kumar
>>
>> On Thu, Jan 21, 2016 at 8:04 PM, Surinder Kumar <
>> [email protected]> wrote:
>>
>>> Hi,
>>>
>>> I've fixed the issues given in review comments.
>>>
>>> Please find the attached updated patch for extension module, review it
>>> and let me know for any comments.
>>>
>>> On Mon, Jan 18, 2016 at 5:44 PM, Surinder Kumar <
>>> [email protected]> wrote:
>>>
>>>> Thanks Neel for reviewing. I'll send the patch with the fixes suggested.
>>>>
>>>> On Mon, Jan 18, 2016 at 4:52 PM, Neel Patel <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Surinder,
>>>>>
>>>>> We have applied/tested the patch and below are the review comments.
>>>>>
>>>>> 1. When we select the extension "plpython3u", "plperl", "plperu" etc.
>>>>> then it gives 'TypeError' in Javascript.
>>>>> TypeError: d.version is undefined
>>>>> 'version': (!_.isNull(d.version[0]) ? d.version[0]: '')
>>>>>
>>>>> We are getting this error while selecting many extensions so please
>>>>> test with all types of extensions, it should not give any error at client
>>>>> side.
>>>>>
>>>> Fixed.
>>>
>>>>
>>>>> 2. Use 2 space indentation instead of 4 space in javascript file.
>>>>>
>>>> Done
>>>
>>>>
>>>>> 3. In "validate" function in "extension.js" file, validate only the
>>>>> changed values not all, and "this.get('name') - should be called only one
>>>>> time not multiple
>>>>> time".
>>>>>
>>>> Fixed.
>>>
>>>>
>>>>> 4. When we pass object identifier, use the function 'qtIdent', and
>>>>> for the values, use function 'qtLiteral' in all the sql files.
>>>>>
>>>> Done.
>>>
>>>>
>>>>> 5. By default, when we create the extension, "schema_name" and
>>>>> "version" should not be be set with value. It should be set blank by
>>>>> default.
>>>>>
>>>> Kept blank while creating extension.
>>>
>>>>
>>>>> 6. When we create any extension like "citext" then we are not able to
>>>>> create the same extension again after deleting the same extension. May be
>>>>> issue
>>>>> with caching mechanism.
>>>>>
>>>> It is an architecture change. we'll fix it later.
>>>
>>>>
>>>>> 7. When we remove the schema_name during the "Edit" operation then
>>>>> wrong SQL is getting generated.
>>>>>
>>>> Fixed, Now it generates right SQL.
>>>
>>>>
>>>>>
>>>> 8. Remove "Use Slony" option. As discussed with Ashesh, we will
>>>>> implement it as separate module.
>>>>>
>>>> Removed.
>>>
>>>>
>>>>> Please fix the above issues. Let us know if you want more information.
>>>>>
>>>>> Thanks,
>>>>> Neel Patel
>>>>>
>>>>>
>>>>> Thanks,
>>>>> Neel Patel
>>>>>
>>>>>
>>>>> On Tue, Jan 12, 2016 at 1:15 PM, Surinder Kumar <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Please find the updated patch with following changes:
>>>>>>
>>>>>> 1. corrected copyright.
>>>>>> 2. Added proper comment for script_module function in __init__.py
>>>>>> file.
>>>>>> 3. Renamed collection Node's label to Extensions in extensions.js
>>>>>> file.
>>>>>>
>>>>>>
>>>>>> On Tue, Jan 12, 2016 at 12:44 PM, Surinder Kumar <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi,
>>>>>>>
>>>>>>> Please find attached patch for the extension module.
>>>>>>> Please review it 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
>>>>>>
>>>>>>
>>>>>
>>>>
>>>
>>
>
--
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] extension_v4.patch (29.6K, 3-extension_v4.patch)
download | inline diff:
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py
new file mode 100644
index 0000000..37ad229
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py
@@ -0,0 +1,394 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+from flask import render_template, make_response, request, jsonify
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_json_response, \
+ make_response as ajax_response, internal_server_error
+from pgadmin.browser.utils import NodeView
+from pgadmin.browser.collection import CollectionNodeModule
+import pgadmin.browser.server_groups.servers.databases as databases
+from pgadmin.utils.ajax import precondition_required
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from functools import wraps
+
+# 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
+
+
+class ExtensionModule(CollectionNodeModule):
+ NODE_TYPE = "extension"
+ COLLECTION_LABEL = gettext("Extensions")
+
+ def __init__(self, *args, **kwargs):
+ super(ExtensionModule, self).__init__(*args, **kwargs)
+
+ def get_nodes(self, gid, sid, did):
+ """
+ Generate the collection node
+ """
+ yield self.generate_browser_collection_node(did)
+
+ @property
+ def script_load(self):
+ """
+ Load the module script for extension, when any of the database node is
+ initialized.
+ """
+ return databases.DatabaseModule.NODE_TYPE
+
+
+blueprint = ExtensionModule(__name__)
+
+
+class ExtensionView(NodeView):
+ node_type = blueprint.node_type
+
+ parent_ids = [
+ {'type': 'int', 'id': 'gid'},
+ {'type': 'int', 'id': 'sid'},
+ {'type': 'int', 'id': 'did'}
+ ]
+ ids = [
+ {'type': 'int', 'id': 'eid'}
+ ]
+
+ operations = dict({
+ 'obj': [
+ {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+ {'get': 'list', 'post': 'create'}
+ ],
+ 'delete': [{'delete': 'delete'}],
+ 'nodes': [{'get': 'node'}, {'get': 'nodes'}],
+ 'sql': [{'get': 'sql'}],
+ 'msql': [{'get': 'msql'}, {'get': 'msql'}],
+ 'stats': [{'get': 'statistics'}],
+ 'dependency': [{'get': 'dependencies'}],
+ 'dependent': [{'get': 'dependents'}],
+ 'module.js': [{}, {}, {'get': 'module_js'}],
+ 'avails': [{}, {'get': 'avails'}],
+ 'schemas': [{}, {'get': 'schemas'}],
+ 'children': [{'get': 'children'}]
+ })
+
+ 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 self
+ """
+ @wraps(f)
+ def wrap(*args, **kwargs):
+ # Here args[0] will hold self & kwargs will hold gid,sid,did
+ self = args[0]
+ self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(kwargs['sid'])
+ self.conn = self.manager.connection(did=kwargs['did'])
+ self.template_path = 'extensions/sql'
+
+ return f(*args, **kwargs)
+ return wrap
+
+ @check_precondition
+ def list(self, gid, sid, did):
+ """
+ It fetches all extensions properties and render into properties
+ tab
+ """
+ SQL = render_template("/".join([self.template_path, 'properties.sql']))
+ status, res = self.conn.execute_dict(SQL)
+
+ if not status:
+ return internal_server_error(errormsg=res)
+ return ajax_response(
+ response=res['rows'],
+ status=200
+ )
+
+ @check_precondition
+ def nodes(self, gid, sid, did):
+ """
+ It lists down the all extensions under the Extensions Collection node
+ """
+ res = []
+ SQL = render_template("/".join([self.template_path, 'properties.sql']))
+ status, rset = self.conn.execute_2darray(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+
+ for row in rset['rows']:
+ res.append(
+ self.blueprint.generate_browser_node(
+ row['eid'],
+ row['name'],
+ 'icon-extension'
+ ))
+
+ return make_json_response(
+ data=res,
+ status=200
+ )
+
+ @check_precondition
+ def properties(self, gid, sid, did, eid):
+ """
+ It fetches the properties of a single extension
+ and render in properties tab
+
+ """
+ SQL = render_template("/".join([self.template_path, 'properties.sql']), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return ajax_response(
+ response=res['rows'][0],
+ status=200
+ )
+
+ @check_precondition
+ def create(self, gid, sid, did):
+ """
+ This function will creates new the extension object
+ """
+ required_args = [
+ 'name'
+ ]
+
+ data = request.form if request.form else json.loads(request.data.decode())
+ for arg in required_args:
+ if arg not in data:
+ return make_json_response(
+ status=410,
+ success=0,
+ errormsg=gettext(
+ "Couldn't find the required parameter (%s)." % arg
+ )
+ )
+
+ status, res = self.conn.execute_dict(
+ render_template(
+ "/".join([self.template_path, 'create.sql']),
+ data=data
+ )
+ )
+
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ status, rset = self.conn.execute_dict(
+ render_template(
+ "/".join([self.template_path, 'properties.sql']),
+ ename=data['name']
+ )
+ )
+
+ if not status:
+ return internal_server_error(errormsg=rset)
+
+ for row in rset['rows']:
+ return jsonify(
+ node=self.blueprint.generate_browser_node(
+ row['eid'],
+ row['name'],
+ 'icon-extension'
+ )
+ )
+
+ @check_precondition
+ def update(self, gid, sid, did, eid):
+ """
+ This function will update extension object
+ """
+ data = request.form if request.form else json.loads(request.data.decode())
+ SQL = self.getSQL(gid, sid, data, did, eid)
+
+ try:
+ if SQL and isinstance(SQL, basestring) and SQL.strip('\n') and SQL.strip(' '):
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return make_json_response(
+ success=1,
+ info="Extension updated",
+ data={
+ 'id': eid,
+ 'sid': sid,
+ 'gid': gid
+ }
+ )
+ else:
+ return make_json_response(
+ success=1,
+ info="Nothing to update",
+ data={
+ 'id': did,
+ 'sid': sid,
+ 'gid': gid
+ }
+ )
+
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def delete(self, gid, sid, did, eid):
+ """
+ This function will delete drop/drop cascade the extension object
+ """
+ cascade = True if self.cmd == 'delete' else False
+ try:
+ # check if extension with eid exists
+ SQL = render_template("/".join([self.template_path, 'delete.sql']), eid=eid)
+ status, name = self.conn.execute_scalar(SQL)
+ if not status:
+ return internal_server_error(errormsg=name)
+ # drop extension
+ SQL = render_template("/".join([self.template_path, 'delete.sql']), name=name, cascade=cascade)
+ status, res = self.conn.execute_scalar(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return make_json_response(
+ success=1,
+ info=gettext("Extension dropped"),
+ data={
+ 'id': did,
+ 'sid': sid,
+ 'gid': gid,
+ }
+ )
+
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def msql(self, gid, sid, did, eid=None):
+ """
+ This function to return modified SQL
+ """
+ data = request.args.copy()
+ SQL = self.getSQL(gid, sid, data, did, eid)
+ if SQL and isinstance(SQL, basestring) and SQL.strip('\n') and SQL.strip(' '):
+ return make_json_response(
+ data=SQL,
+ status=200
+ )
+ else:
+ return make_json_response(
+ data='-- Modified SQL --',
+ status=200
+ )
+
+ def getSQL(self, gid, sid, data, did, eid=None):
+ """
+ This function will generate sql from model data
+ """
+ required_args = [
+ 'name'
+ ]
+ try:
+ if eid is not None:
+ SQL = render_template("/".join([self.template_path, 'properties.sql']), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+ old_data = res['rows'][0]
+ for arg in required_args:
+ if arg not in data:
+ data[arg] = old_data[arg]
+ SQL = render_template("/".join([self.template_path, 'update.sql']), data=data, o_data=old_data)
+ else:
+ SQL = render_template("/".join([self.template_path, 'create.sql']), data=data)
+ return SQL
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def avails(self, gid, sid, did):
+ """
+ This function with fetch all the available extensions
+ """
+ SQL = render_template("/".join([self.template_path, 'extensions.sql']))
+ status, rset = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+ return make_json_response(
+ data=rset['rows'],
+ status=200
+ )
+
+ @check_precondition
+ def schemas(self, gid, sid, did):
+ """
+ This function with fetch all the schemas
+ """
+ SQL = render_template("/".join([self.template_path, 'schemas.sql']))
+ status, rset = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+ return make_json_response(
+ data=rset['rows'],
+ status=200
+ )
+
+ def module_js(self):
+ """
+ This property defines (if javascript) exists for this node.
+ Override this property for your own logic.
+ """
+ return make_response(
+ render_template(
+ "extensions/js/extensions.js",
+ _=gettext
+ ),
+ 200, {'Content-Type': 'application/x-javascript'}
+ )
+
+ @check_precondition
+ def sql(self, gid, sid, did, eid):
+ """
+ This function will generate sql for sql panel
+ """
+ SQL = render_template("/".join([self.template_path, 'properties.sql']), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ result = res['rows'][0]
+
+ SQL = render_template("/".join([self.template_path, 'create.sql']),
+ data=result,
+ conn=self.conn,
+ display_comments=True
+ )
+
+ return ajax_response(response=SQL)
+
+ExtensionView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/static/img/coll-extension.png b/web/pgadmin/browser/server_groups/servers/databases/extensions/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/browser/server_groups/servers/databases/extensions/static/img/extension.png b/web/pgadmin/browser/server_groups/servers/databases/extensions/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/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js
new file mode 100644
index 0000000..11385b0
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js
@@ -0,0 +1,196 @@
+define(
+ ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'pgadmin.browser.collection'],
+function($, _, S, pgAdmin, pgBrowser) {
+
+ if (!pgBrowser.Nodes['coll-extension']) {
+ var extensions = pgAdmin.Browser.Nodes['coll-extension'] =
+ pgAdmin.Browser.Collection.extend({
+ node: 'extension',
+ label: '{{ _('Extension') }}',
+ type: 'coll-extension',
+ columns: ['name', 'owner', 'comment']
+ });
+ };
+
+ if (!pgBrowser.Nodes['extension']) {
+ pgAdmin.Browser.Nodes['extension'] =
+ pgAdmin.Browser.Node.extend({
+ parent_type: 'database',
+ type: 'extension',
+ hasSQL: true,
+ canDrop: true,
+ canDropCascade: true,
+ label: '{{ _('Extension') }}',
+
+ Init: function(){
+ if(this.initialized)
+ return;
+
+ this.initialized = true;
+
+ pgBrowser.add_menus([{
+ name: 'create_extension_on_coll', node: 'coll-extension', module: this,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ category: 'create', priority: 4, label: '{{ _('Extension...') }}',
+ icon: 'wcTabIcon pg-icon-extension', data: {action: 'create'}
+ },{
+ name: 'create_extension', node: 'extension', module: this,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ category: 'create', priority: 4, label: '{{ _('Extension...') }}',
+ icon: 'wcTabIcon pg-icon-extension', data: {action: 'create'}
+ }
+ ]);
+ },
+ model: pgAdmin.Browser.Node.Model.extend({
+ schema: [
+ {
+ id: 'name', label: '{{ _('Name')}}', first_empty: true,
+ type: 'text', mode: ['properties', 'create', 'edit'],
+ visible: true, url:'avails', disabled: function(m) {
+ return !m.isNew();
+ },
+ transform: function(data) {
+ var res = [];
+ var label = this.model.get('name');
+ if (!this.model.isNew()){
+ res.push({label: label, value: label});
+ } else {
+ if (data && _.isArray(data)) {
+ _.each(data, function(d) {
+ if (d.installed_version === null)
+ /* d contains json data and sets into
+ * select's option control
+ *
+ * We need to stringify data because formatter will
+ * convert Array Object as [Object] string
+ */
+ res.push({label: d.name, value: JSON.stringify(d)});
+ })
+ }
+ }
+ return res;
+ },
+ /* extends NodeAjaxOptionsControl to override the properties
+ * getValueFromDOM which takes stringified data from option of
+ * select control and parse it. And `onChange` takes the stringified
+ * data from select's option, thus convert it to json format and set the
+ * data into Model which is used to enable/disable the schema field.
+ */
+ control: Backform.NodeAjaxOptionsControl.extend({
+ getValueFromDOM: function() {
+ var data = this.formatter.toRaw(_.unescape(this.$el.find("select").val()), this.model);
+ /*
+ * return null if data is empty to prevent it from
+ * throwing parsing error. Adds check as name can be empty
+ */
+ if (data === ''){
+ return null;
+ }
+ else if (typeof(data) === 'string') {
+ data=JSON.parse(data);
+ }
+ return data.name;
+ },
+ onChange: function() {
+ Backform.NodeAjaxOptionsControl.prototype.onChange.apply(this, arguments);
+ var selectedValue = this.$el.find("select").val();
+ if (selectedValue.trim() != ""){
+ var d = this.formatter.toRaw(selectedValue, this.model);
+ if(typeof(d) === 'string')
+ d=JSON.parse(d);
+ var changes = {
+ 'version' : '',
+ 'relocatable': ((!_.isNull(d.relocatable[0]) && !_.isUndefined(d.relocatable[0])) ? d.relocatable[0]: ''),
+ 'schema': ((!_.isNull(d.schema[0]) && !_.isUndefined(d.schema[0])) ? d.schema[0]: '')
+ };
+ this.model.set(changes);
+ }else{
+ var changes = {'version': '', 'relocatable': true, 'schema': ''};
+ this.model.set(changes);
+ }
+ },
+ })
+ },{
+ id: 'eid', label: '{{ _('Oid')}}', cell: 'string',
+ type: 'text', disabled: true, mode: ['properties', 'edit', 'create']
+ },{
+ id: 'owner', label:'{{ _('Owner') }}', control: 'node-list-by-name',
+ mode: ['properties'], node: 'role', cell: 'string'
+ },{
+ id: 'schema', label: '{{ _('Schema')}}', type: 'text', control: 'node-ajax-options',
+ mode: ['properties', 'create', 'edit'], group: 'Definition', deps: ['relocatable'],
+ url: 'schemas', first_empty: true, disabled: function(m) {
+ /* enable or disable schema field if model's relocatable
+ * attribute is True or False
+ */
+ return (m.has('relocatable') ? !m.get('relocatable') : false);
+ },
+ transform: function(data) {
+ var res = [];
+ if (data && _.isArray(data)) {
+ _.each(data, function(d) {
+ res.push({label: d.schema, value: d.schema});
+ })
+ }
+ return res;
+ }
+ },
+ {
+ id: 'relocatable', label: '{{ _('Relocatable?')}}', cell: 'switch',
+ type: 'switch', mode: ['properties'], 'options': {
+ 'onText': 'Yes', 'offText': 'No', 'onColor': 'success',
+ 'offColor': 'default', 'size': 'small'
+ }
+ },
+ {
+ id: 'version', label: '{{ _('Version')}}', cell: 'string',
+ mode: ['properties', 'create', 'edit'], group: 'Definition',
+ control: 'node-ajax-options', url:'avails', first_empty: true,
+ /*
+ * Transform the data into version for the selected extension.
+ */
+ transform: function(data) {
+ res = [];
+ var extension = this.model.get('name');
+ _.each(data, function(dt){
+ if(dt.name == extension){
+ if(dt.version && _.isArray(dt.version)){
+ _.each(dt.version, function(v){
+ res.push({ label: v, value: v });
+ });
+ }
+ }
+ });
+ return res;
+ }
+ },
+ {
+ id: 'comment', label: '{{ _('Comment')}}', cell: 'string',
+ type: 'multiline', disabled: true
+ }
+ ],
+ validate: function() {
+ /*
+ * Triggers error messages for name
+ * if it is empty/undefined/null
+ */
+ var err = {},
+ errmsg,
+ name = this.get('name');
+ if (_.isUndefined(name) || _.isNull(name) ||
+ String(name).replace(/^\s+|\s+$/g, '') == '') {
+ err['name'] = '{{ _('Name can not be empty!') }}';
+ errmsg = errmsg || err['name'];
+ this.errorModel.set('name', errmsg);
+ return errmsg;
+ }else{
+ this.errorModel.unset('name');
+ }
+ return null;
+ }
+ })
+ })
+ };
+
+ return pgBrowser.Nodes['coll-extension'];
+});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql
new file mode 100644
index 0000000..288a7cc
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql
@@ -0,0 +1,19 @@
+{#=========================Create new extension======================#}
+{#===Generates comments and code for SQL tab===#}
+{% if display_comments %}
+-- Extension: {{ conn|qtIdent(data.name) }}
+
+-- DROP EXTENSION {{ conn|qtIdent(data.name) }};
+
+{% endif %}
+{% if data.name %}
+ CREATE EXTENSION {{ conn|qtIdent(data.name) }}{% if data.schema == '' and data.version == '' %};{% endif %}
+{% if data.schema %}
+
+ SCHEMA {{ conn|qtIdent(data.schema) }}{% if data.version == '' %};{% endif %}
+{% endif %}
+{% if data.version %}
+
+ VERSION {{ conn|qtIdent(data.version) }};
+{% endif %}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql
new file mode 100644
index 0000000..44155f6
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql
@@ -0,0 +1,8 @@
+{#============================Drop/Cascade Extension by name=========================#}
+{% if eid %}
+SELECT x.extname from pg_extension x
+ WHERE x.oid = {{ eid }}::int
+{% endif %}
+{% if name %}
+DROP EXTENSION {{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql
new file mode 100644
index 0000000..bf3979d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql
@@ -0,0 +1,12 @@
+{# ======================Fetch extensions names=====================#}
+SELECT
+ a.name, a.installed_version,
+ array_agg(av.version) as version,
+ array_agg(av.schema) as schema,
+ array_agg(av.superuser) as superuser,
+ array_agg(av.relocatable) as relocatable
+FROM
+ pg_available_extensions a
+ LEFT JOIN pg_available_extension_versions av ON (a.name = av.name)
+GROUP BY a.name, a.installed_version
+ORDER BY a.name
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql
new file mode 100644
index 0000000..f652676
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql
@@ -0,0 +1,17 @@
+{#===================Fetch properties of each extension by name or oid===================#}
+SELECT
+ x.oid AS eid, pg_get_userbyid(extowner) AS owner,
+ x.extname AS name, n.nspname AS schema,
+ x.extrelocatable AS relocatable, x.extversion AS version,
+ e.comment
+FROM
+ pg_extension x
+ LEFT JOIN pg_namespace n ON x.extnamespace=n.oid
+ JOIN pg_available_extensions() e(name, default_version, comment) ON x.extname=e.name
+{%- if eid %}
+ WHERE x.oid = {{eid}}::int
+{% elif ename %}
+ WHERE x.extname = {{ename|qtLiteral}}::text
+{% else %}
+ ORDER BY x.extname
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql
new file mode 100644
index 0000000..b8e3c97
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql
@@ -0,0 +1,3 @@
+{#===================fetch all schemas==========================#}
+SELECT nspname As schema FROM pg_namespace
+ ORDER BY nspname
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql
new file mode 100644
index 0000000..c4cd626
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql
@@ -0,0 +1,10 @@
+{# =============Update extension schema============= #}
+{% if data.schema and data.schema != o_data.schema %}
+ALTER EXTENSION {{ conn|qtIdent(o_data.name) }}
+ SET SCHEMA {{ conn|qtIdent(data.schema) }};
+{% endif %}
+{# =============Update extension version============= #}
+{% if data.version and data.version != o_data.version %}
+ALTER EXTENSION {{ conn|qtIdent(o_data.name) }}
+ UPDATE TO {{ conn|qtIdent(data.version) }};
+{% endif %}
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin4] [Patch]: Extension Module
2016-01-12 07:14 [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-12 07:45 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-18 11:22 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
2016-01-18 12:14 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-21 14:34 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-22 06:55 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-02-04 04:46 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
2016-02-04 06:10 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
@ 2016-02-04 06:29 ` Neel Patel <[email protected]>
2016-02-15 09:55 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Neel Patel @ 2016-02-04 06:29 UTC (permalink / raw)
To: Surinder Kumar <[email protected]>; +Cc: pgadmin-hackers
Hi,
New patch is working fine.
Ashesh - Can you please review it? If it looks good then we can commit the
extension module.
Thanks,
Neel Patel
On Thu, Feb 4, 2016 at 11:40 AM, Surinder Kumar <
[email protected]> wrote:
> Hi Neel,
>
> Please find the patch with following changes:
> 1. Removed Whitespace from files.
> 2. Fixed an issue in which json object converted into [object object]
> string.
> 3. Fixed python3 issue of unicode type where code breaks at
> "isinstance(SQL, str)" in python3 if str is unicode,
> because python no longer support unicode type of string.
>
> Please review the patch and Let me know for any comments.
>
> Thanks,
> Surinder Kumar
>
> On Thu, Feb 4, 2016 at 10:16 AM, Neel Patel <[email protected]>
> wrote:
>
>> Hi Surinder,
>>
>> While applying the patch, we are getting below warnings.
>>
>> extension_v3.patch:362: trailing whitespace.
>> This function will generate sql for sql panel
>> extension_v3.patch:646: trailing whitespace.
>> -- Extension: {{ conn|qtIdent(data.name) }}
>> warning: 2 lines add whitespace errors.
>>
>>
>> Can you please resend the patch file, after fixing above warnings ?
>>
>> Thanks,
>> Neel Patel
>>
>> On Fri, Jan 22, 2016 at 12:25 PM, Surinder Kumar <
>> [email protected]> wrote:
>>
>>> Hi
>>>
>>> Please find the updated patch with following fixes:
>>> 1. Missing `owner` column under properties for `extensions collection`.
>>> Add cell: 'string' property for owner fixed it
>>> 2. Schema object identifier should be wrapped with in function qtIdent .
>>> Using function `qtIdent` for schema in create.sql fixed it.
>>>
>>>
>>> Thanks
>>> Surinder Kumar
>>>
>>> On Thu, Jan 21, 2016 at 8:04 PM, Surinder Kumar <
>>> [email protected]> wrote:
>>>
>>>> Hi,
>>>>
>>>> I've fixed the issues given in review comments.
>>>>
>>>> Please find the attached updated patch for extension module, review it
>>>> and let me know for any comments.
>>>>
>>>> On Mon, Jan 18, 2016 at 5:44 PM, Surinder Kumar <
>>>> [email protected]> wrote:
>>>>
>>>>> Thanks Neel for reviewing. I'll send the patch with the fixes
>>>>> suggested.
>>>>>
>>>>> On Mon, Jan 18, 2016 at 4:52 PM, Neel Patel <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Surinder,
>>>>>>
>>>>>> We have applied/tested the patch and below are the review comments.
>>>>>>
>>>>>> 1. When we select the extension "plpython3u", "plperl", "plperu" etc.
>>>>>> then it gives 'TypeError' in Javascript.
>>>>>> TypeError: d.version is undefined
>>>>>> 'version': (!_.isNull(d.version[0]) ? d.version[0]: '')
>>>>>>
>>>>>> We are getting this error while selecting many extensions so please
>>>>>> test with all types of extensions, it should not give any error at client
>>>>>> side.
>>>>>>
>>>>> Fixed.
>>>>
>>>>>
>>>>>> 2. Use 2 space indentation instead of 4 space in javascript file.
>>>>>>
>>>>> Done
>>>>
>>>>>
>>>>>> 3. In "validate" function in "extension.js" file, validate only the
>>>>>> changed values not all, and "this.get('name') - should be called only one
>>>>>> time not multiple
>>>>>> time".
>>>>>>
>>>>> Fixed.
>>>>
>>>>>
>>>>>> 4. When we pass object identifier, use the function 'qtIdent', and
>>>>>> for the values, use function 'qtLiteral' in all the sql files.
>>>>>>
>>>>> Done.
>>>>
>>>>>
>>>>>> 5. By default, when we create the extension, "schema_name" and
>>>>>> "version" should not be be set with value. It should be set blank by
>>>>>> default.
>>>>>>
>>>>> Kept blank while creating extension.
>>>>
>>>>>
>>>>>> 6. When we create any extension like "citext" then we are not able to
>>>>>> create the same extension again after deleting the same extension. May be
>>>>>> issue
>>>>>> with caching mechanism.
>>>>>>
>>>>> It is an architecture change. we'll fix it later.
>>>>
>>>>>
>>>>>> 7. When we remove the schema_name during the "Edit" operation then
>>>>>> wrong SQL is getting generated.
>>>>>>
>>>>> Fixed, Now it generates right SQL.
>>>>
>>>>>
>>>>>>
>>>>> 8. Remove "Use Slony" option. As discussed with Ashesh, we will
>>>>>> implement it as separate module.
>>>>>>
>>>>> Removed.
>>>>
>>>>>
>>>>>> Please fix the above issues. Let us know if you want more information.
>>>>>>
>>>>>> Thanks,
>>>>>> Neel Patel
>>>>>>
>>>>>>
>>>>>> Thanks,
>>>>>> Neel Patel
>>>>>>
>>>>>>
>>>>>> On Tue, Jan 12, 2016 at 1:15 PM, Surinder Kumar <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Please find the updated patch with following changes:
>>>>>>>
>>>>>>> 1. corrected copyright.
>>>>>>> 2. Added proper comment for script_module function in
>>>>>>> __init__.py file.
>>>>>>> 3. Renamed collection Node's label to Extensions in
>>>>>>> extensions.js file.
>>>>>>>
>>>>>>>
>>>>>>> On Tue, Jan 12, 2016 at 12:44 PM, Surinder Kumar <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Hi,
>>>>>>>>
>>>>>>>> Please find attached patch for the extension module.
>>>>>>>> Please review it 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
>>>>>>>
>>>>>>>
>>>>>>
>>>>>
>>>>
>>>
>>
>
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin4] [Patch]: Extension Module
2016-01-12 07:14 [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-12 07:45 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-18 11:22 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
2016-01-18 12:14 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-21 14:34 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-22 06:55 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-02-04 04:46 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
2016-02-04 06:10 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-02-04 06:29 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
@ 2016-02-15 09:55 ` Surinder Kumar <[email protected]>
2016-02-15 11:07 ` Re: [pgAdmin4] [Patch]: Extension Module Dave Page <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Surinder Kumar @ 2016-02-15 09:55 UTC (permalink / raw)
To: Neel Patel <[email protected]>; +Cc: pgadmin-hackers
Hi,
PFA patch with following changes:
1. Added "Create Extension" menu item in context menu of Database node.
2. Added a new method "node_node" in ExtensionModule class. If a node
has child, returns True, otherwise False.
3. Fixed an issue in which icon won't display in create extension link
in context menu.
4. Added Docstring for the class and methods in python file and proper
commenting in js file.
5. Followed PEP-08 coding conventions.
Please review the patch and let me know for any comments.
On Thu, Feb 4, 2016 at 11:59 AM, Neel Patel <[email protected]>
wrote:
> Hi,
>
> New patch is working fine.
> Ashesh - Can you please review it? If it looks good then we can commit the
> extension module.
>
> Thanks,
> Neel Patel
>
>
> On Thu, Feb 4, 2016 at 11:40 AM, Surinder Kumar <
> [email protected]> wrote:
>
>> Hi Neel,
>>
>> Please find the patch with following changes:
>> 1. Removed Whitespace from files.
>> 2. Fixed an issue in which json object converted into [object object]
>> string.
>> 3. Fixed python3 issue of unicode type where code breaks at
>> "isinstance(SQL, str)" in python3 if str is unicode,
>> because python no longer support unicode type of string.
>>
>> Please review the patch and Let me know for any comments.
>>
>> Thanks,
>> Surinder Kumar
>>
>> On Thu, Feb 4, 2016 at 10:16 AM, Neel Patel <[email protected]>
>> wrote:
>>
>>> Hi Surinder,
>>>
>>> While applying the patch, we are getting below warnings.
>>>
>>> extension_v3.patch:362: trailing whitespace.
>>> This function will generate sql for sql panel
>>> extension_v3.patch:646: trailing whitespace.
>>> -- Extension: {{ conn|qtIdent(data.name) }}
>>> warning: 2 lines add whitespace errors.
>>>
>>>
>>> Can you please resend the patch file, after fixing above warnings ?
>>>
>>> Thanks,
>>> Neel Patel
>>>
>>> On Fri, Jan 22, 2016 at 12:25 PM, Surinder Kumar <
>>> [email protected]> wrote:
>>>
>>>> Hi
>>>>
>>>> Please find the updated patch with following fixes:
>>>> 1. Missing `owner` column under properties for `extensions collection`.
>>>> Add cell: 'string' property for owner fixed it
>>>> 2. Schema object identifier should be wrapped with in function qtIdent
>>>> . Using function `qtIdent` for schema in create.sql fixed it.
>>>>
>>>>
>>>> Thanks
>>>> Surinder Kumar
>>>>
>>>> On Thu, Jan 21, 2016 at 8:04 PM, Surinder Kumar <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi,
>>>>>
>>>>> I've fixed the issues given in review comments.
>>>>>
>>>>> Please find the attached updated patch for extension module, review it
>>>>> and let me know for any comments.
>>>>>
>>>>> On Mon, Jan 18, 2016 at 5:44 PM, Surinder Kumar <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Thanks Neel for reviewing. I'll send the patch with the fixes
>>>>>> suggested.
>>>>>>
>>>>>> On Mon, Jan 18, 2016 at 4:52 PM, Neel Patel <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi Surinder,
>>>>>>>
>>>>>>> We have applied/tested the patch and below are the review comments.
>>>>>>>
>>>>>>> 1. When we select the extension "plpython3u", "plperl", "plperu"
>>>>>>> etc. then it gives 'TypeError' in Javascript.
>>>>>>> TypeError: d.version is undefined
>>>>>>> 'version': (!_.isNull(d.version[0]) ? d.version[0]: '')
>>>>>>>
>>>>>>> We are getting this error while selecting many extensions so please
>>>>>>> test with all types of extensions, it should not give any error at client
>>>>>>> side.
>>>>>>>
>>>>>> Fixed.
>>>>>
>>>>>>
>>>>>>> 2. Use 2 space indentation instead of 4 space in javascript file.
>>>>>>>
>>>>>> Done
>>>>>
>>>>>>
>>>>>>> 3. In "validate" function in "extension.js" file, validate only the
>>>>>>> changed values not all, and "this.get('name') - should be called only one
>>>>>>> time not multiple
>>>>>>> time".
>>>>>>>
>>>>>> Fixed.
>>>>>
>>>>>>
>>>>>>> 4. When we pass object identifier, use the function 'qtIdent', and
>>>>>>> for the values, use function 'qtLiteral' in all the sql files.
>>>>>>>
>>>>>> Done.
>>>>>
>>>>>>
>>>>>>> 5. By default, when we create the extension, "schema_name" and
>>>>>>> "version" should not be be set with value. It should be set blank by
>>>>>>> default.
>>>>>>>
>>>>>> Kept blank while creating extension.
>>>>>
>>>>>>
>>>>>>> 6. When we create any extension like "citext" then we are not able
>>>>>>> to create the same extension again after deleting the same extension. May
>>>>>>> be issue
>>>>>>> with caching mechanism.
>>>>>>>
>>>>>> It is an architecture change. we'll fix it later.
>>>>>
>>>>>>
>>>>>>> 7. When we remove the schema_name during the "Edit" operation then
>>>>>>> wrong SQL is getting generated.
>>>>>>>
>>>>>> Fixed, Now it generates right SQL.
>>>>>
>>>>>>
>>>>>>>
>>>>>> 8. Remove "Use Slony" option. As discussed with Ashesh, we will
>>>>>>> implement it as separate module.
>>>>>>>
>>>>>> Removed.
>>>>>
>>>>>>
>>>>>>> Please fix the above issues. Let us know if you want more
>>>>>>> information.
>>>>>>>
>>>>>>> Thanks,
>>>>>>> Neel Patel
>>>>>>>
>>>>>>>
>>>>>>> Thanks,
>>>>>>> Neel Patel
>>>>>>>
>>>>>>>
>>>>>>> On Tue, Jan 12, 2016 at 1:15 PM, Surinder Kumar <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Please find the updated patch with following changes:
>>>>>>>>
>>>>>>>> 1. corrected copyright.
>>>>>>>> 2. Added proper comment for script_module function in
>>>>>>>> __init__.py file.
>>>>>>>> 3. Renamed collection Node's label to Extensions in
>>>>>>>> extensions.js file.
>>>>>>>>
>>>>>>>>
>>>>>>>> On Tue, Jan 12, 2016 at 12:44 PM, Surinder Kumar <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Hi,
>>>>>>>>>
>>>>>>>>> Please find attached patch for the extension module.
>>>>>>>>> Please review it 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
>>>>>>>>
>>>>>>>>
>>>>>>>
>>>>>>
>>>>>
>>>>
>>>
>>
>
--
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] extension_v5.patch (32.3K, 3-extension_v5.patch)
download | inline diff:
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py
new file mode 100644
index 0000000..2429eb9
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py
@@ -0,0 +1,446 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+from flask import render_template, make_response, request, jsonify
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_json_response, \
+ make_response as ajax_response, internal_server_error
+from pgadmin.browser.utils import NodeView
+from pgadmin.browser.collection import CollectionNodeModule
+import pgadmin.browser.server_groups.servers.databases as databases
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from functools import wraps
+
+# 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
+
+
+class ExtensionModule(CollectionNodeModule):
+ """
+ class ExtensionModule(Object):
+
+ A collection Node which inherits CollectionNodeModule
+ class and define methods to get child nodes, to load its own
+ javascript file.
+ """
+ NODE_TYPE = "extension"
+ COLLECTION_LABEL = gettext("Extensions")
+
+ def __init__(self, *args, **kwargs):
+ """
+ Initialising the base class
+ """
+ super(ExtensionModule, self).__init__(*args, **kwargs)
+
+ def get_nodes(self, gid, sid, did):
+ """
+ Generate the collection node
+ """
+ yield self.generate_browser_collection_node(did)
+
+ @property
+ def node_inode(self):
+ """
+ If a node have child return True otherwise False
+ """
+ return False
+
+ @property
+ def script_load(self):
+ """
+ Load the module script for extension, when any of the database node is
+ initialized.
+ """
+ return databases.DatabaseModule.NODE_TYPE
+
+
+# Create blueprint of extension module
+blueprint = ExtensionModule(__name__)
+
+
+class ExtensionView(NodeView):
+ """
+ It is a class for extension node which inherits the
+ properties and methods from NodeView class and define
+ various methods to list, create, update and delete extension.
+
+ Variables:
+ ---------
+ * node_type - tells which type of node it is
+ * parent_ids - id with its type and name of parent nodes
+ * ids - id with type and name of extension module being used.
+ * operations - function routes mappings defined.
+ """
+ node_type = blueprint.node_type
+
+ parent_ids = [
+ {'type': 'int', 'id': 'gid'},
+ {'type': 'int', 'id': 'sid'},
+ {'type': 'int', 'id': 'did'}
+ ]
+ ids = [
+ {'type': 'int', 'id': 'eid'}
+ ]
+
+ operations = dict({
+ 'obj': [
+ {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+ {'get': 'list', 'post': 'create'}
+ ],
+ 'delete': [{'delete': 'delete'}],
+ 'nodes': [{'get': 'node'}, {'get': 'nodes'}],
+ 'sql': [{'get': 'sql'}],
+ 'msql': [{'get': 'msql'}, {'get': 'msql'}],
+ 'stats': [{'get': 'statistics'}],
+ 'dependency': [{'get': 'dependencies'}],
+ 'dependent': [{'get': 'dependents'}],
+ 'module.js': [{}, {}, {'get': 'module_js'}],
+ 'avails': [{}, {'get': 'avails'}],
+ 'schemas': [{}, {'get': 'schemas'}],
+ 'children': [{'get': 'children'}]
+ })
+
+ 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 self
+ """
+ @wraps(f)
+ def wrap(*args, **kwargs):
+ # Here args[0] will hold self & kwargs will hold gid,sid,did
+ self = args[0]
+ self.manager = get_driver(
+ PG_DEFAULT_DRIVER
+ ).connection_manager(kwargs['sid'])
+ self.conn = self.manager.connection(did=kwargs['did'])
+ self.template_path = 'extensions/sql'
+
+ return f(*args, **kwargs)
+ return wrap
+
+ @check_precondition
+ def list(self, gid, sid, did):
+ """
+ It fetches all extensions properties and render into properties
+ tab
+ """
+ SQL = render_template("/".join([self.template_path, 'properties.sql']))
+ status, res = self.conn.execute_dict(SQL)
+
+ if not status:
+ return internal_server_error(errormsg=res)
+ return ajax_response(
+ response=res['rows'],
+ status=200
+ )
+
+ @check_precondition
+ def nodes(self, gid, sid, did):
+ """
+ It lists down the all extensions under the Extensions Collection node
+ """
+ res = []
+ SQL = render_template("/".join([self.template_path, 'properties.sql']))
+ status, rset = self.conn.execute_2darray(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+
+ for row in rset['rows']:
+ res.append(
+ self.blueprint.generate_browser_node(
+ row['eid'],
+ did,
+ row['name'],
+ 'icon-extension'
+ ))
+
+ return make_json_response(
+ data=res,
+ status=200
+ )
+
+ @check_precondition
+ def properties(self, gid, sid, did, eid):
+ """
+ It fetches the properties of a single extension
+ and render in properties tab
+
+ """
+ SQL = render_template("/".join(
+ [self.template_path, 'properties.sql']), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return ajax_response(
+ response=res['rows'][0],
+ status=200
+ )
+
+ @check_precondition
+ def create(self, gid, sid, did):
+ """
+ This function will creates new the extension object
+ """
+ required_args = [
+ 'name'
+ ]
+
+ data = request.form if request.form else \
+ json.loads(request.data.decode())
+ for arg in required_args:
+ if arg not in data:
+ return make_json_response(
+ status=410,
+ success=0,
+ errormsg=gettext(
+ "Couldn't find the required parameter (%s)." % arg
+ )
+ )
+
+ status, res = self.conn.execute_dict(
+ render_template(
+ "/".join([self.template_path, 'create.sql']),
+ data=data
+ )
+ )
+
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ status, rset = self.conn.execute_dict(
+ render_template(
+ "/".join([self.template_path, 'properties.sql']),
+ ename=data['name']
+ )
+ )
+
+ if not status:
+ return internal_server_error(errormsg=rset)
+
+ for row in rset['rows']:
+ return jsonify(
+ node=self.blueprint.generate_browser_node(
+ row['eid'],
+ did,
+ row['name'],
+ 'icon-extension'
+ )
+ )
+
+ @check_precondition
+ def update(self, gid, sid, did, eid):
+ """
+ This function will update extension object
+ """
+ data = request.form if request.form else \
+ json.loads(request.data.decode())
+ SQL = self.getSQL(gid, sid, data, did, eid)
+
+ try:
+ if SQL and isinstance(SQL, basestring) and \
+ SQL.strip('\n') and SQL.strip(' '):
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return make_json_response(
+ success=1,
+ info="Extension updated",
+ data={
+ 'id': eid,
+ 'sid': sid,
+ 'gid': gid
+ }
+ )
+ else:
+ return make_json_response(
+ success=1,
+ info="Nothing to update",
+ data={
+ 'id': did,
+ 'sid': sid,
+ 'gid': gid
+ }
+ )
+
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def delete(self, gid, sid, did, eid):
+ """
+ This function will delete drop/drop cascade the extension object
+ """
+ cascade = True if self.cmd == 'delete' else False
+ try:
+ # check if extension with eid exists
+ SQL = render_template("/".join(
+ [self.template_path, 'delete.sql']), eid=eid)
+ status, name = self.conn.execute_scalar(SQL)
+ if not status:
+ return internal_server_error(errormsg=name)
+ # drop extension
+ SQL = render_template("/".join(
+ [self.template_path, 'delete.sql']
+ ), name=name, cascade=cascade)
+ status, res = self.conn.execute_scalar(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return make_json_response(
+ success=1,
+ info=gettext("Extension dropped"),
+ data={
+ 'id': did,
+ 'sid': sid,
+ 'gid': gid,
+ }
+ )
+
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def msql(self, gid, sid, did, eid=None):
+ """
+ This function to return modified SQL
+ """
+ data = request.args.copy()
+ SQL = self.getSQL(gid, sid, data, did, eid)
+ if SQL and isinstance(SQL, basestring) and SQL.strip('\n') \
+ and SQL.strip(' '):
+ return make_json_response(
+ data=SQL,
+ status=200
+ )
+ else:
+ return make_json_response(
+ data='-- Modified SQL --',
+ status=200
+ )
+
+ def getSQL(self, gid, sid, data, did, eid=None):
+ """
+ This function will generate sql from model data
+ """
+ required_args = [
+ 'name'
+ ]
+ try:
+ if eid is not None:
+ SQL = render_template("/".join(
+ [self.template_path, 'properties.sql']
+ ), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+ old_data = res['rows'][0]
+ for arg in required_args:
+ if arg not in data:
+ data[arg] = old_data[arg]
+ SQL = render_template("/".join(
+ [self.template_path, 'update.sql']
+ ), data=data, o_data=old_data)
+ else:
+ SQL = render_template("/".join(
+ [self.template_path, 'create.sql']
+ ), data=data)
+ return SQL
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def avails(self, gid, sid, did):
+ """
+ This function with fetch all the available extensions
+ """
+ SQL = render_template("/".join([self.template_path, 'extensions.sql']))
+ status, rset = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+ return make_json_response(
+ data=rset['rows'],
+ status=200
+ )
+
+ @check_precondition
+ def schemas(self, gid, sid, did):
+ """
+ This function with fetch all the schemas
+ """
+ SQL = render_template("/".join([self.template_path, 'schemas.sql']))
+ status, rset = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+ return make_json_response(
+ data=rset['rows'],
+ status=200
+ )
+
+ def module_js(self):
+ """
+ This property defines (if javascript) exists for this node.
+ Override this property for your own logic.
+ """
+ return make_response(
+ render_template(
+ "extensions/js/extensions.js",
+ _=gettext
+ ),
+ 200, {'Content-Type': 'application/x-javascript'}
+ )
+
+ @check_precondition
+ def sql(self, gid, sid, did, eid):
+ """
+ This function will generate sql for sql panel
+ """
+ SQL = render_template("/".join(
+ [self.template_path, 'properties.sql']
+ ), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ result = res['rows'][0]
+
+ SQL = render_template("/".join(
+ [self.template_path, 'create.sql']
+ ),
+ data=result,
+ conn=self.conn,
+ display_comments=True
+ )
+
+ return ajax_response(response=SQL)
+
+# Register and add ExtensionView as blueprint
+ExtensionView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/static/img/coll-extension.png b/web/pgadmin/browser/server_groups/servers/databases/extensions/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/browser/server_groups/servers/databases/extensions/static/img/extension.png b/web/pgadmin/browser/server_groups/servers/databases/extensions/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/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js
new file mode 100644
index 0000000..b24314e
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js
@@ -0,0 +1,228 @@
+define(
+ ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'pgadmin.browser.collection'],
+function($, _, S, pgAdmin, pgBrowser) {
+
+ /*
+ * Create and Add an Extension Collection into nodes
+ * Params:
+ * label - Label for Node
+ * type - Type of Node
+ * columns - List of columns to show under under properties.
+ */
+ if (!pgBrowser.Nodes['coll-extension']) {
+ var extensions = pgAdmin.Browser.Nodes['coll-extension'] =
+ pgAdmin.Browser.Collection.extend({
+ node: 'extension',
+ label: '{{ _('Extension') }}',
+ type: 'coll-extension',
+ columns: ['name', 'owner', 'comment']
+ });
+ };
+
+ /*
+ * Create and Add an Extension Node into nodes
+ * Params:
+ * parent_type - Name of parent Node
+ * type - Type of Node
+ * hasSQL - True if we need to show SQL query Tab control, otherwise False
+ * canDrop - True to show "Drop Extension" link under Context menu,
+ * otherwise False
+ * canDropCascade - True to show "Drop Cascade" link under Context menu,
+ * otherwise False
+ * columns - List of columns to show under under properties tab.
+ * label - Label for Node
+ */
+ if (!pgBrowser.Nodes['extension']) {
+ pgAdmin.Browser.Nodes['extension'] =
+ pgAdmin.Browser.Node.extend({
+ parent_type: 'database',
+ type: 'extension',
+ hasSQL: true,
+ canDrop: true,
+ canDropCascade: true,
+ label: '{{ _('Extension') }}',
+
+ Init: function(){
+ if(this.initialized)
+ return;
+
+ this.initialized = true;
+ // Add "create extension" menu item into context and object menu
+ // for the following nodes:
+ // coll-extension, extension and database.
+ pgBrowser.add_menus([{
+ name: 'create_extension_on_coll', node: 'coll-extension', module: this,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ category: 'create', priority: 4, label: '{{ _('Extension...') }}',
+ icon: 'wcTabIcon icon-extension', data: {action: 'create'}
+ },{
+ name: 'create_extension', node: 'extension', module: this,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ category: 'create', priority: 4, label: '{{ _('Extension...') }}',
+ icon: 'wcTabIcon icon-extension', data: {action: 'create'}
+ },{
+ name: 'create_extension', node: 'database', module: this,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ category: 'create', priority: 4, label: '{{ _('Extension...') }}',
+ icon: 'wcTabIcon icon-extension', data: {action: 'create'}
+ }
+ ]);
+ },
+ // Define model for the Node and specify the properties of the model in schema.
+ model: pgAdmin.Browser.Node.Model.extend({
+ schema: [
+ {
+ id: 'name', label: '{{ _('Name')}}', first_empty: true,
+ type: 'text', mode: ['properties', 'create', 'edit'],
+ visible: true, url:'avails', disabled: function(m) {
+ return !m.isNew();
+ },
+ transform: function(data) {
+ var res = [];
+ var label = this.model.get('name');
+ if (!this.model.isNew()){
+ res.push({label: label, value: label});
+ } else {
+ if (data && _.isArray(data)) {
+ _.each(data, function(d) {
+ if (d.installed_version === null)
+ /* d contains json data and sets into
+ * select's option control
+ *
+ * We need to stringify data because formatter will
+ * convert Array Object as [Object] string
+ */
+ res.push({label: d.name, value: JSON.stringify(d)});
+ })
+ }
+ }
+ return res;
+ },
+ /* extends NodeAjaxOptionsControl to override the properties
+ * getValueFromDOM which takes stringified data from option of
+ * select control and parse it. And `onChange` takes the stringified
+ * data from select's option, thus convert it to json format and set the
+ * data into Model which is used to enable/disable the schema field.
+ */
+ control: Backform.NodeAjaxOptionsControl.extend({
+ getValueFromDOM: function() {
+ var data = this.formatter.toRaw(_.unescape(this.$el.find("select").val()), this.model);
+ /*
+ * return null if data is empty to prevent it from
+ * throwing parsing error. Adds check as name can be empty
+ */
+ if (data === ''){
+ return null;
+ }
+ else if (typeof(data) === 'string') {
+ data=JSON.parse(data);
+ }
+ return data.name;
+ },
+ /*
+ * When name is changed, extract value from its select option and
+ * set attributes values into the model
+ */
+ onChange: function() {
+ Backform.NodeAjaxOptionsControl.prototype.onChange.apply(this, arguments);
+ var selectedValue = this.$el.find("select").val();
+ if (selectedValue.trim() != ""){
+ var d = this.formatter.toRaw(selectedValue, this.model);
+ if(typeof(d) === 'string')
+ d=JSON.parse(d);
+ var changes = {
+ 'version' : '',
+ 'relocatable': ((!_.isNull(d.relocatable[0]) && !_.isUndefined(d.relocatable[0])) ? d.relocatable[0]: ''),
+ 'schema': ((!_.isNull(d.schema[0]) && !_.isUndefined(d.schema[0])) ? d.schema[0]: '')
+ };
+ this.model.set(changes);
+ }else{
+ var changes = {'version': '', 'relocatable': true, 'schema': ''};
+ this.model.set(changes);
+ }
+ },
+ })
+ },{
+ id: 'eid', label: '{{ _('Oid')}}', cell: 'string',
+ type: 'text', disabled: true, mode: ['properties', 'edit', 'create']
+ },{
+ id: 'owner', label:'{{ _('Owner') }}', control: 'node-list-by-name',
+ mode: ['properties'], node: 'role', cell: 'string'
+ },{
+ id: 'schema', label: '{{ _('Schema')}}', type: 'text', control: 'node-ajax-options',
+ mode: ['properties', 'create', 'edit'], group: 'Definition', deps: ['relocatable'],
+ url: 'schemas', first_empty: true, disabled: function(m) {
+ /* enable or disable schema field if model's relocatable
+ * attribute is True or False
+ */
+ return (m.has('relocatable') ? !m.get('relocatable') : false);
+ },
+ transform: function(data) {
+ var res = [];
+ if (data && _.isArray(data)) {
+ _.each(data, function(d) {
+ res.push({label: d.schema, value: d.schema});
+ })
+ }
+ return res;
+ }
+ },
+ {
+ id: 'relocatable', label: '{{ _('Relocatable?')}}', cell: 'switch',
+ type: 'switch', mode: ['properties'], 'options': {
+ 'onText': 'Yes', 'offText': 'No', 'onColor': 'success',
+ 'offColor': 'default', 'size': 'small'
+ }
+ },
+ {
+ id: 'version', label: '{{ _('Version')}}', cell: 'string',
+ mode: ['properties', 'create', 'edit'], group: 'Definition',
+ control: 'node-ajax-options', url:'avails', first_empty: true,
+ /*
+ * Transform the data into version for the selected extension.
+ */
+ transform: function(data) {
+ res = [];
+ var extension = this.model.get('name');
+ _.each(data, function(dt){
+ if(dt.name == extension){
+ if(dt.version && _.isArray(dt.version)){
+ _.each(dt.version, function(v){
+ res.push({ label: v, value: v });
+ });
+ }
+ }
+ });
+ return res;
+ }
+ },
+ {
+ id: 'comment', label: '{{ _('Comment')}}', cell: 'string',
+ type: 'multiline', disabled: true
+ }
+ ],
+ validate: function() {
+ /*
+ * Triggers error messages for name
+ * if it is empty/undefined/null
+ */
+ var err = {},
+ errmsg,
+ name = this.get('name');
+ if (_.isUndefined(name) || _.isNull(name) ||
+ String(name).replace(/^\s+|\s+$/g, '') == '') {
+ err['name'] = '{{ _('Name can not be empty!') }}';
+ errmsg = errmsg || err['name'];
+ this.errorModel.set('name', errmsg);
+ return errmsg;
+ }else{
+ this.errorModel.unset('name');
+ }
+ return null;
+ }
+ })
+ })
+ };
+
+ return pgBrowser.Nodes['coll-extension'];
+});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql
new file mode 100644
index 0000000..288a7cc
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql
@@ -0,0 +1,19 @@
+{#=========================Create new extension======================#}
+{#===Generates comments and code for SQL tab===#}
+{% if display_comments %}
+-- Extension: {{ conn|qtIdent(data.name) }}
+
+-- DROP EXTENSION {{ conn|qtIdent(data.name) }};
+
+{% endif %}
+{% if data.name %}
+ CREATE EXTENSION {{ conn|qtIdent(data.name) }}{% if data.schema == '' and data.version == '' %};{% endif %}
+{% if data.schema %}
+
+ SCHEMA {{ conn|qtIdent(data.schema) }}{% if data.version == '' %};{% endif %}
+{% endif %}
+{% if data.version %}
+
+ VERSION {{ conn|qtIdent(data.version) }};
+{% endif %}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql
new file mode 100644
index 0000000..44155f6
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql
@@ -0,0 +1,8 @@
+{#============================Drop/Cascade Extension by name=========================#}
+{% if eid %}
+SELECT x.extname from pg_extension x
+ WHERE x.oid = {{ eid }}::int
+{% endif %}
+{% if name %}
+DROP EXTENSION {{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql
new file mode 100644
index 0000000..bf3979d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql
@@ -0,0 +1,12 @@
+{# ======================Fetch extensions names=====================#}
+SELECT
+ a.name, a.installed_version,
+ array_agg(av.version) as version,
+ array_agg(av.schema) as schema,
+ array_agg(av.superuser) as superuser,
+ array_agg(av.relocatable) as relocatable
+FROM
+ pg_available_extensions a
+ LEFT JOIN pg_available_extension_versions av ON (a.name = av.name)
+GROUP BY a.name, a.installed_version
+ORDER BY a.name
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql
new file mode 100644
index 0000000..f652676
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql
@@ -0,0 +1,17 @@
+{#===================Fetch properties of each extension by name or oid===================#}
+SELECT
+ x.oid AS eid, pg_get_userbyid(extowner) AS owner,
+ x.extname AS name, n.nspname AS schema,
+ x.extrelocatable AS relocatable, x.extversion AS version,
+ e.comment
+FROM
+ pg_extension x
+ LEFT JOIN pg_namespace n ON x.extnamespace=n.oid
+ JOIN pg_available_extensions() e(name, default_version, comment) ON x.extname=e.name
+{%- if eid %}
+ WHERE x.oid = {{eid}}::int
+{% elif ename %}
+ WHERE x.extname = {{ename|qtLiteral}}::text
+{% else %}
+ ORDER BY x.extname
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql
new file mode 100644
index 0000000..b8e3c97
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql
@@ -0,0 +1,3 @@
+{#===================fetch all schemas==========================#}
+SELECT nspname As schema FROM pg_namespace
+ ORDER BY nspname
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql
new file mode 100644
index 0000000..c4cd626
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql
@@ -0,0 +1,10 @@
+{# =============Update extension schema============= #}
+{% if data.schema and data.schema != o_data.schema %}
+ALTER EXTENSION {{ conn|qtIdent(o_data.name) }}
+ SET SCHEMA {{ conn|qtIdent(data.schema) }};
+{% endif %}
+{# =============Update extension version============= #}
+{% if data.version and data.version != o_data.version %}
+ALTER EXTENSION {{ conn|qtIdent(o_data.name) }}
+ UPDATE TO {{ conn|qtIdent(data.version) }};
+{% endif %}
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin4] [Patch]: Extension Module
2016-01-12 07:14 [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-12 07:45 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-18 11:22 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
2016-01-18 12:14 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-21 14:34 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-22 06:55 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-02-04 04:46 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
2016-02-04 06:10 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-02-04 06:29 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
2016-02-15 09:55 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
@ 2016-02-15 11:07 ` Dave Page <[email protected]>
2016-02-23 11:07 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Dave Page @ 2016-02-15 11:07 UTC (permalink / raw)
To: Surinder Kumar <[email protected]>; +Cc: Neel Patel <[email protected]>; pgadmin-hackers
Hi
On Mon, Feb 15, 2016 at 9:55 AM, Surinder Kumar <
[email protected]> wrote:
> Hi,
>
> PFA patch with following changes:
>
> 1. Added "Create Extension" menu item in context menu of Database node.
> 2. Added a new method "node_node" in ExtensionModule class. If a node
> has child, returns True, otherwise False.
> 3. Fixed an issue in which icon won't display in create extension link
> in context menu.
> 4. Added Docstring for the class and methods in python file and proper
> commenting in js file.
> 5. Followed PEP-08 coding conventions.
>
>
I haven't tested this, but a few initial comments:
- The commenting of the JS code is better than I've seen in other patches
\o/, but the commenting style is inconsistent. We should use /* */ for
multi-line comments, and // for single line.
- The JS code could use some carefully introduced blank lines to help make
it more readable.
- s/}else{/} else {/
- Dependency/depends display is missing (see previous email to Akshay).
This is essential for this node!
- There's no pydoc comment introducing __init__.py
- Shouldn't "data='-- Modified SQL --'," be "data=gettext('-- Modified SQL
--'),"?
--
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake
EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin4] [Patch]: Extension Module
2016-01-12 07:14 [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-12 07:45 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-18 11:22 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
2016-01-18 12:14 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-21 14:34 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-22 06:55 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-02-04 04:46 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
2016-02-04 06:10 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-02-04 06:29 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
2016-02-15 09:55 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-02-15 11:07 ` Re: [pgAdmin4] [Patch]: Extension Module Dave Page <[email protected]>
@ 2016-02-23 11:07 ` Surinder Kumar <[email protected]>
2016-02-24 16:47 ` Re: [pgAdmin4] [Patch]: Extension Module Dave Page <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Surinder Kumar @ 2016-02-23 11:07 UTC (permalink / raw)
To: Dave Page <[email protected]>; +Cc: Neel Patel <[email protected]>; pgadmin-hackers
Hi,
PFA patch with changes suggested by Dave
Please review the patch and let me know for any comments.
On Mon, Feb 15, 2016 at 4:37 PM, Dave Page <[email protected]> wrote:
> Hi
>
> On Mon, Feb 15, 2016 at 9:55 AM, Surinder Kumar <
> [email protected]> wrote:
>
>> Hi,
>>
>> PFA patch with following changes:
>>
>> 1. Added "Create Extension" menu item in context menu of Database
>> node.
>> 2. Added a new method "node_node" in ExtensionModule class. If a node
>> has child, returns True, otherwise False.
>> 3. Fixed an issue in which icon won't display in create extension
>> link in context menu.
>> 4. Added Docstring for the class and methods in python file and
>> proper commenting in js file.
>> 5. Followed PEP-08 coding conventions.
>>
>>
> I haven't tested this, but a few initial comments:
>
> - The commenting of the JS code is better than I've seen in other patches
> \o/, but the commenting style is inconsistent. We should use /* */ for
> multi-line comments, and // for single line.
> - The JS code could use some carefully introduced blank lines to help
> make it more readable.
>
Done
> - s/}else{/} else {/
>
Done
> - Dependency/depends display is missing (see previous email to Akshay).
> This is essential for this node!
>
Implemented dependency and depends
> - There's no pydoc comment introducing __init__.py
>
Added pydoc
> - Shouldn't "data='-- Modified SQL --'," be "data=gettext('-- Modified SQL
> --'),"?
>
Yes, it should be. Fixed.
>
>
> --
> 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] extension_v6.patch (33.7K, 3-extension_v6.patch)
download | inline diff:
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py
new file mode 100644
index 0000000..181180a
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py
@@ -0,0 +1,484 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+""" Implements Extension Node """
+
+import json
+from flask import render_template, make_response, request, jsonify
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_json_response, \
+ make_response as ajax_response, internal_server_error
+from pgadmin.browser.utils import PGChildNodeView
+from pgadmin.browser.collection import CollectionNodeModule
+import pgadmin.browser.server_groups.servers.databases as databases
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from functools import wraps
+
+# 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
+
+
+class ExtensionModule(CollectionNodeModule):
+ """
+ class ExtensionModule(Object):
+
+ A collection Node which inherits CollectionNodeModule
+ class and define methods to get child nodes, to load its own
+ javascript file.
+ """
+ NODE_TYPE = "extension"
+ COLLECTION_LABEL = gettext("Extensions")
+
+ def __init__(self, *args, **kwargs):
+ """
+ Initialising the base class
+ """
+ super(ExtensionModule, self).__init__(*args, **kwargs)
+
+ def get_nodes(self, gid, sid, did):
+ """
+ Generate the collection node
+ """
+ yield self.generate_browser_collection_node(did)
+
+ @property
+ def node_inode(self):
+ """
+ If a node have child return True otherwise False
+ """
+ return False
+
+ @property
+ def script_load(self):
+ """
+ Load the module script for extension, when any of the database node is
+ initialized.
+ """
+ return databases.DatabaseModule.NODE_TYPE
+
+
+# Create blueprint of extension module
+blueprint = ExtensionModule(__name__)
+
+
+class ExtensionView(PGChildNodeView):
+ """
+ It is a class for extension node which inherits the
+ properties and methods from NodeView class and define
+ various methods to list, create, update and delete extension.
+
+ Variables:
+ ---------
+ * node_type - tells which type of node it is
+ * parent_ids - id with its type and name of parent nodes
+ * ids - id with type and name of extension module being used.
+ * operations - function routes mappings defined.
+ """
+ node_type = blueprint.node_type
+
+ parent_ids = [
+ {'type': 'int', 'id': 'gid'},
+ {'type': 'int', 'id': 'sid'},
+ {'type': 'int', 'id': 'did'}
+ ]
+ ids = [
+ {'type': 'int', 'id': 'eid'}
+ ]
+
+ operations = dict({
+ 'obj': [
+ {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+ {'get': 'list', 'post': 'create'}
+ ],
+ 'delete': [{'delete': 'delete'}],
+ 'nodes': [{'get': 'node'}, {'get': 'nodes'}],
+ 'sql': [{'get': 'sql'}],
+ 'msql': [{'get': 'msql'}, {'get': 'msql'}],
+ 'stats': [{'get': 'statistics'}],
+ 'dependency': [{'get': 'dependencies'}],
+ 'dependent': [{'get': 'dependents'}],
+ 'module.js': [{}, {}, {'get': 'module_js'}],
+ 'avails': [{}, {'get': 'avails'}],
+ 'schemas': [{}, {'get': 'schemas'}],
+ 'children': [{'get': 'children'}]
+ })
+
+ 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 self
+ """
+ @wraps(f)
+ def wrap(*args, **kwargs):
+ # Here args[0] will hold self & kwargs will hold gid,sid,did
+ self = args[0]
+ self.manager = get_driver(
+ PG_DEFAULT_DRIVER
+ ).connection_manager(kwargs['sid'])
+ self.conn = self.manager.connection(did=kwargs['did'])
+ self.template_path = 'extensions/sql'
+
+ return f(*args, **kwargs)
+ return wrap
+
+ @check_precondition
+ def list(self, gid, sid, did):
+ """
+ It fetches all extensions properties and render into properties
+ tab
+ """
+ SQL = render_template("/".join([self.template_path, 'properties.sql']))
+ status, res = self.conn.execute_dict(SQL)
+
+ if not status:
+ return internal_server_error(errormsg=res)
+ return ajax_response(
+ response=res['rows'],
+ status=200
+ )
+
+ @check_precondition
+ def nodes(self, gid, sid, did):
+ """
+ It lists down the all extensions under the Extensions Collection node
+ """
+ res = []
+ SQL = render_template("/".join([self.template_path, 'properties.sql']))
+ status, rset = self.conn.execute_2darray(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+
+ for row in rset['rows']:
+ res.append(
+ self.blueprint.generate_browser_node(
+ row['eid'],
+ did,
+ row['name'],
+ 'icon-extension'
+ ))
+
+ return make_json_response(
+ data=res,
+ status=200
+ )
+
+ @check_precondition
+ def properties(self, gid, sid, did, eid):
+ """
+ It fetches the properties of a single extension
+ and render in properties tab
+
+ """
+ SQL = render_template("/".join(
+ [self.template_path, 'properties.sql']), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return ajax_response(
+ response=res['rows'][0],
+ status=200
+ )
+
+ @check_precondition
+ def create(self, gid, sid, did):
+ """
+ This function will creates new the extension object
+ """
+ required_args = [
+ 'name'
+ ]
+
+ data = request.form if request.form else \
+ json.loads(request.data.decode())
+ for arg in required_args:
+ if arg not in data:
+ return make_json_response(
+ status=410,
+ success=0,
+ errormsg=gettext(
+ "Couldn't find the required parameter (%s)." % arg
+ )
+ )
+
+ status, res = self.conn.execute_dict(
+ render_template(
+ "/".join([self.template_path, 'create.sql']),
+ data=data
+ )
+ )
+
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ status, rset = self.conn.execute_dict(
+ render_template(
+ "/".join([self.template_path, 'properties.sql']),
+ ename=data['name']
+ )
+ )
+
+ if not status:
+ return internal_server_error(errormsg=rset)
+
+ for row in rset['rows']:
+ return jsonify(
+ node=self.blueprint.generate_browser_node(
+ row['eid'],
+ did,
+ row['name'],
+ 'icon-extension'
+ )
+ )
+
+ @check_precondition
+ def update(self, gid, sid, did, eid):
+ """
+ This function will update extension object
+ """
+ data = request.form if request.form else \
+ json.loads(request.data.decode())
+ SQL = self.getSQL(gid, sid, data, did, eid)
+
+ try:
+ if SQL and isinstance(SQL, basestring) and \
+ SQL.strip('\n') and SQL.strip(' '):
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return make_json_response(
+ success=1,
+ info="Extension updated",
+ data={
+ 'id': eid,
+ 'sid': sid,
+ 'gid': gid
+ }
+ )
+ else:
+ return make_json_response(
+ success=1,
+ info="Nothing to update",
+ data={
+ 'id': did,
+ 'sid': sid,
+ 'gid': gid
+ }
+ )
+
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def delete(self, gid, sid, did, eid):
+ """
+ This function will delete drop/drop cascade the extension object
+ """
+ cascade = True if self.cmd == 'delete' else False
+ try:
+ # check if extension with eid exists
+ SQL = render_template("/".join(
+ [self.template_path, 'delete.sql']), eid=eid)
+ status, name = self.conn.execute_scalar(SQL)
+ if not status:
+ return internal_server_error(errormsg=name)
+ # drop extension
+ SQL = render_template("/".join(
+ [self.template_path, 'delete.sql']
+ ), name=name, cascade=cascade)
+ status, res = self.conn.execute_scalar(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return make_json_response(
+ success=1,
+ info=gettext("Extension dropped"),
+ data={
+ 'id': did,
+ 'sid': sid,
+ 'gid': gid,
+ }
+ )
+
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def msql(self, gid, sid, did, eid=None):
+ """
+ This function to return modified SQL
+ """
+ data = request.args.copy()
+ SQL = self.getSQL(gid, sid, data, did, eid)
+ if SQL and isinstance(SQL, basestring) and SQL.strip('\n') \
+ and SQL.strip(' '):
+ return make_json_response(
+ data=SQL,
+ status=200
+ )
+ else:
+ return make_json_response(
+ data=gettext('-- Modified SQL --'),
+ status=200
+ )
+
+ def getSQL(self, gid, sid, data, did, eid=None):
+ """
+ This function will generate sql from model data
+ """
+ required_args = [
+ 'name'
+ ]
+ try:
+ if eid is not None:
+ SQL = render_template("/".join(
+ [self.template_path, 'properties.sql']
+ ), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+ old_data = res['rows'][0]
+ for arg in required_args:
+ if arg not in data:
+ data[arg] = old_data[arg]
+ SQL = render_template("/".join(
+ [self.template_path, 'update.sql']
+ ), data=data, o_data=old_data)
+ else:
+ SQL = render_template("/".join(
+ [self.template_path, 'create.sql']
+ ), data=data)
+ return SQL
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def avails(self, gid, sid, did):
+ """
+ This function with fetch all the available extensions
+ """
+ SQL = render_template("/".join([self.template_path, 'extensions.sql']))
+ status, rset = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+ return make_json_response(
+ data=rset['rows'],
+ status=200
+ )
+
+ @check_precondition
+ def schemas(self, gid, sid, did):
+ """
+ This function with fetch all the schemas
+ """
+ SQL = render_template("/".join([self.template_path, 'schemas.sql']))
+ status, rset = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+ return make_json_response(
+ data=rset['rows'],
+ status=200
+ )
+
+ def module_js(self):
+ """
+ This property defines (if javascript) exists for this node.
+ Override this property for your own logic.
+ """
+ return make_response(
+ render_template(
+ "extensions/js/extensions.js",
+ _=gettext
+ ),
+ 200, {'Content-Type': 'application/x-javascript'}
+ )
+
+ @check_precondition
+ def sql(self, gid, sid, did, eid):
+ """
+ This function will generate sql for sql panel
+ """
+ SQL = render_template("/".join(
+ [self.template_path, 'properties.sql']
+ ), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ result = res['rows'][0]
+
+ SQL = render_template("/".join(
+ [self.template_path, 'create.sql']
+ ),
+ data=result,
+ conn=self.conn,
+ display_comments=True
+ )
+
+ return ajax_response(response=SQL)
+
+ @check_precondition
+ def dependents(self, gid, sid, did, eid):
+ """
+ This function get the dependents and return ajax response
+ for the extension node.
+
+ Args:
+ gid: Server Group ID
+ sid: Server ID
+ did: Database ID
+ eid: Extension ID
+ """
+ dependents_result = self.get_dependents(self.conn, eid)
+ return ajax_response(
+ response=dependents_result,
+ status=200
+ )
+
+ @check_precondition
+ def dependencies(self, gid, sid, did, eid):
+ """
+ This function get the dependencies and return ajax response
+ for the extension node.
+
+ Args:
+ gid: Server Group ID
+ sid: Server ID
+ did: Database ID
+ lid: Extension ID
+ """
+ dependencies_result = self.get_dependencies(self.conn, eid)
+ return ajax_response(
+ response=dependencies_result,
+ status=200
+ )
+
+# Register and add ExtensionView as blueprint
+ExtensionView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/static/img/coll-extension.png b/web/pgadmin/browser/server_groups/servers/databases/extensions/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/browser/server_groups/servers/databases/extensions/static/img/extension.png b/web/pgadmin/browser/server_groups/servers/databases/extensions/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/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js
new file mode 100644
index 0000000..170a9a1
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js
@@ -0,0 +1,254 @@
+define(
+ ['jquery', 'underscore', 'underscore.string', 'pgadmin',
+ 'pgadmin.browser', 'pgadmin.browser.collection'],
+function($, _, S, pgAdmin, pgBrowser) {
+
+ /*
+ * Create and Add an Extension Collection into nodes
+ * Params:
+ * label - Label for Node
+ * type - Type of Node
+ * columns - List of columns to show under under properties.
+ */
+ if (!pgBrowser.Nodes['coll-extension']) {
+ var extensions = pgAdmin.Browser.Nodes['coll-extension'] =
+ pgAdmin.Browser.Collection.extend({
+ node: 'extension',
+ label: '{{ _('Extension') }}',
+ type: 'coll-extension',
+ columns: ['name', 'owner', 'comment']
+ });
+ };
+
+ /*
+ * Create and Add an Extension Node into nodes
+ * Params:
+ * parent_type - Name of parent Node
+ * type - Type of Node
+ * hasSQL - True if we need to show SQL query Tab control, otherwise False
+ * canDrop - True to show "Drop Extension" link under Context menu,
+ * otherwise False
+ * canDropCascade - True to show "Drop Cascade" link under Context menu,
+ * otherwise False
+ * columns - List of columns to show under under properties tab.
+ * label - Label for Node
+ */
+ if (!pgBrowser.Nodes['extension']) {
+ pgAdmin.Browser.Nodes['extension'] =
+ pgAdmin.Browser.Node.extend({
+ parent_type: 'database',
+ type: 'extension',
+ hasSQL: true,
+ hasDepends: true,
+ canDrop: true,
+ canDropCascade: true,
+ label: '{{ _('Extension') }}',
+
+ Init: function() {
+ if(this.initialized)
+ return;
+
+ this.initialized = true;
+
+ /*
+ * Add "create extension" menu item into context and object menu
+ * for the following nodes:
+ * coll-extension, extension and database.
+ */
+ pgBrowser.add_menus([{
+ name: 'create_extension_on_coll', node: 'coll-extension', module: this,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ category: 'create', priority: 4, label: '{{ _('Extension...') }}',
+ icon: 'wcTabIcon icon-extension', data: {action: 'create'}
+ },{
+ name: 'create_extension', node: 'extension', module: this,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ category: 'create', priority: 4, label: '{{ _('Extension...') }}',
+ icon: 'wcTabIcon icon-extension', data: {action: 'create'}
+ },{
+ name: 'create_extension', node: 'database', module: this,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ category: 'create', priority: 4, label: '{{ _('Extension...') }}',
+ icon: 'wcTabIcon icon-extension', data: {action: 'create'}
+ }
+ ]);
+ },
+
+ /*
+ * Define model for the Node and specify the properties
+ * of the model in schema.
+ */
+ model: pgAdmin.Browser.Node.Model.extend({
+ schema: [
+ {
+ id: 'name', label: '{{ _('Name')}}', first_empty: true,
+ type: 'text', mode: ['properties', 'create', 'edit'],
+ visible: true, url:'avails', disabled: function(m) {
+ return !m.isNew();
+ },
+ transform: function(data) {
+ var res = [];
+ var label = this.model.get('name');
+ if (!this.model.isNew()) {
+ res.push({label: label, value: label});
+ }
+ else {
+ if (data && _.isArray(data)) {
+ _.each(data, function(d) {
+ if (d.installed_version === null)
+
+ /*
+ * d contains json data and sets into
+ * select's option control
+ *
+ * We need to stringify data because formatter will
+ * convert Array Object as [Object] string
+ */
+ res.push({label: d.name, value: JSON.stringify(d)});
+ })
+ }
+ }
+ return res;
+ },
+
+ /*
+ * extends NodeAjaxOptionsControl to override the properties
+ * getValueFromDOM which takes stringified data from option of
+ * select control and parse it. And `onChange` takes the stringified
+ * data from select's option, thus convert it to json format and set the
+ * data into Model which is used to enable/disable the schema field.
+ */
+ control: Backform.NodeAjaxOptionsControl.extend({
+ getValueFromDOM: function() {
+ var data = this.formatter.toRaw(
+ _.unescape(this.$el.find("select").val()), this.model);
+ /*
+ * return null if data is empty to prevent it from
+ * throwing parsing error. Adds check as name can be empty
+ */
+ if (data === '') {
+ return null;
+ }
+ else if (typeof(data) === 'string') {
+ data=JSON.parse(data);
+ }
+ return data.name;
+ },
+
+ /*
+ * When name is changed, extract value from its select option and
+ * set attributes values into the model
+ */
+ onChange: function() {
+ Backform.NodeAjaxOptionsControl.prototype.onChange.apply(this, arguments);
+ var selectedValue = this.$el.find("select").val();
+ if (selectedValue.trim() != "") {
+ var d = this.formatter.toRaw(selectedValue, this.model);
+ if(typeof(d) === 'string')
+ d=JSON.parse(d);
+ var changes = {
+ 'version' : '',
+ 'relocatable': (
+ (!_.isNull(d.relocatable[0]) && !_.isUndefined(d.relocatable[0])) ?
+ d.relocatable[0]: ''),
+ 'schema': ((!_.isNull(d.schema[0]) &&
+ !_.isUndefined(d.schema[0])) ? d.schema[0]: '')
+ };
+ this.model.set(changes);
+ }
+ else {
+ var changes = {'version': '', 'relocatable': true, 'schema': ''};
+ this.model.set(changes);
+ }
+ },
+ })
+ },
+ {
+ id: 'eid', label: '{{ _('Oid')}}', cell: 'string',
+ type: 'text', disabled: true, mode: ['properties', 'edit', 'create']
+ },
+ {
+ id: 'owner', label:'{{ _('Owner') }}', control: 'node-list-by-name',
+ mode: ['properties'], node: 'role', cell: 'string'
+ },
+ {
+ id: 'schema', label: '{{ _('Schema')}}', type: 'text', control: 'node-ajax-options',
+ mode: ['properties', 'create', 'edit'], group: 'Definition', deps: ['relocatable'],
+ url: 'schemas', first_empty: true, disabled: function(m) {
+
+ /*
+ * enable or disable schema field if model's relocatable
+ * attribute is True or False
+ */
+ return (m.has('relocatable') ? !m.get('relocatable') : false);
+ },
+ transform: function(data) {
+ var res = [];
+ if (data && _.isArray(data)) {
+ _.each(data, function(d) {
+ res.push({label: d.schema, value: d.schema});
+ })
+ }
+ return res;
+ }
+ },
+ {
+ id: 'relocatable', label: '{{ _('Relocatable?')}}', cell: 'switch',
+ type: 'switch', mode: ['properties'], 'options': {
+ 'onText': 'Yes', 'offText': 'No', 'onColor': 'success',
+ 'offColor': 'default', 'size': 'small'
+ }
+ },
+ {
+ id: 'version', label: '{{ _('Version')}}', cell: 'string',
+ mode: ['properties', 'create', 'edit'], group: 'Definition',
+ control: 'node-ajax-options', url:'avails', first_empty: true,
+
+ // Transform the data into version for the selected extension.
+ transform: function(data) {
+ res = [];
+ var extension = this.model.get('name');
+ _.each(data, function(dt) {
+ if(dt.name == extension) {
+ if(dt.version && _.isArray(dt.version)) {
+ _.each(dt.version, function(v) {
+ res.push({ label: v, value: v });
+ });
+ }
+ }
+ });
+ return res;
+ }
+ },
+ {
+ id: 'comment', label: '{{ _('Comment')}}', cell: 'string',
+ type: 'multiline', disabled: true
+ }
+ ],
+ validate: function() {
+
+ /*
+ * Triggers error messages for name
+ * if it is empty/undefined/null
+ */
+ var err = {},
+ errmsg,
+ name = this.get('name');
+ if (_.isUndefined(name) || _.isNull(name) ||
+ String(name).replace(/^\s+|\s+$/g, '') == '') {
+ err['name'] = '{{ _('Name can not be empty!') }}';
+ errmsg = errmsg || err['name'];
+ this.errorModel.set('name', errmsg);
+ return errmsg;
+ }
+ else {
+ this.errorModel.unset('name');
+ }
+ return null;
+ }
+ })
+ })
+ };
+
+ return pgBrowser.Nodes['coll-extension'];
+});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql
new file mode 100644
index 0000000..288a7cc
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql
@@ -0,0 +1,19 @@
+{#=========================Create new extension======================#}
+{#===Generates comments and code for SQL tab===#}
+{% if display_comments %}
+-- Extension: {{ conn|qtIdent(data.name) }}
+
+-- DROP EXTENSION {{ conn|qtIdent(data.name) }};
+
+{% endif %}
+{% if data.name %}
+ CREATE EXTENSION {{ conn|qtIdent(data.name) }}{% if data.schema == '' and data.version == '' %};{% endif %}
+{% if data.schema %}
+
+ SCHEMA {{ conn|qtIdent(data.schema) }}{% if data.version == '' %};{% endif %}
+{% endif %}
+{% if data.version %}
+
+ VERSION {{ conn|qtIdent(data.version) }};
+{% endif %}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql
new file mode 100644
index 0000000..44155f6
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql
@@ -0,0 +1,8 @@
+{#============================Drop/Cascade Extension by name=========================#}
+{% if eid %}
+SELECT x.extname from pg_extension x
+ WHERE x.oid = {{ eid }}::int
+{% endif %}
+{% if name %}
+DROP EXTENSION {{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql
new file mode 100644
index 0000000..bf3979d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql
@@ -0,0 +1,12 @@
+{# ======================Fetch extensions names=====================#}
+SELECT
+ a.name, a.installed_version,
+ array_agg(av.version) as version,
+ array_agg(av.schema) as schema,
+ array_agg(av.superuser) as superuser,
+ array_agg(av.relocatable) as relocatable
+FROM
+ pg_available_extensions a
+ LEFT JOIN pg_available_extension_versions av ON (a.name = av.name)
+GROUP BY a.name, a.installed_version
+ORDER BY a.name
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql
new file mode 100644
index 0000000..f652676
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql
@@ -0,0 +1,17 @@
+{#===================Fetch properties of each extension by name or oid===================#}
+SELECT
+ x.oid AS eid, pg_get_userbyid(extowner) AS owner,
+ x.extname AS name, n.nspname AS schema,
+ x.extrelocatable AS relocatable, x.extversion AS version,
+ e.comment
+FROM
+ pg_extension x
+ LEFT JOIN pg_namespace n ON x.extnamespace=n.oid
+ JOIN pg_available_extensions() e(name, default_version, comment) ON x.extname=e.name
+{%- if eid %}
+ WHERE x.oid = {{eid}}::int
+{% elif ename %}
+ WHERE x.extname = {{ename|qtLiteral}}::text
+{% else %}
+ ORDER BY x.extname
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql
new file mode 100644
index 0000000..bd43f09
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql
@@ -0,0 +1,3 @@
+{#===================fetch all schemas==========================#}
+SELECT nspname As schema FROM pg_namespace
+ ORDER BY nspname
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql
new file mode 100644
index 0000000..c4cd626
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql
@@ -0,0 +1,10 @@
+{# =============Update extension schema============= #}
+{% if data.schema and data.schema != o_data.schema %}
+ALTER EXTENSION {{ conn|qtIdent(o_data.name) }}
+ SET SCHEMA {{ conn|qtIdent(data.schema) }};
+{% endif %}
+{# =============Update extension version============= #}
+{% if data.version and data.version != o_data.version %}
+ALTER EXTENSION {{ conn|qtIdent(o_data.name) }}
+ UPDATE TO {{ conn|qtIdent(data.version) }};
+{% endif %}
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin4] [Patch]: Extension Module
2016-01-12 07:14 [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-12 07:45 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-18 11:22 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
2016-01-18 12:14 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-21 14:34 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-22 06:55 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-02-04 04:46 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
2016-02-04 06:10 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-02-04 06:29 ` Re: [pgAdmin4] [Patch]: Extension Module Neel Patel <[email protected]>
2016-02-15 09:55 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-02-15 11:07 ` Re: [pgAdmin4] [Patch]: Extension Module Dave Page <[email protected]>
2016-02-23 11:07 ` Re: [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
@ 2016-02-24 16:47 ` Dave Page <[email protected]>
0 siblings, 0 replies; 13+ messages in thread
From: Dave Page @ 2016-02-24 16:47 UTC (permalink / raw)
To: Surinder Kumar <[email protected]>; +Cc: Neel Patel <[email protected]>; pgadmin-hackers
Thanks - committed.
On Tue, Feb 23, 2016 at 11:07 AM, Surinder Kumar <
[email protected]> wrote:
> Hi,
>
> PFA patch with changes suggested by Dave
>
> Please review the patch and let me know for any comments.
>
> On Mon, Feb 15, 2016 at 4:37 PM, Dave Page <[email protected]> wrote:
>
>> Hi
>>
>> On Mon, Feb 15, 2016 at 9:55 AM, Surinder Kumar <
>> [email protected]> wrote:
>>
>>> Hi,
>>>
>>> PFA patch with following changes:
>>>
>>> 1. Added "Create Extension" menu item in context menu of Database
>>> node.
>>> 2. Added a new method "node_node" in ExtensionModule class. If a
>>> node has child, returns True, otherwise False.
>>> 3. Fixed an issue in which icon won't display in create extension
>>> link in context menu.
>>> 4. Added Docstring for the class and methods in python file and
>>> proper commenting in js file.
>>> 5. Followed PEP-08 coding conventions.
>>>
>>>
>> I haven't tested this, but a few initial comments:
>>
>> - The commenting of the JS code is better than I've seen in other patches
>> \o/, but the commenting style is inconsistent. We should use /* */ for
>> multi-line comments, and // for single line.
>> - The JS code could use some carefully introduced blank lines to help
>> make it more readable.
>>
> Done
>
>> - s/}else{/} else {/
>>
> Done
>
>> - Dependency/depends display is missing (see previous email to Akshay).
>> This is essential for this node!
>>
> Implemented dependency and depends
>
>> - There's no pydoc comment introducing __init__.py
>>
> Added pydoc
>
>> - Shouldn't "data='-- Modified SQL --'," be "data=gettext('-- Modified
>> SQL --'),"?
>>
> Yes, it should be. Fixed.
>
>>
>>
>> --
>> 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] 13+ messages in thread
end of thread, other threads:[~2016-02-24 16:47 UTC | newest]
Thread overview: 13+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2016-01-12 07:14 [pgAdmin4] [Patch]: Extension Module Surinder Kumar <[email protected]>
2016-01-12 07:45 ` Surinder Kumar <[email protected]>
2016-01-18 11:22 ` Neel Patel <[email protected]>
2016-01-18 12:14 ` Surinder Kumar <[email protected]>
2016-01-21 14:34 ` Surinder Kumar <[email protected]>
2016-01-22 06:55 ` Surinder Kumar <[email protected]>
2016-02-04 04:46 ` Neel Patel <[email protected]>
2016-02-04 06:10 ` Surinder Kumar <[email protected]>
2016-02-04 06:29 ` Neel Patel <[email protected]>
2016-02-15 09:55 ` Surinder Kumar <[email protected]>
2016-02-15 11:07 ` Dave Page <[email protected]>
2016-02-23 11:07 ` Surinder Kumar <[email protected]>
2016-02-24 16:47 ` Dave Page <[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