public inbox for [email protected]
help / color / mirror / Atom feedFrom: Surinder Kumar <[email protected]>
To: Dave Page <[email protected]>
Cc: Neel Patel <[email protected]>
Cc: pgadmin-hackers <[email protected]>
Subject: Re: [pgAdmin4] [Patch]: Extension Module
Date: Tue, 23 Feb 2016 16:37:22 +0530
Message-ID: <CAM5-9D8cMiiUsSYt8sE+-+hhLPm0F+2ughxvohuqUMi1f_Xs0A@mail.gmail.com> (raw)
In-Reply-To: <CA+OCxowvwaqPNzn317d4CEa3HC2j8RfFNPH2SNNuJ+QpaAt=0A@mail.gmail.com>
References: <CAM5-9D9k1MGYxhf-pobDbeDJxOttVawje=JzFesEMgD=fxRR+w@mail.gmail.com>
<CAM5-9D9P6tfPajz1mUi1B03+ypLKsebehkaxWfV_C6k=ZjCSPg@mail.gmail.com>
<CACCA4P3JuzVzoq7DFmyFzjbKaE3uf-P1+4_zkBO6RfxTB-VVSQ@mail.gmail.com>
<CAM5-9D_jZUQMinr4s3upX3ktSM6AV_NWQ-RMQMjyroWMNL3bcg@mail.gmail.com>
<CAM5-9D81Yg5ofY0bjgZvSDM7Gt1t7bTuqgMSmExx1086tMT7Qg@mail.gmail.com>
<CAM5-9D-c2s9bfxVgoZMHbWNsvsiQzyHSMtx2revHz-2vaDYTJw@mail.gmail.com>
<CACCA4P2OFF8mHsRocYpq+YFV5mMZTw1yLOW-FYsHuOvrm7G0UQ@mail.gmail.com>
<CAM5-9D-FMDcV8L1FbPiEU4pCeOO1dcLu-62r5CHRuO_ks9hiSw@mail.gmail.com>
<CACCA4P27KHmsiWAJzOy3rL+9pLyg1jr=c27kbK97g+N3nAZZyQ@mail.gmail.com>
<CAM5-9D9LD+e0U2O59qdHc8zSKmazT0OytybFOQnNKLsr5SxkrA@mail.gmail.com>
<CA+OCxowvwaqPNzn317d4CEa3HC2j8RfFNPH2SNNuJ+QpaAt=0A@mail.gmail.com>
List-Unsubscribe: <mailto:[email protected]?body=unsub%20pgadmin-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 %}
view thread (13+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected]
Subject: Re: [pgAdmin4] [Patch]: Extension Module
In-Reply-To: <CAM5-9D8cMiiUsSYt8sE+-+hhLPm0F+2ughxvohuqUMi1f_Xs0A@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox