From c950683fa17cc0be132a3e314e5e5b5b7d2c32b8 Mon Sep 17 00:00:00 2001 From: Surinder Kumar Date: Wed, 24 Feb 2016 16:45:35 +0000 Subject: [PATCH] Add support for extensions. --- .../servers/databases/extensions/__init__.py | 480 ++++++++++++++++++ .../extensions/static/img/coll-extension.png | Bin 0 -> 1017 bytes .../extensions/static/img/extension.png | Bin 0 -> 996 bytes .../templates/extensions/js/extensions.js | 254 +++++++++ .../templates/extensions/sql/create.sql | 19 + .../templates/extensions/sql/delete.sql | 8 + .../templates/extensions/sql/extensions.sql | 12 + .../templates/extensions/sql/properties.sql | 17 + .../templates/extensions/sql/schemas.sql | 3 + .../templates/extensions/sql/update.sql | 10 + 10 files changed, 803 insertions(+) create mode 100644 web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/extensions/static/img/coll-extension.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/extensions/static/img/extension.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js create mode 100644 web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql 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 000000000..3d1458ba0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py @@ -0,0 +1,480 @@ +########################################################################## +# +# 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 nodes are + initialized. + """ + return databases.DatabaseModule.NODE_TYPE + + +# Create blueprint of extension module +blueprint = ExtensionModule(__name__) + + +class ExtensionView(PGChildNodeView): + """ + This is a class for extension nodes 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): + """ + 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): + """ + Lists 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): + """ + Fetch 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): + """ + Create a new 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 an 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 drop/drop cascade a 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 returns 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 whether javascript exists for this node. + """ + 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 the 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 gets the dependents and returns an 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 gets the dependencies and returns an 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 zcmVPx%_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*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*Toyk;|+MLMZbK)_+XojHAH5m&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)3pD2ngr00000NkvXXu0mjfPx%`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-A^X%E{;lk+My5`xk#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?Wm1BZ;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$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 zQchC0W=QhVA)yH_FkL{}1;huk zpaKCQKz@=7s`{i97i0@vl2TS8w1C7?R&G7y;)1Nm<